private static void SliceSpritesheets(SpritesheetData data, SpritesheetDataImporter spritesheetImporter)
        {
            string        assetDirectory = Path.GetDirectoryName(data.dataFilePath);
            List <string> textures       = new List <string>();

            if (!string.IsNullOrEmpty(data.imageFile))
            {
                textures.Add(data.imageFile);
            }

            if (data.materialData != null)
            {
                foreach (var material in data.materialData)
                {
                    if (!spritesheetImporter.sliceSecondaryTextures && material.IsSecondaryTexture)
                    {
                        Log($"Asset \"{material.file}\" is a secondary texture and slicing of secondary textures is disabled; no action taken", LogLevel.Verbose);
                        continue;
                    }

                    if (!spritesheetImporter.sliceUnidentifiedTextures && material.IsUnidentifiedTexture)
                    {
                        Log($"Asset \"{material.file}\" is an unidentified texture and slicing of unidentified textures is disabled; no action taken", LogLevel.Verbose);
                        continue;
                    }

                    textures.Add(material.file);
                }
            }

            foreach (string texturePath in textures)
            {
                string assetPath = Path.Combine(assetDirectory, texturePath);
                SliceTexture(data, assetPath, applyChanges: false);
            }
        }
        private static void SliceTexture(SpritesheetData data, string assetPath, bool applyChanges)
        {
            TextureImporter         textureImporter     = AssetImporter.GetAtPath(assetPath) as TextureImporter;
            SpritesheetDataImporter spritesheetImporter = AssetImporter.GetAtPath(data.dataFilePath) as SpritesheetDataImporter;
            Texture2D  texture     = AssetDatabase.LoadAssetAtPath <Texture2D>(assetPath).ReadableView();
            Vector2Int textureSize = GetTextureSize(textureImporter);

            var existingSprites = textureImporter.spritesheet;
            List <SpriteMetaData> newSprites = new List <SpriteMetaData>();

            int numStills = data.stills.Count;

            if (spritesheetImporter.subdivideSprites)
            {
                numStills *= spritesheetImporter.subdivisions.x * spritesheetImporter.subdivisions.y;
            }

            // TODO: might be helpful to name sprites differently if subdivisions are on, to group them by their original undivided sprite
            for (int i = 0; i < numStills; i++)
            {
                string name = data.baseObjectName + "_" + i;
                Log($"Creating sprite still for frame {i} with sprite name {name}", LogLevel.Verbose);
                newSprites.Add(CreateSpriteMetaData(i, data, FormatAssetName(name), textureImporter, spritesheetImporter, texture));
            }

            foreach (SpritesheetAnimationData animation in data.animations)
            {
                for (int i = animation.startFrame; i < animation.startFrame + animation.numFrames; i++)
                {
                    string name = data.baseObjectName + "_" + animation.name + "_rot" + animation.rotation + "_" + (i - animation.startFrame);
                    Log($"Creating sprite slice for animation frame {i} with sprite name {name}", LogLevel.Verbose);
                    newSprites.Add(CreateSpriteMetaData(i, data, FormatAssetName(name), textureImporter, spritesheetImporter, texture));
                }
            }

            Log($"Asset \"{assetPath}\" has been sliced into {newSprites.Count} sprites", LogLevel.Info);

            // Even if there's only one sprite, we import in Multiple mode, both for consistency
            // and so that we can apply trimming to that sprite
            bool importSettingsChanged = textureImporter.spriteImportMode != SpriteImportMode.Multiple;

            if (newSprites.Count != existingSprites.Length)
            {
                importSettingsChanged = true;
            }
            else
            {
                // Check each individual sprite to look for changes
                for (int i = 0; i < newSprites.Count; i++)
                {
                    var newSprite = newSprites[i];
                    var oldSprite = existingSprites[i];

                    if (!newSprite.Equals(oldSprite))
                    {
                        importSettingsChanged = true;
                        break;
                    }
                }
            }

            if (importSettingsChanged)
            {
                Log($"Asset at \"{assetPath}\" will be re-imported due to new import settings", LogLevel.Verbose);
                var newAsArray = newSprites.ToArray();

                textureImporter.spriteImportMode = SpriteImportMode.Multiple;
                textureImporter.spritesheet      = newAsArray;

                EditorUtility.SetDirty(textureImporter); // For some reason the importer is too dumb to notice it has a new spritesheet
                textureImporter.SaveAndReimport();
            }
            else
            {
                Log($"Asset at \"{assetPath}\" was already sliced correctly; not flagging for re-import", LogLevel.Verbose);
            }
        }
        private static Vector2 FindCustomPivotPoint(SpriteMetaData sprite, TextureImporter importer, SpritesheetDataImporter spritesheetImporter)
        {
            if (spritesheetImporter.customPivotMode == CustomPivotMode.Tilemap)
            {
                float pixelsPerUnit = importer.spritePixelsPerUnit;

                float x = sprite.rect.width / (pixelsPerUnit * spritesheetImporter.tilemapGridSize.x);
                float y = sprite.rect.height / (pixelsPerUnit * spritesheetImporter.tilemapGridSize.y);

                return(new Vector2(1 / (2 * x), 1 / (2 * y)));
            }
            else
            {
                throw new Exception($"Unknown customPivotMode value {spritesheetImporter.customPivotMode}");
            }
        }
        private static SpriteMetaData CreateSpriteMetaData(int frame, SpritesheetData data, string name, TextureImporter textureImporter, SpritesheetDataImporter spritesheetImporter, Texture2D texture)
        {
            int rowSubdivisions    = spritesheetImporter.subdivideSprites ? spritesheetImporter.subdivisions.y : 1;
            int columnSubdivisions = spritesheetImporter.subdivideSprites ? spritesheetImporter.subdivisions.x : 1;
            int numRows            = data.numRows * rowSubdivisions;
            int numColumns         = data.numColumns * columnSubdivisions;
            int columnWidth        = data.spriteWidth / columnSubdivisions;
            int rowHeight          = data.spriteHeight / rowSubdivisions;

            // Our spritesheets and Unity's sprite coordinate system are both row-major; however, Unity's origin is in the bottom left
            // of the texture, and ours is in the top left. We just have to do a small transformation to match.
            int row    = numRows - frame / numColumns - 1;
            int column = frame % numColumns;

            // The input texture might have padding applied, which would be at the right and the bottom. Since our coordinate system
            // and Unity's both start on the left, we don't care about the horizontal padding.
            Rect spriteRect = new Rect(columnWidth * column, data.paddingHeight + rowHeight * row, columnWidth, rowHeight);

            Log($"Frame {frame} ({name}) is in row {row} and column {column} (Unity coordinates); its subrect is {spriteRect}", LogLevel.Verbose);

            if (spritesheetImporter.trimIndividualSprites)
            {
                RectInt trimmedRect = texture.GetTrimRegion(spritesheetImporter.trimAlphaThreshold, spriteRect.ToRectInt());
                Log($"Trimmed sprite rect from {spriteRect} to {trimmedRect}", LogLevel.Verbose);

                spriteRect = trimmedRect.ToRect();
            }

            var metadata = new SpriteMetaData()
            {
                alignment = (int)spritesheetImporter.pivotPlacement,
                border    = Vector4.zero,
                name      = name,
                rect      = spriteRect
            };

            if (spritesheetImporter.pivotPlacement == SpriteAlignment.Custom)
            {
                metadata.pivot = FindCustomPivotPoint(metadata, textureImporter, spritesheetImporter);
            }

            return(metadata);
        }
Beispiel #5
0
        public override void OnInspectorGUI()
        {
            SpritesheetDataImporter importer = target as SpritesheetDataImporter;

            serializedObject.Update();

            // Animations
            EditorGUILayout.PropertyField(serializedObject.FindProperty("createAnimations"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("placeAnimationsInSubfolders"));

            // Slicing
            EditorGUILayout.PropertyField(serializedObject.FindProperty("trimIndividualSprites"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("trimAlphaThreshold"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("sliceSecondaryTextures"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("sliceUnidentifiedTextures"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("subdivideSprites"));

            if (importer.subdivideSprites)
            {
                using (new EditorGUILayout.HorizontalScope()) {
                    EditorGUILayout.PrefixLabel("Subdivisions");
                    using (new EditorGUILayout.VerticalScope()) {
                        EditorGUI.BeginChangeCheck();
                        using (new EditorGUILayout.HorizontalScope()) {
                            importer.subdivisions.y = EditorGUILayout.IntField(Mathf.Max(importer.subdivisions.y, 0));
                            EditorGUILayout.LabelField("rows");
                        }

                        using (new EditorGUILayout.HorizontalScope()) {
                            importer.subdivisions.x = EditorGUILayout.IntField(Mathf.Max(importer.subdivisions.x, 0));
                            EditorGUILayout.LabelField("columns");
                        }

                        if (EditorGUI.EndChangeCheck())
                        {
                            EditorUtility.SetDirty(target);
                        }
                    }
                }

                SpritesheetData data = AssetDatabase.LoadAssetAtPath <SpritesheetData>(importer.assetPath);

                if (data != null)
                {
                    int rowRemainder = data.spriteHeight % importer.subdivisions.y;
                    int colRemainder = data.spriteWidth % importer.subdivisions.x;

                    if (rowRemainder != 0 || colRemainder != 0)
                    {
                        EditorGUILayout.HelpBox($"Chosen subdivision values do not divide evenly into the sprite size of {data.spriteWidth}x{data.spriteHeight}. " +
                                                $"There will be a remainder of {rowRemainder} pixels per row and {colRemainder} pixels per column.", MessageType.Warning);
                    }

                    if (data.animations != null && data.animations.Count > 0)
                    {
                        EditorGUILayout.HelpBox("Subdivisions will not be applied to animations; animations will use the full sprite as defined in the ssdata file.", MessageType.Warning);
                    }
                }
                else
                {
                    Debug.Log($"Data is null for asset path {importer.assetPath}");
                }
            }

            // Pivots
            EditorGUILayout.PropertyField(serializedObject.FindProperty("pivotPlacement"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("customPivotMode"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("tilemapGridSize"));

            serializedObject.ApplyModifiedProperties();
            base.ApplyRevertGUI();
        }
Beispiel #6
0
        public override void OnInspectorGUI()
        {
            GUI.enabled = true;
            SpritesheetData data = target as SpritesheetData;

            string assetPath                 = AssetDatabase.GetAssetPath(target);
            string assetDirectory            = Path.GetDirectoryName(assetPath);
            SpritesheetDataImporter importer = AssetImporter.GetAtPath(assetPath) as SpritesheetDataImporter;

            GUIStyle labelStyle = new GUIStyle(EditorStyles.label)
            {
                alignment = TextAnchor.MiddleLeft,
                clipping  = TextClipping.Overflow
            };

            GUIStyle miniLabelStyle = new GUIStyle(EditorStyles.miniLabel)
            {
                alignment = TextAnchor.MiddleCenter,
                clipping  = TextClipping.Overflow
            };

            if (importer.subdivideSprites)
            {
                // Sprite size
                int columnWidth     = data.spriteWidth / importer.subdivisions.x;
                int columnRemainder = data.spriteWidth % importer.subdivisions.x;
                int rowHeight       = data.spriteHeight / importer.subdivisions.y;
                int rowRemainder    = data.spriteHeight % importer.subdivisions.y;

                EditorGUILayout.LabelField("Sprite Size", $"{data.spriteWidth}x{data.spriteHeight} px (base)");
                EditorGUILayout.LabelField(" ", $"{columnWidth}x{rowHeight} px (after subdividing)");

                // Sheet size (# of sprites)
                int totalColumns = importer.subdivisions.x * data.numColumns;
                int totalRows    = importer.subdivisions.y * data.numRows;

                EditorGUILayout.LabelField("Sheet Size", $"{data.numRows} rows by {data.numColumns} columns (base)");
                EditorGUILayout.LabelField(" ", $"{totalRows} rows by {totalColumns} columns (after subdividing)");
            }
            else
            {
                EditorGUILayout.LabelField("Sprite Size", $"{data.spriteWidth}x{data.spriteHeight} px");
                EditorGUILayout.LabelField("Sheet Size", $"{data.numRows} rows by {data.numColumns} columns");
            }

            EditorGUILayout.LabelField("Image Padding", $"{data.paddingWidth}x{data.paddingHeight} px");

            #region Show material data
            expandMaterials = EditorGUILayout.BeginFoldoutHeaderGroup(expandMaterials, $"Materials ({data.materialData.Count})");

            if (expandMaterials)
            {
                using (new EditorGUI.IndentLevelScope()) {
                    for (int i = 0; i < data.materialData.Count; i++)
                    {
                        var material = data.materialData[i];

                        using (new EditorGUILayout.HorizontalScope()) {
                            using (new EditorGUILayout.VerticalScope()) {
                                EditorGUILayout.LabelField($"Material {i + 1}", labelStyle, GUILayout.Width(50));
                                EditorGUILayout.LabelField($"Role: {material.MaterialRole}", labelStyle, GUILayout.Width(50));
                            }

                            string    texturePath    = Path.Combine(assetDirectory, material.file);
                            Texture2D texture        = LoadTexture(texturePath);
                            Texture2D trimmedTexture = GetTrimmedTexture(texture);

                            Rect texturePreviewArea = EditorGUILayout.GetControlRect(GUILayout.Height(texturePreviewHeight + EditorGUIUtility.singleLineHeight));

                            Rect baseTextureRect = new Rect(texturePreviewArea.x + 11 * EditorGUI.indentLevel, texturePreviewArea.y, texturePreviewWidth, texturePreviewHeight);
                            CustomGUI.DrawTexture(baseTextureRect, texture, labelBeneath: $"Original ({texture.width}x{texture.height})");

                            Rect trimmedTextureRect = new Rect(baseTextureRect.xMax + 20.0f, texturePreviewArea.y, texturePreviewWidth, texturePreviewHeight);
                            CustomGUI.DrawTexture(trimmedTextureRect, trimmedTexture, labelBeneath: $"Trimmed ({trimmedTexture.width}x{trimmedTexture.height})");

                            EditorGUILayout.Separator();
                        }
                    }
                }
            }

            EditorGUILayout.EndFoldoutHeaderGroup();
            #endregion

            #region Show animation data
            expandAnimations = EditorGUILayout.BeginFoldoutHeaderGroup(expandAnimations, $"Animations ({data.animations.Count})");

            if (expandAnimations)
            {
                EditorGUI.indentLevel++;

                for (int i = 0; i < data.animations.Count; i++)
                {
                    var  animation = data.animations[i];
                    bool animationIsPartOfRotationSet = data.animations.Count(a => a.name == animation.name) > 1;

                    if (i != 0)
                    {
                        EditorGUILayout.Space();
                    }

                    string animationLabel = animation.name;
                    if (animationIsPartOfRotationSet)
                    {
                        animationLabel += $" ({(int) animation.rotation}°)";
                    }

                    EditorGUILayout.LabelField($"Animation {i+1}", animationLabel, EditorStyles.boldLabel);

                    EditorGUI.indentLevel++;
                    EditorGUILayout.LabelField("Length", $"{animation.numFrames} frames");
                    EditorGUILayout.LabelField("Frame rate", $"{animation.frameRate} fps");
                    EditorGUILayout.LabelField("Frame skip", $"{animation.frameSkip}");
                    EditorGUILayout.LabelField("Starting frame", animation.startFrame.ToString());
                    EditorGUI.indentLevel--;
                }

                EditorGUI.indentLevel--;
            }

            EditorGUILayout.EndFoldoutHeaderGroup();
            #endregion
        }