namespace AtlasUtility { using UnityEditor; using UnityEngine; using System; using System.IO; using System.Linq; using System.Text; using System.Reflection; using System.Collections.Generic; using Random = UnityEngine.Random; public class AtlasUtilityWindow : EditorWindow { protected class Parameter { public int Padding; public int AtlasSize; public float ScaleFactor; public string Path; public List Pathes; public PackPlan PackPlan; public Texture2D Atlas; public Texture2D EditorTexture; public List EditorTextures; public ScalePlatform ScalePlatform; public List TextureList; public List VirtualTextureList; } protected class TextureInfo { #region Variable public int Max { get { if (Width > Height) { return Width; } else { return Height; } } } public int Area { get { return Width * Height; } } public string Name { get { return Texture.name; } } public Rect Rect { get { return new Rect(LowerLeft.x + Padding, LowerLeft.y + Padding, Width - 2 * Padding, Height - 2 * Padding); } } public Vector2 LowerLeft; public Vector2 UpperLeft { get { return LowerLeft + new Vector2(0, Height); } } public Vector2 LowerRight { get { return LowerLeft + new Vector2(Width, 0); } } public Vector2 UpperRight { get { return LowerLeft + new Vector2(Width, Height); } } public Color[] Colors; public Vector4 Pivot; public Vector4 Border; public int Width; public int Height; public int RawWidth; public int RawHeight; public int Padding; public string GUID; public Texture2D Texture; #endregion public TextureInfo(int padding, Texture2D texture2D) { Texture = texture2D; string assetPath = AssetDatabase.GetAssetPath(texture2D); TextureImporter textureImporter = (TextureImporter)AssetImporter.GetAtPath(assetPath); Pivot = textureImporter.spritePivot; Border = textureImporter.spriteBorder; GUID = AssetDatabase.AssetPathToGUID(assetPath); RawWidth = Texture.width; RawHeight = Texture.height; object[] args = new object[2]; MethodInfo methodInfo = typeof(TextureImporter).GetMethod("GetWidthAndHeight", BindingFlags.NonPublic | BindingFlags.Instance); methodInfo.Invoke(textureImporter, args); int originWidth = (int)args[0]; int originHeight = (int)args[1]; if (RawWidth < originWidth || RawHeight < originHeight) { Debug.LogWarning($"注意 {texture2D.name} 将被缩小"); } if (!textureImporter.isReadable) { textureImporter.isReadable = true; textureImporter.SaveAndReimport(); } Colors = texture2D.GetPixels(0, 0, RawWidth, RawHeight); Width = RawWidth + padding * 2; Height = RawHeight + padding * 2; Padding = padding; } public TextureInfo(int padding, Texture2D texture2D, int width, int height) { Width = width + padding * 2; Height = height + padding * 2; Padding = padding; Texture = texture2D; } } protected class Box { #region MyRegion public bool Valid { get { if (Width > 0 && Height > 0) { return true; } else { return false; } } } public Vector2 LowerLeft; public Vector2 UpperLeft { get { return LowerLeft + new Vector2(0, Height); } } public Vector2 LowerRight { get { return LowerLeft + new Vector2(Width, 0); } } public Vector2 UpperRight { get { return LowerLeft + new Vector2(Width, Height); } } public int Width; public int Height; #endregion public Box(int width, int height, Vector2 lowerLeft) { Width = width; Height = height; LowerLeft = lowerLeft; } public bool CanFill(TextureInfo textureInfo) { return textureInfo.Width <= Width && textureInfo.Height <= Height; } } protected class JagBox { protected class Pair { public int Low; public int High; public Pair(int low, int high) { Low = low; High = high; } } #region Variable public bool Valid { get { if (BoxList.Count == 0) { return false; } else { return true; } } } public int Width { get { return (int)BoxList.MySum(box => box.Width); } } public Vector2 UpperRight; public List BoxList; protected Dictionary XCoordDic; protected Dictionary YCoordDic; #endregion public JagBox(List boxList) { BoxList = boxList; if (boxList.Count == 0) { return; } UpperRight = boxList.Back(0).UpperRight; XCoordDic = new Dictionary(); YCoordDic = new Dictionary(); for (int i = 0; i < boxList.Count; i++) { Pair xPair = new Pair((int) boxList[i].LowerLeft.x, (int) boxList[i].LowerRight.x); Pair yPair = new Pair((int) boxList[i].LowerLeft.y, (int) boxList[i].UpperRight.y); XCoordDic.Add(yPair, xPair); YCoordDic.Add(xPair, yPair); } } public bool CanFill(TextureInfo textureInfo, ref int startIndex, ref int endIndex) { if (textureInfo.Width > Width) { return false; } endIndex = BoxList.Count - 1; startIndex = GetStartIndex(textureInfo.Width); reLoop: for (int i = startIndex; i <= endIndex; i++) { if (BoxList[i].Height < textureInfo.Height) { if (startIndex > 0) { startIndex--; endIndex--; goto reLoop; } else { return false; } } } return true; } protected int GetStartIndex(int fillWidth) { int x = (int)UpperRight.x - fillWidth; List xPairList = YCoordDic.Keys.ToList(); for (int i = xPairList.Count - 1; i >= 0; i--) { if (xPairList[i].Low <= x && x <= xPairList[i].High) { return i; } } throw new Exception("遇到一个Bug"); } public List Fill(TextureInfo textureInfo, int startIndex, int endIndex) { textureInfo.LowerLeft = BoxList[endIndex].UpperRight + new Vector2(-textureInfo.Width, -textureInfo.Height); int fillWidth = textureInfo.Width; int fillHeight = textureInfo.Height; Box newLeftBox; Box newDownBox; List leftBoxList = new List(); List downBoxList = new List(); if (fillWidth > fillHeight) { VerticalSlice(startIndex, endIndex, fillWidth, fillHeight, out newLeftBox, out newDownBox); } else { HorizontalSlice(startIndex, endIndex, fillWidth, fillHeight, out newLeftBox, out newDownBox); } for (int i = 0; i < startIndex; i++) { leftBoxList.Add(BoxList[i]); } if (newLeftBox.Valid) { leftBoxList.Add(newLeftBox); } if (newDownBox.Valid) { downBoxList.Add(newDownBox); } for (int i = startIndex + 1; i < endIndex; i++) { downBoxList.Add(new Box(BoxList[i].Width, BoxList[i].Height - fillHeight, BoxList[i].LowerLeft)); } JagBox leftJagBox = new JagBox(leftBoxList); JagBox downJagBox = new JagBox(downBoxList); List jagBoxList = new List(); if (downJagBox.Valid) { jagBoxList.Add(downJagBox); } if (leftJagBox.Valid) { jagBoxList.Add(leftJagBox); } return jagBoxList; } protected void VerticalSlice(int startIndex, int endIndex, int fillWidth, int fillHeight, out Box newLeftBox, out Box newDownBox) { Box endBox = BoxList[endIndex]; Box startBox = BoxList[startIndex]; int relativeFillWidth = (int) (fillWidth - (endBox.UpperRight.x - startBox.UpperRight.x)); int leftBoxWidth = startBox.Width - relativeFillWidth; int leftBoxHeight = startBox.Height; Vector2 leftBoxLowerLeft = startBox.LowerLeft; newLeftBox = new Box(leftBoxWidth, leftBoxHeight, leftBoxLowerLeft); int downBoxWidth = relativeFillWidth; int downBoxHeight = startBox.Height - fillHeight; Vector2 downBoxLowerLeft = startBox.LowerLeft + new Vector2(startBox.Width - relativeFillWidth, 0); newDownBox = new Box(downBoxWidth, downBoxHeight, downBoxLowerLeft); } protected void HorizontalSlice(int startIndex, int endIndex, int fillWidth, int fillHeight, out Box newLeftBox, out Box newDownBox) { Box endBox = BoxList[endIndex]; Box startBox = BoxList[startIndex]; int relativeFillWidth = (int)(fillWidth - (endBox.UpperRight.x - startBox.UpperRight.x)); int leftBoxWidth = startBox.Width - relativeFillWidth; int leftBoxHeight = fillHeight; Vector2 leftBoxLowerLeft = startBox.LowerLeft + new Vector2(0, startBox.Height - fillHeight); newLeftBox = new Box(leftBoxWidth, leftBoxHeight, leftBoxLowerLeft); int downBoxWidth = startBox.Width; int downBoxHeight = startBox.Height - fillHeight; Vector2 downBoxLowerLeft = startBox.LowerLeft; newDownBox = new Box(downBoxWidth, downBoxHeight, downBoxLowerLeft); } } protected class Atlas { #region Variable public int Width; public int Height; public string AssetBundleName; public string AssetBundleVariant; public List TextureInfoList; #endregion public void Create(int index, Parameter parameter) { SpriteMetaData[] spriteMetaDatas = new SpriteMetaData[TextureInfoList.Count]; Color[] atlasColors = new Color[Width * Height]; for (int i = 0; i < TextureInfoList.Count; i++) { TextureInfo textureInfo = TextureInfoList[i]; Vector2 lowerLeft = textureInfo.LowerLeft + new Vector2(parameter.Padding, parameter.Padding); spriteMetaDatas[i].rect = textureInfo.Rect; spriteMetaDatas[i].pivot = textureInfo.Pivot; spriteMetaDatas[i].name = textureInfo.Name; spriteMetaDatas[i].border = textureInfo.Border; spriteMetaDatas[i].alignment = (int)SpriteAlignment.Custom; for (int j = 0; j < textureInfo.RawHeight; j++) { for (int k = 0; k < textureInfo.RawWidth; k++) { int row = (int)lowerLeft.y + j; int column = (int)lowerLeft.x + k; atlasColors[row * Width + column] = textureInfo.Colors[j * textureInfo.RawWidth + k]; } } } string path = index == 0 ? parameter.Path + ".png" : $"{parameter.Path} ({index}).png"; if (File.Exists(path)) { TextureImporter importer = (TextureImporter)AssetImporter.GetAtPath(path); AssetBundleName = importer.assetBundleName; AssetBundleVariant = importer.assetBundleVariant; AssetDatabase.DeleteAsset(path); AssetDatabase.Refresh(); } Texture2D texture2D = new Texture2D(Width, Height, TextureFormat.RGBA32, false); texture2D.SetPixels(atlasColors); texture2D.Apply(); File.WriteAllBytes(path, texture2D.EncodeToPNG()); AssetDatabase.ImportAsset(path); TextureImporter textureImporter = (TextureImporter)AssetImporter.GetAtPath(path); if (!string.IsNullOrEmpty(AssetBundleName)) { textureImporter.assetBundleName = AssetBundleName; } if (!string.IsNullOrEmpty(AssetBundleVariant)) { textureImporter.assetBundleVariant = AssetBundleVariant; } textureImporter.spritesheet = spriteMetaDatas; textureImporter.maxTextureSize = Mathf.Max(Width, Height); textureImporter.isReadable = true; textureImporter.alphaIsTransparency = true; textureImporter.textureType = TextureImporterType.Sprite; textureImporter.spriteImportMode = SpriteImportMode.Multiple; textureImporter.SaveAndReimport(); string atlasGUID = AssetDatabase.AssetPathToGUID(path); List referencePairList = new List(); for (int i = 0; i < spriteMetaDatas.Length; i++) { AtlasReference atlasReference = new AtlasReference((21300000 + i * 2).ToString(), atlasGUID); AtlasReference sourceAtlasReference = new AtlasReference("21300000", TextureInfoList[i].GUID); referencePairList.Add(new AtlasReferencePair(atlasReference, sourceAtlasReference)); } AtlasReferenceTable.WriteAllLine(referencePairList, true); } } #region Variable protected AtlasUtility Instance; protected SerializedObject SerializedObject; protected Vector2 ScrollPosition; protected GUIStyle TitleGuiStyle; protected SerializedProperty PackSize; protected SerializedProperty packPlan; protected SerializedProperty PackPath; protected SerializedProperty PackAtlas; protected SerializedProperty PackName; protected SerializedProperty PackPadding; protected SerializedProperty TextureList; protected SerializedProperty VirtualTextureList; protected SerializedProperty SlicePath; protected SerializedProperty SliceAtlas; protected SerializedProperty SlicePadding; protected SerializedProperty SearchTarget; protected SerializedProperty PlatformSet; protected SerializedProperty ScalePadding; protected SerializedProperty ScaleFactor; protected SerializedProperty ScalePath; protected SerializedProperty ScaleName; protected SerializedProperty MobileTexture; protected SerializedProperty DesktopTexture; protected SerializedProperty EditorTexture; protected SerializedProperty LastEditorTexture; protected SerializedProperty EditorTextures; protected SerializedProperty ScalePlatform; #endregion [MenuItem("DashGame/AtlasUtility")] protected static void ShowWindow() { Type inspectorType = Type.GetType("UnityEditor.InspectorWindow,UnityEditor.dll"); AtlasUtilityWindow window = GetWindow(inspectorType); window.titleContent = new GUIContent("AtlasUtility"); window.Show(); } protected void OnEnable() { Instance = InstanceManager.SearchInstance(); SerializedObject = new SerializedObject(Instance); PackSize = SerializedObject.FindProperty("PackSize"); PackPath = SerializedObject.FindProperty("PackPath"); PackName = SerializedObject.FindProperty("PackName"); SlicePath = SerializedObject.FindProperty("SlicePath"); SlicePadding = SerializedObject.FindProperty("SlicePadding"); PackAtlas = SerializedObject.FindProperty("PackAtlas"); SearchTarget = SerializedObject.FindProperty("SearchTarget"); TextureList = SerializedObject.FindProperty("TextureList"); SliceAtlas = SerializedObject.FindProperty("SliceAtlas"); PackPadding = SerializedObject.FindProperty("PackPadding"); VirtualTextureList = SerializedObject.FindProperty("VirtualTextureList"); packPlan = SerializedObject.FindProperty("PackPlan"); PlatformSet = SerializedObject.FindProperty("PlatformSet"); ScalePadding = SerializedObject.FindProperty("ScalePadding"); ScaleFactor = SerializedObject.FindProperty("ScaleFactor"); ScalePath = SerializedObject.FindProperty("ScalePath"); ScaleName = SerializedObject.FindProperty("ScaleName"); MobileTexture = SerializedObject.FindProperty("MobileTexture"); DesktopTexture = SerializedObject.FindProperty("DesktopTexture"); EditorTexture = SerializedObject.FindProperty("EditorTexture"); EditorTextures = SerializedObject.FindProperty("EditorTextures"); LastEditorTexture = SerializedObject.FindProperty("LastEditorTexture"); ScalePlatform = SerializedObject.FindProperty("ScalePlatform"); TitleGuiStyle = new GUIStyle { fontSize = 20, alignment = TextAnchor.MiddleCenter, normal = {textColor = new Color(0.75f, 0.75f, 0.75f, 1)} }; } protected void Pack() { Parameter parameter = CreatePackParameter(); List textureInfoList = CreateTextureInfoList(parameter); if (textureInfoList.Count == 0) { throw new Exception("没有可用的Texture"); } EditorUtility.DisplayProgressBar("打包", "计算排列位置", 0.5f); List atlasList = new List(); while (textureInfoList.Count > 0) { Box box = CreateBox(parameter, textureInfoList); textureInfoList = FillBox(parameter, box, textureInfoList, atlasList); } if (EditorUtility.DisplayDialog("打包", $"将新建{atlasList.Count}张图集", "继续", "取消")) { for (int i = 0; i < atlasList.Count; i++) { EditorUtility.DisplayProgressBar("打包", "新建图集", (i + 1)/(float) atlasList.Count); atlasList[i].Create(i, parameter); } } EndPack(); } protected Parameter CreatePackParameter() { string directory = Instance.PackPath.TrimEnd('/', '\\') + "/"; if (directory.Length < 6 || directory.Substring(0, 6).ToLower() != "assets") { throw new Exception("PackPath必须位置Assets目录内"); } if (!Directory.Exists(directory)) { throw new Exception("文件夹不存在"); } if (string.IsNullOrEmpty(Instance.PackName) || Instance.PackName.Any(Path.GetInvalidFileNameChars().Contains)) { throw new Exception("图集名字无效"); } if (Instance.PackPadding < 0) { Instance.PackPadding = 0; } if (Instance.PackPlan == PackPlan.Fixed) { if (Instance.PackSize <= 2) { throw new Exception("图集尺寸必须大于2"); } if (!Mathf.IsPowerOfTwo(Instance.PackSize)) { throw new Exception("图集尺寸必须为2次幂"); } } else if (Instance.PackPlan == PackPlan.Smallest) { if (Instance.PackSize <= 0) { Instance.PackSize = 8192; } else { Instance.PackSize = ExMath.PrevPOT(Instance.PackSize); } } for (int i = 0; i < Instance.TextureList.Count; i++) { for (int j = i+1; j < Instance.TextureList.Count; j++) { if (Instance.TextureList[i].name == Instance.TextureList[j].name) { throw new Exception($"Sprite名字重复 {Instance.TextureList[i].name}"); } } } Parameter parameter = new Parameter { Path = directory + Instance.PackName, PackPlan = Instance.PackPlan, Padding = Instance.PackPadding, AtlasSize = Instance.PackSize, TextureList = Instance.TextureList, VirtualTextureList = Instance.VirtualTextureList }; return parameter; } protected List CreateTextureInfoList(Parameter parameter) { List textureInfoList = new List(); for (int i = 0; i < parameter.TextureList.Count; i++) { if (parameter.TextureList[i] != null) { EditorUtility.DisplayProgressBar("打包", "读取Texture", (i + 1)/(float) parameter.TextureList.Count); textureInfoList.Add(new TextureInfo(parameter.Padding, parameter.TextureList[i])); } } for (int i = 0; i < parameter.VirtualTextureList.Count; i++) { Texture2D texture = new Texture2D(parameter.VirtualTextureList[i].Width, parameter.VirtualTextureList[i].Height) {name = parameter.VirtualTextureList[i].Name}; Color color = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f)); for (int j = 0; j < parameter.VirtualTextureList[i].Width; j++) { for (int k = 0; k < parameter.VirtualTextureList[i].Height; k++) { texture.SetPixel(j, k, color); } } texture.Apply(); textureInfoList.Add(new TextureInfo(parameter.Padding, texture, parameter.VirtualTextureList[i].Width, parameter.VirtualTextureList[i].Height)); } EditorUtility.ClearProgressBar(); return textureInfoList; } protected Box CreateBox(Parameter parameter, List textureInfoList) { int maxTextureLength = (int) textureInfoList.MyMax(info => info.Max); if (maxTextureLength > parameter.AtlasSize) { EndPack(); throw new Exception("一张Texture的宽或高大于了图集尺寸"); } if (parameter.PackPlan == PackPlan.Fixed) { return new Box(parameter.AtlasSize, parameter.AtlasSize, new Vector2(0, 0)); } else if (parameter.PackPlan == PackPlan.Smallest) { int totalArea = textureInfoList.Sum(debris => debris.Area); int length = ExMath.NextPOT(Mathf.CeilToInt(Mathf.Sqrt(totalArea))); length = Mathf.Max(length, maxTextureLength); length = Mathf.Min(length, parameter.AtlasSize); if (length* length/2f >= totalArea) { return new Box(length, length/2, new Vector2(0, 0)); } else { return new Box(length, length, new Vector2(0, 0)); } } else { throw new Exception(); } } protected List CreateJagBox(List emptyBoxList) { if (emptyBoxList.Count == 0) { return new List(); ; } List jagBoxList = new List(); reLoop : List boxList = new List() {emptyBoxList[0]}; for (int i = 0; i < emptyBoxList.Count - 1; i++) { int x1 = (int)emptyBoxList[i].LowerRight.x; int x2 = (int)emptyBoxList[i + 1].LowerLeft.x; if (x1 == x2) { boxList.Add(emptyBoxList[i + 1]); emptyBoxList.RemoveAt(i--); } else { jagBoxList.Add(new JagBox(boxList)); goto reLoop; } } jagBoxList.Add(new JagBox(boxList)); return jagBoxList; } protected List FillBox(Parameter parameter, Box box, List textureInfoList, List atlasList) { textureInfoList.MySort((info1, info2) => info1.Width < info2.Width); while (true) { List emptyBoxList = new List(); List usedTextureInfoList = new List(); List remainTexureInfoList = new List(textureInfoList); FillChildBox(box, remainTexureInfoList, usedTextureInfoList, emptyBoxList); List jagBoxList = CreateJagBox(emptyBoxList); remainTexureInfoList.MySort((info1, info2) => info1.Max < info2.Max); FillJagBox(jagBoxList, remainTexureInfoList, usedTextureInfoList); if (remainTexureInfoList.Count == 0) { atlasList.Add(CreateAtlas(box, usedTextureInfoList)); return remainTexureInfoList; } else { if (box.Width > box.Height) { box = new Box(box.Height, box.Width, Vector2.zero); } else if (box.Height > box.Width) { box = new Box(box.Height, box.Height, Vector2.zero); } else if (box.Width == box.Height) { int newLength = ExMath.NextPOT(box.Width + 1); if (newLength > parameter.AtlasSize) { atlasList.Add(CreateAtlas(box, usedTextureInfoList)); return remainTexureInfoList; } else { box = new Box(newLength, newLength/2, Vector2.zero); } } } } } protected void FillChildBox(Box box, List remainTextureInfoList, List usedTextureInfoList, List emptyBoxList) { if (box.Valid) { for (int i = 0; i < remainTextureInfoList.Count; i++) { if (box.CanFill(remainTextureInfoList[i])) { TextureInfo textureInfo = remainTextureInfoList[i]; textureInfo.LowerLeft = box.LowerLeft; remainTextureInfoList.Remove(textureInfo); usedTextureInfoList.Add(textureInfo); Vector2 upChildBoxLowerLeft = box.LowerLeft + new Vector2(0, textureInfo.Height); Vector2 rightChildBoxLowerLeft = box.LowerLeft + new Vector2(textureInfo.Width, 0); Box upChildBox = new Box(textureInfo.Width, box.Height - textureInfo.Height, upChildBoxLowerLeft); Box rightChildBox = new Box(box.Width - textureInfo.Width, box.Height, rightChildBoxLowerLeft); FillChildBox(upChildBox, remainTextureInfoList, usedTextureInfoList, emptyBoxList); FillChildBox(rightChildBox, remainTextureInfoList, usedTextureInfoList, emptyBoxList); return; } } emptyBoxList.Add(box); } } protected void FillJagBox(List jagBoxList, List remainTextureInfoList, List usedTextureInfoList) { for (int i = 0; i < jagBoxList.Count; i++) { for (int j = 0; j < remainTextureInfoList.Count; j++) { int startIndex = 0; int endIndex = 0; if (jagBoxList[i].CanFill(remainTextureInfoList[j], ref startIndex, ref endIndex)) { jagBoxList.AddRange(jagBoxList[i].Fill(remainTextureInfoList[j], startIndex, endIndex)); usedTextureInfoList.Add(remainTextureInfoList[j]); remainTextureInfoList.Remove(remainTextureInfoList[j]); break; } } } } protected Atlas CreateAtlas(Box box, List textureInfoList) { Atlas atlas = new Atlas { Width = box.Width, Height = box.Height, TextureInfoList = textureInfoList, }; return atlas; } protected void EndPack() { EditorUtility.ClearProgressBar(); } protected void CollectDebugInfo() { Parameter parameter = CreateDebugParameter(); List textureInfoList = CreateTextureInfoList(parameter); if (textureInfoList.Count == 0) { throw new Exception("没有可用的Texture"); } StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < textureInfoList.Count; i++) { stringBuilder.AppendLine($"Width:{textureInfoList[i].Width} Height:{textureInfoList[i].Height} Pivot:{textureInfoList[i].Pivot} Border:{textureInfoList[i].Border}"); } StreamWriter streamWriter = new StreamWriter(parameter.Path); streamWriter.WriteLine(Application.unityVersion); streamWriter.Write(stringBuilder.ToString()); streamWriter.Close(); AssetDatabase.ImportAsset(parameter.Path); } protected Parameter CreateDebugParameter() { Parameter parameter = new Parameter { Path = "Assets/DebugInfo.txt".GetUnRepeatFileName(), TextureList = Instance.TextureList, VirtualTextureList = Instance.VirtualTextureList }; return parameter; } protected void Slice() { Parameter parameter = CreateSliceParameter(); string spriteSheetPath = AssetDatabase.GetAssetPath(parameter.Atlas); TextureImporter textureImporter = (TextureImporter)AssetImporter.GetAtPath(spriteSheetPath); if (textureImporter.spritesheet.Length == 0) { throw new Exception("该图片不是MultipleSprite"); } if (!textureImporter.isReadable) { textureImporter.isReadable = true; textureImporter.SaveAndReimport(); } for (int k = 0; k < textureImporter.spritesheet.Length; k++) { SpriteMetaData metaData = textureImporter.spritesheet[k]; EditorUtility.DisplayProgressBar("切图", $"新建 {metaData.name}", (k + 1)/(float) textureImporter.spritesheet.Length); int width = (int)metaData.rect.width + 2 * parameter.Padding; int height = (int)metaData.rect.height + 2 * parameter.Padding; Texture2D texture2D = new Texture2D(width, height); Color[] originColors = parameter.Atlas.GetPixels((int)metaData.rect.x, (int)metaData.rect.y, (int)metaData.rect.width, (int)metaData.rect.height); Color[] newColors = new Color[width * height]; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { if (i < parameter.Padding || j < parameter.Padding || i >= height - parameter.Padding || j >= width - parameter.Padding) { newColors[i * width + j] = new Color(0, 0, 0, 0); } else { int x = i - parameter.Padding; int y = j - parameter.Padding; newColors[i * width + j] = originColors[x * (int)metaData.rect.width + y]; } } } texture2D.SetPixels(newColors); texture2D.Apply(); string spritePath = $"{parameter.Path}{metaData.name}.png".GetUnRepeatFileName(); File.WriteAllBytes(spritePath, texture2D.EncodeToPNG()); AssetDatabase.ImportAsset(spritePath); TextureImporter importer = (TextureImporter)AssetImporter.GetAtPath(spritePath); TextureImporterSettings importerSetting = new TextureImporterSettings(); importer.ReadTextureSettings(importerSetting); importerSetting.textureType = TextureImporterType.Sprite; importerSetting.spritePivot = metaData.pivot; importerSetting.spriteBorder = metaData.border; importerSetting.spriteAlignment = metaData.alignment; importerSetting.alphaIsTransparency = true; importer.SetTextureSettings(importerSetting); importer.isReadable = true; importer.name = metaData.name; importer.spriteImportMode = SpriteImportMode.Single; importer.maxTextureSize = ExMath.NextPOT(Mathf.Max(width, height)); importer.SaveAndReimport(); } EditorUtility.ClearProgressBar(); } protected Parameter CreateSliceParameter() { if (string.IsNullOrEmpty(Instance.SlicePath)) { throw new Exception("Path不能为空"); } string directory = Instance.SlicePath.TrimEnd('/', '\\') + "/"; if(string.IsNullOrEmpty(directory) || !Directory.Exists(directory)) { throw new Exception("文件夹不存在"); } if (Instance.SlicePadding < 0) { Instance.SlicePadding = 0; } if (Instance.SliceAtlas == null) { throw new Exception("Texture为空"); } Parameter parameter = new Parameter { Path = directory, Padding = Instance.SlicePadding, Atlas = Instance.SliceAtlas }; return parameter; } protected void ScaleTextures(Parameter parameter) { ScaleTextureBridge(Instance.EditorTexture, parameter); for (int i = 0; i < Instance.EditorTextures.Count; i++) { parameter.Path = parameter.Pathes[i]; ScaleTextureBridge(Instance.EditorTextures[i], parameter); } } protected void ScaleTextureBridge(Texture2D texture, Parameter parameter) { if (texture == null) { return; } ScaleTexture(parameter.Padding, parameter.ScaleFactor, parameter.Path, texture, parameter.ScalePlatform); } protected void ScaleTexture(int padding, float scaleFactor, string path, Texture2D editorTexture, ScalePlatform scalePlatform) { if (File.Exists(path)) { Debug.LogError($"{path} 存在同名文件"); return; } Texture2D texture = ScaleTextureCore(padding, scaleFactor, editorTexture); File.WriteAllBytes(path, texture.EncodeToPNG()); AssetDatabase.ImportAsset(path); TextureImporter sourceTextureImporter = (TextureImporter)AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(editorTexture)); TextureImporter textureImporter = (TextureImporter)AssetImporter.GetAtPath(path); TextureImporterSettings importerSetting = new TextureImporterSettings(); sourceTextureImporter.ReadTextureSettings(importerSetting); importerSetting.textureType = sourceTextureImporter.textureType; importerSetting.spritePivot = sourceTextureImporter.spritePivot; importerSetting.spriteBorder = sourceTextureImporter.spriteBorder*scaleFactor; importerSetting.alphaIsTransparency = sourceTextureImporter.alphaIsTransparency; importerSetting.spritePixelsPerUnit = sourceTextureImporter.spritePixelsPerUnit/100 * 100*scaleFactor; textureImporter.spritePackingTag = sourceTextureImporter.spritePackingTag; textureImporter.SetTextureSettings(importerSetting); textureImporter.SaveAndReimport(); List referenceSetList = PlatformReferenceTable.ReadAllLine(); string sourceGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(editorTexture)); string platformGUID = AssetDatabase.AssetPathToGUID(path); PlatformReferenceSet referenceSet = PlatformReferenceManager.GetMatchedReferenceSet(sourceGUID, referenceSetList); if (referenceSet == null) { referenceSet = new PlatformReferenceSet(editorTexture, platformGUID, scalePlatform); referenceSetList.Add(referenceSet); } else { referenceSet.SetPlatformReference(platformGUID, scalePlatform); } PlatformReferenceTable.WriteAllLine(referenceSetList); } protected Texture2D ScaleTextureCore(int padding, float factor, Texture2D sourceTexture) { if (Math.Abs(factor - 1) < 0.001) { return sourceTexture.Clone(); } Texture2D texture = AddPaddingToTexture(padding, sourceTexture); int originWidth = texture.width; int originHeight = texture.height; int newWidth = (int)(originWidth * factor); int newHeight = (int)(originHeight * factor); Texture2D newTexture = new Texture2D(newWidth, newHeight); Color[] newColors = new Color[newWidth * newHeight]; for (int i = 0; i < newHeight; i++) { for (int j = 0; j < newWidth; j++) { newColors[i * newWidth + j] = texture.GetPixelBilinear((j + 0.5f) / newWidth, (i + 0.5f) / newHeight); } } newTexture.SetPixels(newColors); return newTexture; } protected Texture2D AddPaddingToTexture(int padding, Texture2D texture) { if (padding == 0) { return texture; } int originWidth = texture.width; int originHeight = texture.height; int newWidth = originWidth + 2 * padding; int newHeight = originHeight + 2 * padding; Texture2D newTexture = new Texture2D(newWidth, newHeight); Color[] newColors = new Color[newWidth * newHeight]; Color[] originColors = texture.GetPixels(); for (int i = 0; i < originHeight; i++) { for (int j = 0; j < originWidth; j++) { newColors[(i + padding) * newWidth + (j + padding)] = originColors[i * originWidth + j]; } } newTexture.SetPixels(newColors); return newTexture; } protected Parameter CreateScaleParameter() { if (string.IsNullOrEmpty(Instance.ScalePath)) { throw new Exception("Path不能为空"); } string directory = Instance.ScalePath.TrimEnd('/', '\\') + "/"; if (!Directory.Exists(directory)) { throw new Exception("文件夹不存在"); } if (Instance.ScaleFactor <= 0) { throw new Exception("Factor必须大于0"); } if (Instance.ScalePadding < 0) { Instance.SlicePadding = 0; } List pathes = new List(); for (int i = 0; i < Instance.EditorTextures.Count; i++) { if (Instance.EditorTextures[i] == null) { Instance.EditorTextures.RemoveAt(i--); } else { TextureImporter importer1 = (TextureImporter)AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(Instance.EditorTextures[i])); if (!importer1.isReadable) { importer1.isReadable = true; importer1.SaveAndReimport(); } pathes.Add(directory + Instance.EditorTextures[i].name + Instance.ScaleName + ".png"); } } if (Instance.EditorTexture == null && Instance.EditorTextures.Count == 0) { throw new Exception("Editor Texture为空"); } TextureImporter importer2 = (TextureImporter) AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(Instance.EditorTexture)); if (importer2 != null && !importer2.isReadable) { importer2.isReadable = true; importer2.SaveAndReimport(); } Parameter parameter = new Parameter { Path = directory + Instance.ScaleName + ".png", Pathes = pathes, Padding = Instance.SlicePadding, ScaleFactor = Instance.ScaleFactor, ScalePlatform = Instance.ScalePlatform, EditorTexture = Instance.EditorTexture, EditorTextures = Instance.EditorTextures, }; return parameter; } protected void SwitchAtlasPlatform(Platform toPlatform) { for (int i = 0; i < Instance.PlatformSet.Count; i++) { SwitchAtlasPlatformCore(Instance.PlatformSet[i], toPlatform); } } protected void SwitchAtlasPlatformCore(PlatformSet platformSet, Platform toPlatform) { string editorPath = AssetDatabase.GetAssetPath(platformSet.EditorObject); string mobilePath = AssetDatabase.GetAssetPath(platformSet.MobileObject); string desktopPath = AssetDatabase.GetAssetPath(platformSet.DesktopObject); AssetBundleManager.SwitchAssetBundle(editorPath, mobilePath, desktopPath, toPlatform); } public static void GetSelectedPath(ref string value) { if (Selection.assetGUIDs.Length > 0) { string path = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); path = Path.GetDirectoryName(path); if (string.IsNullOrEmpty(path)) { value = "Assets"; } else { value = path; } } } public static void GetSelectedName(ref string value) { if (Selection.assetGUIDs.Length > 0) { string path = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); value = Path.GetFileNameWithoutExtension(path); } } protected void OnGUI() { SerializedObject.Update(); EditorGUILayout.LabelField("AtlasUtility", TitleGuiStyle, GUILayout.Height(30)); ScrollPosition = EditorGUILayout.BeginScrollView(ScrollPosition); DrawPackAtlasArea(); DrawSliceAtlasArea(); DrawSearchReferenceArea(); DrawReferenceControllArea(); DrawScaleTextureArea(); DrawDebugArea(); EditorGUILayout.EndScrollView(); SerializedObject.ApplyModifiedProperties(); } protected void DrawPackAtlasArea() { Instance.PackAtlasFunction = EditorGUILayout.Foldout(Instance.PackAtlasFunction, "Pack Atlas"); if (!Instance.PackAtlasFunction) { EditorGUILayout.Separator(); return; } EditorGUILayout.Separator(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(PackPath, new GUIContent("Path")); if (GUILayout.Button("UseSelectedPath", GUILayout.MaxWidth(120))) { GetSelectedPath(ref Instance.PackPath); } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(PackName, new GUIContent("Name")); if (GUILayout.Button("UseSelectedName", GUILayout.MaxWidth(120))) { GetSelectedName(ref Instance.PackName); } EditorGUILayout.EndHorizontal(); EditorGUILayout.PropertyField(packPlan, new GUIContent("Plan")); if (Instance.PackPlan == PackPlan.Fixed) { EditorGUILayout.PropertyField(PackSize, new GUIContent("Size")); } else if (Instance.PackPlan == PackPlan.Smallest) { EditorGUILayout.PropertyField(PackSize, new GUIContent("Max Size")); } EditorGUILayout.PropertyField(PackAtlas, new GUIContent("Atlas")); if (Instance.PackAtlas != null) { List childPathList = ReferenceManager.GetChildSpritePath(AssetDatabase.GetAssetPath(Instance.PackAtlas)); for (int i = 0; i < childPathList.Count; i++) { Instance.TextureList.Add(AssetDatabase.LoadAssetAtPath(childPathList[i])); } Instance.PackAtlas = null; } EditorGUILayout.PropertyField(PackPadding, new GUIContent("Padding")); EditorGUILayout.PropertyField(TextureList, true); if (GUILayout.Button("Pack", GUILayout.Height(30))) { Pack(); } EditorGUILayout.Separator(); } protected void DrawSliceAtlasArea() { EditorGUILayout.Separator(); Instance.SliceAtlasFunction = EditorGUILayout.Foldout(Instance.SliceAtlasFunction, "Slice Atlas"); if (!Instance.SliceAtlasFunction) { EditorGUILayout.Separator(); return; } EditorGUILayout.Separator(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(SlicePath, new GUIContent("Path")); if (GUILayout.Button("UseSelectedPath")) { GetSelectedPath(ref Instance.SlicePath); } EditorGUILayout.EndHorizontal(); EditorGUILayout.PropertyField(SliceAtlas, new GUIContent("Atlas")); EditorGUILayout.PropertyField(SlicePadding, new GUIContent("Padding")); if (GUILayout.Button("Slice", GUILayout.Height(30))) { Slice(); } EditorGUILayout.Separator(); } protected void DrawSearchReferenceArea() { EditorGUILayout.Separator(); Instance.SearchReferenceFunction = EditorGUILayout.Foldout(Instance.SearchReferenceFunction, "Search Reference"); if (!Instance.SearchReferenceFunction) { EditorGUILayout.Separator(); return; } EditorGUILayout.Separator(); EditorGUILayout.PropertyField(SearchTarget); if (GUILayout.Button("SearchReference", GUILayout.Height(30))) { ReferenceManager.FindReference(AssetDatabase.GetAssetPath(Instance.SearchTarget)); } EditorGUILayout.Separator(); } protected void DrawReferenceControllArea() { EditorGUILayout.Separator(); Instance.PlatformControllFunction = EditorGUILayout.Foldout(Instance.PlatformControllFunction, "Platform & Reference Controll"); if (!Instance.PlatformControllFunction) { EditorGUILayout.Separator(); return; } EditorGUILayout.Separator(); EditorGUILayout.PropertyField(PlatformSet, true); EditorGUILayout.Separator(); if (GUILayout.Button("Enable Atlas", GUILayout.Height(30))) { if (EditorUtility.DisplayDialog("注意", "用图集替代Sprite?", "继续", "取消")) { AtlasReferenceManager.EnableAtlas(); } } EditorGUILayout.Separator(); EditorGUILayout.Separator(); if (GUILayout.Button("Disable Atlas", GUILayout.Height(30))) { if (EditorUtility.DisplayDialog("注意", "还原图集到Sprite?", "继续", "取消")) { AtlasReferenceManager.DisableAtlas(); } } EditorGUILayout.Separator(); EditorGUILayout.Separator(); if (GUILayout.Button("Switch To Editor Platform", GUILayout.Height(30))) { if (EditorUtility.DisplayDialog("注意", "切换平台到Editor?(切换平台会Enable Atlas)", "继续", "取消")) { AssetDatabase.Refresh(); List fromList = new List(); List toList = new List(); AtlasReferenceManager.GetDisableFromToList(fromList, toList); PlatformReferenceManager.GetFromToList(Platform.Editor, fromList, toList); AtlasReferenceManager.GetEnableFromToList(fromList, toList); ReferenceManager.ChangeReference(true, fromList, toList); SwitchAtlasPlatform(Platform.Editor); } } EditorGUILayout.Separator(); EditorGUILayout.Separator(); if (GUILayout.Button("Switch To Mobile Platform", GUILayout.Height(30))) { if (EditorUtility.DisplayDialog("注意", "切换平台到Mobile?(切换平台会Enable Atlas)", "继续", "取消")) { AssetDatabase.Refresh(); List fromList = new List(); List toList = new List(); AtlasReferenceManager.GetDisableFromToList(fromList, toList); PlatformReferenceManager.GetFromToList(Platform.Mobile, fromList, toList); AtlasReferenceManager.GetEnableFromToList(fromList, toList); ReferenceManager.ChangeReference(true, fromList, toList); SwitchAtlasPlatform(Platform.Mobile); } } EditorGUILayout.Separator(); EditorGUILayout.Separator(); if (GUILayout.Button("Switch To Desktop Platform", GUILayout.Height(30))) { if (EditorUtility.DisplayDialog("注意", "切换平台到Desktop?(切换平台会Enable Atlas)", "继续", "取消")) { AssetDatabase.Refresh(); List fromList = new List(); List toList = new List(); AtlasReferenceManager.GetDisableFromToList(fromList, toList); PlatformReferenceManager.GetFromToList(Platform.Desktop, fromList, toList); AtlasReferenceManager.GetEnableFromToList(fromList, toList); ReferenceManager.ChangeReference(true, fromList, toList); SwitchAtlasPlatform(Platform.Desktop); } } EditorGUILayout.Separator(); } protected void DrawScaleTextureArea() { EditorGUILayout.Separator(); Instance.ScaleTextureFunction = EditorGUILayout.Foldout(Instance.ScaleTextureFunction, "Scale Texture"); if (!Instance.ScaleTextureFunction) { EditorGUILayout.Separator(); return; } EditorGUILayout.Separator(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(ScalePath, new GUIContent("Path")); if (GUILayout.Button("UseSelectedPath", GUILayout.MaxWidth(120))) { GetSelectedPath(ref Instance.ScalePath); } EditorGUILayout.EndHorizontal(); EditorGUILayout.PropertyField(ScaleName, new GUIContent("Name & Suffix")); EditorGUILayout.PropertyField(ScaleFactor, new GUIContent("Factor")); EditorGUILayout.PropertyField(ScalePadding, new GUIContent("Padding")); EditorGUILayout.PropertyField(EditorTexture, new GUIContent("Editor Texture")); EditorGUILayout.PropertyField(EditorTextures, new GUIContent("Editor Textures"), true); if (Instance.EditorTexture != Instance.LastEdiotTexture) { if (Instance.EditorTexture != null) { Instance.ScaleName = Instance.EditorTexture.name; } PlatformReferenceSet referenceSet = PlatformReferenceManager.GetMatchedReferenceSet(Instance.EditorTexture); if (referenceSet != null) { Instance.EditorTexture = referenceSet.EditorTexture; Instance.MobileTexture = referenceSet.MobileTexture; Instance.DesktopTexture = referenceSet.DesktopTexture; } else { Instance.MobileTexture = null; Instance.DesktopTexture = null; } } Instance.LastEdiotTexture = Instance.EditorTexture; if (Instance.MobileTexture != null) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(MobileTexture, new GUIContent("Mobile Texture")); EditorGUILayout.EndHorizontal(); } if (Instance.DesktopTexture != null) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(DesktopTexture, new GUIContent("Desktop Texture")); EditorGUILayout.EndHorizontal(); } EditorGUILayout.PropertyField(ScalePlatform, new GUIContent("Target Platform")); if (GUILayout.Button("Scale", GUILayout.Height(30))) { Parameter parameter = CreateScaleParameter(); ScaleTextures(parameter); } EditorGUILayout.Separator(); } protected void DrawDebugArea() { EditorGUILayout.Separator(); Instance.DebugFunction = EditorGUILayout.Foldout(Instance.DebugFunction, "Debug"); if (!Instance.DebugFunction) { return; } if (GUILayout.Button("CollectDebugInfo", GUILayout.Height(30))) { CollectDebugInfo(); } } } }