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); }
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(); }
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 }