diff --git a/Assets/Editor/DDSAtlasSlicerWindow.cs b/Assets/Editor/DDSAtlasSlicerWindow.cs index 3f1a88088a..f79d014eb3 100644 --- a/Assets/Editor/DDSAtlasSlicerWindow.cs +++ b/Assets/Editor/DDSAtlasSlicerWindow.cs @@ -19,12 +19,6 @@ public class DDSAtlasSlicerWindow : EditorWindow [SerializeField] private float _pixelsPerUnit = 100f; [SerializeField] private bool _exportAsIndividualPngs = false; [SerializeField] private string _exportFolder = string.Empty; // Project relative (starts with Assets/) - [SerializeField] private bool _autoFitGridToAtlas = true; - [SerializeField] private int _nameStartIndex = 0; - [SerializeField] private bool _rowMajor = true; // if false => column-major - [SerializeField] private bool _startTop = true; // if false => start bottom - [SerializeField] private bool _startLeft = true; // if false => start right - [SerializeField] private bool _overrideGridFromAtlas = false; // ignore TXT col/row and derive from atlas size private string _status; @@ -47,14 +41,6 @@ public class DDSAtlasSlicerWindow : EditorWindow _paddingPixels = EditorGUILayout.IntField("Sprite Padding (px)", _paddingPixels); _pixelsPerUnit = EditorGUILayout.FloatField("Pixels Per Unit", _pixelsPerUnit); - GUILayout.Space(6); - _autoFitGridToAtlas = EditorGUILayout.ToggleLeft("Auto-fit columns/rows to atlas size (prevents OOB)", _autoFitGridToAtlas); - _nameStartIndex = EditorGUILayout.IntField("Name Start Index (offset)", _nameStartIndex); - _rowMajor = EditorGUILayout.ToggleLeft("Row-Major order (else Column-Major)", _rowMajor); - _startTop = EditorGUILayout.ToggleLeft("Names start at Top row (else Bottom)", _startTop); - _startLeft = EditorGUILayout.ToggleLeft("Names start at Left col (else Right)", _startLeft); - _overrideGridFromAtlas = EditorGUILayout.ToggleLeft("Override grid from atlas size (ignore TXT col/row)", _overrideGridFromAtlas); - GUILayout.Space(6); EditorGUILayout.LabelField("Output", EditorStyles.boldLabel); _exportAsIndividualPngs = EditorGUILayout.ToggleLeft("Export each sprite as an individual PNG file", _exportAsIndividualPngs); @@ -195,8 +181,8 @@ public class DDSAtlasSlicerWindow : EditorWindow int spriteWidth = ReadInt(lines[0]); int spriteHeight = ReadInt(lines[1]); - int columns = ReadInt(lines[2]); - int rows = ReadInt(lines[3]); + int columns = ReadInt(lines[3]); + int rows = ReadInt(lines[2]); var names = new List(); for (int i = 4; i < lines.Length; i++) @@ -207,7 +193,7 @@ public class DDSAtlasSlicerWindow : EditorWindow { if (_padIndexIfMissing) { - names.Add((i - 4 + _nameStartIndex).ToString()); + names.Add((i - 4).ToString()); } else { @@ -259,57 +245,39 @@ public class DDSAtlasSlicerWindow : EditorWindow int atlasWidth = tex.width; int atlasHeight = tex.height; - if (_overrideGridFromAtlas) - { - columns = Mathf.Max(1, atlasWidth / spriteWidth); - rows = Mathf.Max(1, atlasHeight / spriteHeight); - } - else if (_autoFitGridToAtlas) - { - int maxCols = Mathf.Max(1, atlasWidth / spriteWidth); - int maxRows = Mathf.Max(1, atlasHeight / spriteHeight); - columns = Mathf.Min(columns, maxCols); - rows = Mathf.Min(rows, maxRows); - } - if (spriteWidth <= 0 || spriteHeight <= 0) throw new InvalidDataException("Sprite width/height must be > 0."); if (columns <= 0 || rows <= 0) throw new InvalidDataException("Columns/Rows must be > 0."); int expected = columns * rows; + // Align names count + if (names.Count < expected) + { + for (int i = names.Count; i < expected; i++) + { + names.Add(_padIndexIfMissing ? i.ToString() : string.Empty); + } + } + else if (names.Count > expected) + { + names = names.Take(expected).ToList(); + } + var metas = new List(expected); for (int r = 0; r < rows; r++) { for (int c = 0; c < columns; c++) { - int rr = _startTop ? r : (rows - 1 - r); - int cc = _startLeft ? c : (columns - 1 - c); - int index = _rowMajor ? (rr * columns + cc) : (cc * rows + rr); + int index = r * columns + c; int x = c * spriteWidth; - int yFromBottom = (rows - 1 - r) * spriteHeight; // Unity rect y starts from bottom + int yFromTop = r * spriteHeight; // DDS starts from top-left + int yFromBottom = atlasHeight - yFromTop - spriteHeight; // Convert to Unity bottom-left origin var rect = new Rect(x, yFromBottom, spriteWidth, spriteHeight); - if (rect.x < 0 || rect.y < 0 || rect.xMax > atlasWidth || rect.yMax > atlasHeight) - { - if (_autoFitGridToAtlas) - { - float nx = Mathf.Clamp(rect.x, 0, Mathf.Max(0, atlasWidth - 1)); - float ny = Mathf.Clamp(rect.y, 0, Mathf.Max(0, atlasHeight - 1)); - float nw = Mathf.Clamp(rect.width, 0, atlasWidth - nx); - float nh = Mathf.Clamp(rect.height, 0, atlasHeight - ny); - rect = new Rect(nx, ny, nw, nh); - if (rect.width <= 0 || rect.height <= 0) continue; - } - else - { - continue; - } - } - string rawName = GetNameForIndex(names, index); var meta = new SpriteMetaData { - name = SafeSpriteName(rawName, index), + name = SafeSpriteName(names[index], index), rect = rect, alignment = (int)SpriteAlignment.Center, border = Vector4.zero, @@ -341,19 +309,6 @@ public class DDSAtlasSlicerWindow : EditorWindow int atlasWidth = tex.width; int atlasHeight = tex.height; - if (_overrideGridFromAtlas) - { - columns = Mathf.Max(1, atlasWidth / spriteWidth); - rows = Mathf.Max(1, atlasHeight / spriteHeight); - } - else if (_autoFitGridToAtlas) - { - int maxCols = Mathf.Max(1, atlasWidth / spriteWidth); - int maxRows = Mathf.Max(1, atlasHeight / spriteHeight); - columns = Mathf.Min(columns, maxCols); - rows = Mathf.Min(rows, maxRows); - } - if (spriteWidth <= 0 || spriteHeight <= 0) throw new InvalidDataException("Sprite width/height must be > 0."); if (columns <= 0 || rows <= 0) @@ -362,6 +317,15 @@ public class DDSAtlasSlicerWindow : EditorWindow Debug.LogWarning($"[DDSAtlasSlicer] Grid exceeds atlas bounds: grid={columns}x{rows} cell={spriteWidth}x{spriteHeight} atlas={atlasWidth}x{atlasHeight}"); int expected = columns * rows; + if (names.Count < expected) + { + for (int i = names.Count; i < expected; i++) + names.Add(_padIndexIfMissing ? i.ToString() : string.Empty); + } + else if (names.Count > expected) + { + names = names.Take(expected).ToList(); + } // Clear previous Sprite sub-assets under this texture var all = AssetDatabase.LoadAllAssetsAtPath(atlasAssetPath); @@ -377,29 +341,12 @@ public class DDSAtlasSlicerWindow : EditorWindow { for (int c = 0; c < columns; c++) { - int rr = _startTop ? r : (rows - 1 - r); - int cc = _startLeft ? c : (columns - 1 - c); - int index = _rowMajor ? (rr * columns + cc) : (cc * rows + rr); + int index = r * columns + c; int x = c * spriteWidth; - int yFromBottom = (rows - 1 - r) * spriteHeight; // bottom-left origin + int yFromTop = r * spriteHeight; // DDS starts from top-left + int yFromBottom = atlasHeight - yFromTop - spriteHeight; // Convert to Unity bottom-left origin var rect = new Rect(x, yFromBottom, spriteWidth, spriteHeight); - if (rect.x < 0 || rect.y < 0 || rect.xMax > atlasWidth || rect.yMax > atlasHeight) - { - if (_autoFitGridToAtlas) - { - float nx = Mathf.Clamp(rect.x, 0, Mathf.Max(0, atlasWidth - 1)); - float ny = Mathf.Clamp(rect.y, 0, Mathf.Max(0, atlasHeight - 1)); - float nw = Mathf.Clamp(rect.width, 0, atlasWidth - nx); - float nh = Mathf.Clamp(rect.height, 0, atlasHeight - ny); - rect = new Rect(nx, ny, nw, nh); - if (rect.width <= 0 || rect.height <= 0) continue; - } - else - { - continue; - } - } if (_paddingPixels > 0) { float nx = Mathf.Max(0, rect.x - _paddingPixels); @@ -409,7 +356,7 @@ public class DDSAtlasSlicerWindow : EditorWindow rect = new Rect(nx, ny, nw, nh); } - string sprName = SafeSpriteName(GetNameForIndex(names, index), index); + string sprName = SafeSpriteName(names[index], index); var sprite = Sprite.Create(tex, rect, new Vector2(0.5f, 0.5f), Mathf.Max(1f, _pixelsPerUnit)); sprite.name = sprName; AssetDatabase.AddObjectToAsset(sprite, tex); @@ -430,120 +377,108 @@ public class DDSAtlasSlicerWindow : EditorWindow int atlasWidth = atlasTex.width; int atlasHeight = atlasTex.height; - if (_overrideGridFromAtlas) - { - columns = Mathf.Max(1, atlasWidth / spriteWidth); - rows = Mathf.Max(1, atlasHeight / spriteHeight); - } - else if (_autoFitGridToAtlas) - { - int maxCols = Mathf.Max(1, atlasWidth / spriteWidth); - int maxRows = Mathf.Max(1, atlasHeight / spriteHeight); - columns = Mathf.Min(columns, maxCols); - rows = Mathf.Min(rows, maxRows); - } - if (spriteWidth <= 0 || spriteHeight <= 0) throw new InvalidDataException("Sprite width/height must be > 0."); if (columns <= 0 || rows <= 0) throw new InvalidDataException("Columns/Rows must be > 0."); int expected = columns * rows; + if (names.Count < expected) + { + for (int i = names.Count; i < expected; i++) + names.Add(_padIndexIfMissing ? i.ToString() : string.Empty); + } + else if (names.Count > expected) + { + names = names.Take(expected).ToList(); + } - // Resolve export folder + // Resolve export path string targetFolderProject = _exportFolder; if (string.IsNullOrEmpty(targetFolderProject)) { string atlasDir = Path.GetDirectoryName(atlasAssetPath).Replace('\\', '/'); string atlasName = Path.GetFileNameWithoutExtension(atlasAssetPath); - targetFolderProject = $"{atlasDir}/{atlasName}_slices"; + targetFolderProject = $"{atlasDir}/{atlasName}_multisprite.png"; + } + else + { + string atlasName = Path.GetFileNameWithoutExtension(atlasAssetPath); + targetFolderProject = $"{targetFolderProject}/{atlasName}_multisprite.png"; } - EnsureFolder(targetFolderProject); - string targetFolderAbs = ProjectPathToAbsolute(targetFolderProject); - var rt = new RenderTexture(atlasWidth, atlasHeight, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); - var prevActive = RenderTexture.active; + string targetFolderAbs = ProjectPathToAbsolute(Path.GetDirectoryName(targetFolderProject)); + string fileName = Path.GetFileName(targetFolderProject); + string fileAbs = Path.Combine(targetFolderAbs, fileName); + fileAbs = GetUniqueFilePath(fileAbs); + + // Create a readable copy of the texture + Texture2D readableTex = new Texture2D(atlasWidth, atlasHeight, TextureFormat.RGBA32, false); + RenderTexture rt = RenderTexture.GetTemporary(atlasWidth, atlasHeight, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); Graphics.Blit(atlasTex, rt); RenderTexture.active = rt; + readableTex.ReadPixels(new Rect(0, 0, atlasWidth, atlasHeight), 0, 0); + readableTex.Apply(); + RenderTexture.active = null; + RenderTexture.ReleaseTemporary(rt); - try + // Create PNG from readable copy + var png = ImageConversion.EncodeToPNG(readableTex); + File.WriteAllBytes(fileAbs, png); + + // Clean up + UnityEngine.Object.DestroyImmediate(readableTex); + + string fileProject = AbsoluteToProjectPath(fileAbs).Replace('\\', '/'); + if (!string.IsNullOrEmpty(fileProject)) { - for (int r = 0; r < rows; r++) + AssetDatabase.ImportAsset(fileProject); + var pngImporter = AssetImporter.GetAtPath(fileProject) as TextureImporter; + if (pngImporter != null) { - for (int c = 0; c < columns; c++) + pngImporter.textureType = TextureImporterType.Sprite; + pngImporter.spriteImportMode = SpriteImportMode.Multiple; + pngImporter.mipmapEnabled = false; + pngImporter.alphaIsTransparency = true; + pngImporter.spritePixelsPerUnit = Mathf.Max(1f, _pixelsPerUnit); + + // Create sprite metadata + var metas = new List(expected); + for (int r = 0; r < rows; r++) { - int rr = _startTop ? r : (rows - 1 - r); - int cc = _startLeft ? c : (columns - 1 - c); - int index = _rowMajor ? (rr * columns + cc) : (cc * rows + rr); - int x = c * spriteWidth; - int yFromBottom = (rows - 1 - r) * spriteHeight; - - var rect = new Rect(x, yFromBottom, spriteWidth, spriteHeight); - if (rect.x < 0 || rect.y < 0 || rect.xMax > atlasWidth || rect.yMax > atlasHeight) - { - if (_autoFitGridToAtlas) + for (int c = 0; c < columns; c++) { - float nx = Mathf.Clamp(rect.x, 0, Mathf.Max(0, atlasWidth - 1)); - float ny = Mathf.Clamp(rect.y, 0, Mathf.Max(0, atlasHeight - 1)); - float nw = Mathf.Clamp(rect.width, 0, atlasWidth - nx); - float nh = Mathf.Clamp(rect.height, 0, atlasHeight - ny); - rect = new Rect(nx, ny, nw, nh); - if (rect.width <= 0 || rect.height <= 0) continue; - } - else - { - continue; - } - } - if (_paddingPixels > 0) - { - float nx = Mathf.Max(0, rect.x - _paddingPixels); - float ny = Mathf.Max(0, rect.y - _paddingPixels); - float nw = Mathf.Min(atlasWidth - nx, rect.width + 2 * _paddingPixels); - float nh = Mathf.Min(atlasHeight - ny, rect.height + 2 * _paddingPixels); - rect = new Rect(nx, ny, nw, nh); - } + int index = r * columns + c; + int x = c * spriteWidth; + int yFromTop = r * spriteHeight; // DDS starts from top-left + int yFromBottom = atlasHeight - yFromTop - spriteHeight; // Convert to Unity bottom-left origin - int w = Mathf.RoundToInt(rect.width); - int h = Mathf.RoundToInt(rect.height); - if (w <= 0 || h <= 0) continue; - - var slice = new Texture2D(w, h, TextureFormat.RGBA32, false, false); - slice.ReadPixels(rect, 0, 0); - slice.Apply(false, false); - - string sprName = SafeSpriteName(GetNameForIndex(names, index), index); - if (string.IsNullOrWhiteSpace(sprName)) sprName = $"sprite_{index:0000}"; - string fileAbs = Path.Combine(targetFolderAbs, sprName + ".png"); - fileAbs = GetUniqueFilePath(fileAbs); - var png = ImageConversion.EncodeToPNG(slice); - File.WriteAllBytes(fileAbs, png); - UnityEngine.Object.DestroyImmediate(slice); - - string fileProject = AbsoluteToProjectPath(fileAbs).Replace('\\', '/'); - if (!string.IsNullOrEmpty(fileProject)) - { - AssetDatabase.ImportAsset(fileProject); - var pngImporter = AssetImporter.GetAtPath(fileProject) as TextureImporter; - if (pngImporter != null) + var rect = new Rect(x, yFromBottom, spriteWidth, spriteHeight); + if (_paddingPixels > 0) { - pngImporter.textureType = TextureImporterType.Sprite; - pngImporter.spriteImportMode = SpriteImportMode.Single; - pngImporter.mipmapEnabled = false; - pngImporter.alphaIsTransparency = true; - pngImporter.spritePixelsPerUnit = Mathf.Max(1f, _pixelsPerUnit); - pngImporter.SaveAndReimport(); + float nx = Mathf.Max(0, rect.x - _paddingPixels); + float ny = Mathf.Max(0, rect.y - _paddingPixels); + float nw = Mathf.Min(atlasWidth - nx, rect.width + 2 * _paddingPixels); + float nh = Mathf.Min(atlasHeight - ny, rect.height + 2 * _paddingPixels); + rect = new Rect(nx, ny, nw, nh); } + + var meta = new SpriteMetaData + { + name = SafeSpriteName(names[index], index), + rect = rect, + alignment = (int)SpriteAlignment.Center, + border = Vector4.zero, + pivot = new Vector2(0.5f, 0.5f) + }; + metas.Add(meta); } } + + pngImporter.spritesheet = metas.ToArray(); + pngImporter.SaveAndReimport(); } } - finally - { - RenderTexture.active = prevActive; - rt.Release(); - UnityEngine.Object.DestroyImmediate(rt); - } AssetDatabase.Refresh(); } @@ -637,20 +572,5 @@ public class DDSAtlasSlicerWindow : EditorWindow } return sb.ToString(); } - - private string GetNameForIndex(List names, int gridIndex) - { - int idx = gridIndex; - if (_nameStartIndex != 0) - { - idx = gridIndex + _nameStartIndex; - } - if (idx >= 0 && idx < names.Count) - { - string n = names[idx]; - if (!string.IsNullOrWhiteSpace(n)) return n; - } - return gridIndex.ToString(); - } } #endif