Art_SubModule/Art_Scripts/Editor/AtlasBuilderEditor.cs
2026-02-10 17:41:18 +08:00

1052 lines
37 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using UnityEditor;
using UnityEditor.U2D;
using UnityEngine.U2D;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using ArtResource;
using Thrift;
using Thrift.Protocol;
using Thrift.Transport;
using Thrift.Transport.Client;
namespace ArtTools
{
/// <summary>
/// 图集构建工具
/// </summary>
public class AtlasBuilderEditor : EditorWindow
{
private const string ATLAS_ROOT_PATH = "Assets/Art_SubModule/Art_Atlas";
private const string SPRITE_ROOT_PATH = "Assets/Art_SubModule";
private const string CONFIG_PATH = "Assets/Art_SubModule/Art_Atlas/AtlasConfig.json";
private const string BYTES_PATH = "Assets/Art_SubModule/Art_Bytes/ArtAtlasConfig.bytes";
private enum MenuState
{
Normal,
Add,
Rename,
Remove
}
// 数据
private AtlasConfig config;
private AtlasData selectedAtlas;
private SpriteFolder spriteRoot;
// UI状态
private MenuState menuState = MenuState.Normal;
private Vector2 atlasListScroll = Vector2.zero;
private Vector2 atlasContentScroll = Vector2.zero;
private Vector2 spriteListScroll = Vector2.zero;
// 选择状态
private HashSet<string> selectedSpritesInAtlas = new HashSet<string>();
private HashSet<SpriteFolder> expandedSpriteFolders = new HashSet<SpriteFolder>();
private HashSet<SpriteAsset> selectedSpriteAssets = new HashSet<SpriteAsset>();
private HashSet<SpriteFolder> selectedSpriteFolders = new HashSet<SpriteFolder>();
// 缓存
private HashSet<SpriteAsset> cachedAssignedSprites = new HashSet<SpriteAsset>();
private HashSet<SpriteFolder> cachedAssignedFolders = new HashSet<SpriteFolder>();
// 输入
private string inputAtlasName = "";
private bool hideAssignedSprites = false;
// 绘制计数
private int currentAtlasRowOnDraw = 0;
private int currentSpriteRowOnDraw = 0;
[MenuItem("程序工具/图集构建工具")]
public static void ShowWindow()
{
var window = GetWindow<AtlasBuilderEditor>("图集构建工具");
window.minSize = new Vector2(1400, 600);
window.Show();
}
private void OnEnable()
{
LoadConfig();
RefreshSpriteTree();
}
private void OnGUI()
{
EditorGUILayout.BeginHorizontal(GUILayout.Width(position.width), GUILayout.Height(position.height));
{
GUILayout.Space(2f);
// 左侧:图集列表
EditorGUILayout.BeginVertical(GUILayout.Width(position.width * 0.25f));
{
GUILayout.Space(5f);
EditorGUILayout.LabelField($"图集列表 ({config.Atlases.Count})", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal("box", GUILayout.Height(position.height - 52f));
{
DrawAtlasList();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
{
GUILayout.Space(5f);
DrawAtlasListMenu();
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
// 中间:图集内容
EditorGUILayout.BeginVertical(GUILayout.Width(position.width * 0.25f));
{
GUILayout.Space(5f);
EditorGUILayout.LabelField($"图集内容 ({(selectedAtlas != null ? selectedAtlas.SpritePaths.Count : 0)})", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal("box", GUILayout.Height(position.height - 52f));
{
DrawAtlasContent();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
{
GUILayout.Space(5f);
DrawAtlasContentMenu();
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
// 右侧Sprite列表
EditorGUILayout.BeginVertical(GUILayout.Width(position.width * 0.5f - 16f));
{
GUILayout.Space(5f);
EditorGUILayout.LabelField("Sprite列表", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal("box", GUILayout.Height(position.height - 52f));
{
DrawSpriteList();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
{
GUILayout.Space(5f);
DrawSpriteListMenu();
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
GUILayout.Space(5f);
}
EditorGUILayout.EndHorizontal();
}
// ==================== 绘制方法 ====================
private void DrawAtlasList()
{
currentAtlasRowOnDraw = 0;
atlasListScroll = EditorGUILayout.BeginScrollView(atlasListScroll);
{
if (config != null && config.Atlases != null)
{
foreach (var atlas in config.Atlases)
{
DrawAtlasItem(atlas);
}
}
}
EditorGUILayout.EndScrollView();
}
private void DrawAtlasItem(AtlasData atlas)
{
EditorGUILayout.BeginHorizontal();
{
bool isSelected = selectedAtlas == atlas;
float emptySpace = position.width * 0.25f;
if (EditorGUILayout.Toggle(isSelected, GUILayout.Width(emptySpace - 12f)))
{
ChangeSelectedAtlas(atlas);
}
else if (isSelected)
{
ChangeSelectedAtlas(null);
}
GUILayout.Space(-emptySpace + 24f);
Texture2D icon = EditorGUIUtility.FindTexture("SpriteAtlas Icon");
GUI.DrawTexture(new Rect(32f, 20f * currentAtlasRowOnDraw + 3f, 16f, 16f), icon);
GUILayout.Space(20f);
EditorGUILayout.LabelField($"{atlas.Name} ({atlas.SpritePaths.Count})");
}
EditorGUILayout.EndHorizontal();
currentAtlasRowOnDraw++;
}
private void DrawAtlasListMenu()
{
switch (menuState)
{
case MenuState.Normal:
DrawAtlasListMenu_Normal();
break;
case MenuState.Add:
DrawAtlasListMenu_Add();
break;
case MenuState.Rename:
DrawAtlasListMenu_Rename();
break;
case MenuState.Remove:
DrawAtlasListMenu_Remove();
break;
}
}
private void DrawAtlasListMenu_Normal()
{
if (GUILayout.Button("添加", GUILayout.Width(65f)))
{
menuState = MenuState.Add;
inputAtlasName = "";
GUI.FocusControl(null);
}
EditorGUI.BeginDisabledGroup(selectedAtlas == null);
{
if (GUILayout.Button("重命名", GUILayout.Width(65f)))
{
menuState = MenuState.Rename;
inputAtlasName = selectedAtlas != null ? selectedAtlas.Name : "";
GUI.FocusControl(null);
}
if (GUILayout.Button("移除", GUILayout.Width(65f)))
{
menuState = MenuState.Remove;
}
}
EditorGUI.EndDisabledGroup();
}
private void DrawAtlasListMenu_Add()
{
GUI.SetNextControlName("NewAtlasNameTextField");
inputAtlasName = EditorGUILayout.TextField(inputAtlasName);
if (GUI.GetNameOfFocusedControl() == "NewAtlasNameTextField")
{
if (Event.current.isKey && Event.current.keyCode == KeyCode.Return)
{
AddAtlas(inputAtlasName);
Repaint();
}
}
if (GUILayout.Button("添加", GUILayout.Width(50f)))
{
AddAtlas(inputAtlasName);
}
if (GUILayout.Button("取消", GUILayout.Width(50f)))
{
menuState = MenuState.Normal;
}
}
private void DrawAtlasListMenu_Rename()
{
if (selectedAtlas == null)
{
menuState = MenuState.Normal;
return;
}
GUI.SetNextControlName("RenameAtlasNameTextField");
inputAtlasName = EditorGUILayout.TextField(inputAtlasName);
if (GUI.GetNameOfFocusedControl() == "RenameAtlasNameTextField")
{
if (Event.current.isKey && Event.current.keyCode == KeyCode.Return)
{
RenameAtlas(selectedAtlas, inputAtlasName);
Repaint();
}
}
if (GUILayout.Button("确定", GUILayout.Width(50f)))
{
RenameAtlas(selectedAtlas, inputAtlasName);
}
if (GUILayout.Button("取消", GUILayout.Width(50f)))
{
menuState = MenuState.Normal;
}
}
private void DrawAtlasListMenu_Remove()
{
if (selectedAtlas == null)
{
menuState = MenuState.Normal;
return;
}
GUILayout.Label($"移除图集 '{selectedAtlas.Name}' ?");
if (GUILayout.Button("确定", GUILayout.Width(50f)))
{
RemoveAtlas();
menuState = MenuState.Normal;
}
if (GUILayout.Button("取消", GUILayout.Width(50f)))
{
menuState = MenuState.Normal;
}
}
private void DrawAtlasContent()
{
atlasContentScroll = EditorGUILayout.BeginScrollView(atlasContentScroll);
{
if (selectedAtlas != null)
{
foreach (string spritePath in selectedAtlas.SpritePaths)
{
EditorGUILayout.BeginHorizontal();
{
bool select = selectedSpritesInAtlas.Contains(spritePath);
float emptySpace = position.width * 0.25f;
if (select != EditorGUILayout.Toggle(select, GUILayout.Width(emptySpace - 12f)))
{
if (select)
selectedSpritesInAtlas.Remove(spritePath);
else
selectedSpritesInAtlas.Add(spritePath);
}
GUILayout.Space(-emptySpace + 24f);
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(spritePath);
// 正确处理图集sprite的预览
Rect iconRect = new Rect(32f, 20f * (selectedAtlas.SpritePaths.IndexOf(spritePath)) + 3f, 16f, 16f);
if (sprite != null && sprite.texture != null)
{
Rect texCoords = sprite.textureRect;
Texture2D tex = sprite.texture;
Rect normalizedCoords = new Rect(
texCoords.x / tex.width,
texCoords.y / tex.height,
texCoords.width / tex.width,
texCoords.height / tex.height
);
GUI.DrawTextureWithTexCoords(iconRect, tex, normalizedCoords, true);
}
else
{
Texture2D icon = EditorGUIUtility.FindTexture("Sprite Icon");
GUI.DrawTexture(iconRect, icon);
}
EditorGUILayout.LabelField(string.Empty, GUILayout.Width(26f), GUILayout.Height(18f));
EditorGUILayout.LabelField(sprite != null ? sprite.name : Path.GetFileName(spritePath));
}
EditorGUILayout.EndHorizontal();
}
}
}
EditorGUILayout.EndScrollView();
}
private void DrawAtlasContentMenu()
{
if (GUILayout.Button("全选", GUILayout.Width(50f)) && selectedAtlas != null)
{
selectedSpritesInAtlas.Clear();
foreach (string path in selectedAtlas.SpritePaths)
{
selectedSpritesInAtlas.Add(path);
}
}
if (GUILayout.Button("全不选", GUILayout.Width(60f)))
{
selectedSpritesInAtlas.Clear();
}
GUILayout.Label(string.Empty);
EditorGUI.BeginDisabledGroup(selectedAtlas == null || selectedSpritesInAtlas.Count <= 0);
{
if (GUILayout.Button($"{selectedSpritesInAtlas.Count} >>", GUILayout.Width(80f)))
{
foreach (string path in selectedSpritesInAtlas.ToList())
{
selectedAtlas.SpritePaths.Remove(path);
}
selectedSpritesInAtlas.Clear();
RefreshAssignedCache();
}
}
EditorGUI.EndDisabledGroup();
}
private void DrawSpriteList()
{
currentSpriteRowOnDraw = 0;
spriteListScroll = EditorGUILayout.BeginScrollView(spriteListScroll);
{
if (spriteRoot != null)
{
DrawSpriteFolder(spriteRoot);
}
}
EditorGUILayout.EndScrollView();
}
private void DrawSpriteFolder(SpriteFolder folder)
{
// 跳过没有可见Sprite的文件夹HasVisibleSprites已经考虑了hideAssignedSprites
if (!HasVisibleSprites(folder))
{
return;
}
EditorGUILayout.BeginHorizontal();
{
bool select = IsSelectedFolder(folder);
if (select != EditorGUILayout.Toggle(select, GUILayout.Width(12f + 14f * folder.Depth)))
{
SetSelectedFolder(folder, !select);
}
GUILayout.Space(-14f * folder.Depth);
bool expand = expandedSpriteFolders.Contains(folder);
bool foldout = EditorGUI.Foldout(new Rect(18f + 14f * folder.Depth, 20f * currentSpriteRowOnDraw + 4f, int.MaxValue, 14f), expand, string.Empty, true);
if (expand != foldout)
{
if (foldout)
expandedSpriteFolders.Add(folder);
else
expandedSpriteFolders.Remove(folder);
}
Texture2D icon = EditorGUIUtility.FindTexture("Folder Icon");
GUI.DrawTexture(new Rect(32f + 14f * folder.Depth, 20f * currentSpriteRowOnDraw + 3f, 16f, 16f), icon);
EditorGUILayout.LabelField(string.Empty, GUILayout.Width(30f + 14f * folder.Depth), GUILayout.Height(18f));
EditorGUILayout.LabelField(folder.Name);
}
EditorGUILayout.EndHorizontal();
currentSpriteRowOnDraw++;
if (expandedSpriteFolders.Contains(folder))
{
foreach (var subFolder in folder.SubFolders)
{
DrawSpriteFolder(subFolder);
}
foreach (var sprite in folder.Sprites)
{
DrawSpriteAsset(sprite);
}
}
}
private void DrawSpriteAsset(SpriteAsset spriteAsset)
{
if (hideAssignedSprites && IsAssignedSprite(spriteAsset))
{
return;
}
EditorGUILayout.BeginHorizontal();
{
bool select = selectedSpriteAssets.Contains(spriteAsset);
float emptySpace = position.width * 0.5f - 16f;
if (select != EditorGUILayout.Toggle(select, GUILayout.Width(emptySpace - 12f)))
{
SetSelectedSprite(spriteAsset, !select);
}
GUILayout.Space(-emptySpace + 24f);
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(spriteAsset.Path);
// 正确处理图集sprite的预览
Rect iconRect = new Rect(32f + 14f * spriteAsset.Depth, 20f * currentSpriteRowOnDraw + 3f, 16f, 16f);
if (sprite != null && sprite.texture != null)
{
Rect texCoords = sprite.textureRect;
Texture2D tex = sprite.texture;
Rect normalizedCoords = new Rect(
texCoords.x / tex.width,
texCoords.y / tex.height,
texCoords.width / tex.width,
texCoords.height / tex.height
);
GUI.DrawTextureWithTexCoords(iconRect, tex, normalizedCoords, true);
}
else
{
Texture2D icon = EditorGUIUtility.FindTexture("Sprite Icon");
GUI.DrawTexture(iconRect, icon);
}
EditorGUILayout.LabelField(string.Empty, GUILayout.Width(26f + 14f * spriteAsset.Depth), GUILayout.Height(18f));
EditorGUILayout.LabelField(spriteAsset.Name);
}
EditorGUILayout.EndHorizontal();
currentSpriteRowOnDraw++;
}
private void DrawSpriteListMenu()
{
HashSet<SpriteAsset> selectedSprites = GetSelectedSprites();
EditorGUI.BeginDisabledGroup(selectedAtlas == null || selectedSprites.Count <= 0);
{
if (GUILayout.Button($"<< {selectedSprites.Count}", GUILayout.Width(80f)))
{
foreach (var sprite in selectedSprites)
{
if (!selectedAtlas.SpritePaths.Contains(sprite.Path))
{
selectedAtlas.SpritePaths.Add(sprite.Path);
}
}
selectedSpriteAssets.Clear();
selectedSpriteFolders.Clear();
RefreshAssignedCache();
}
}
EditorGUI.EndDisabledGroup();
bool newHideState = EditorGUILayout.ToggleLeft("隐藏在图集中的sprite", hideAssignedSprites, GUILayout.Width(180f));
if (newHideState != hideAssignedSprites)
{
hideAssignedSprites = newHideState;
RefreshAssignedCache();
}
GUILayout.Label(string.Empty);
if (GUILayout.Button("清除丢失引用", GUILayout.Width(100f)))
{
CleanMissingReferences();
}
if (GUILayout.Button("保存", GUILayout.Width(80f)))
{
SaveAndBuildAllAtlases();
}
}
// ==================== 逻辑方法 ====================
private void ChangeSelectedAtlas(AtlasData atlas)
{
if (selectedAtlas == atlas) return;
selectedAtlas = atlas;
selectedSpritesInAtlas.Clear();
RefreshAssignedCache();
}
private void AddAtlas(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
EditorUtility.DisplayDialog("错误", "图集名称不能为空", "确定");
return;
}
if (config.Atlases.Any(a => a.Name == name))
{
EditorUtility.DisplayDialog("错误", $"图集 '{name}' 已存在", "确定");
return;
}
config.Atlases.Add(new AtlasData
{
Name = name,
SpritePaths = new List<string>()
});
menuState = MenuState.Normal;
selectedAtlas = config.Atlases[config.Atlases.Count - 1];
}
private void RenameAtlas(AtlasData atlas, string newName)
{
if (string.IsNullOrWhiteSpace(newName))
{
EditorUtility.DisplayDialog("错误", "图集名称不能为空", "确定");
return;
}
if (config.Atlases.Any(a => a != atlas && a.Name == newName))
{
EditorUtility.DisplayDialog("错误", $"图集 '{newName}' 已存在", "确定");
return;
}
string oldPath = $"{ATLAS_ROOT_PATH}/{atlas.Name}.spriteatlas";
string newPath = $"{ATLAS_ROOT_PATH}/{newName}.spriteatlas";
if (File.Exists(oldPath))
{
AssetDatabase.MoveAsset(oldPath, newPath);
}
atlas.Name = newName;
menuState = MenuState.Normal;
}
private void RemoveAtlas()
{
if (selectedAtlas == null) return;
string atlasPath = $"{ATLAS_ROOT_PATH}/{selectedAtlas.Name}.spriteatlas";
if (File.Exists(atlasPath))
{
AssetDatabase.DeleteAsset(atlasPath);
}
config.Atlases.Remove(selectedAtlas);
selectedAtlas = null;
selectedSpritesInAtlas.Clear();
}
private void CleanMissingReferences()
{
int cleanedCount = 0;
foreach (var atlas in config.Atlases)
{
int originalCount = atlas.SpritePaths.Count;
atlas.SpritePaths.RemoveAll(path => !File.Exists(path));
cleanedCount += originalCount - atlas.SpritePaths.Count;
}
if (cleanedCount > 0)
{
RefreshAssignedCache();
EditorUtility.DisplayDialog("提示", $"已清除 {cleanedCount} 个丢失的引用", "确定");
}
else
{
EditorUtility.DisplayDialog("提示", "没有发现丢失的引用", "确定");
}
}
private void SaveAndBuildAllAtlases()
{
SaveConfig();
EditorUtility.DisplayProgressBar("构建图集", "开始构建...", 0f);
int count = config.Atlases.Count;
for (int i = 0; i < count; i++)
{
var atlas = config.Atlases[i];
EditorUtility.DisplayProgressBar("构建图集", $"构建 {atlas.Name}...", (float)i / count);
BuildAtlas(atlas);
}
AssetDatabase.Refresh();
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("提示", $"已构建 {count} 个图集并保存配置", "确定");
}
private void BuildAtlas(AtlasData atlasData)
{
if (!Directory.Exists(ATLAS_ROOT_PATH))
{
Directory.CreateDirectory(ATLAS_ROOT_PATH);
}
string atlasPath = $"{ATLAS_ROOT_PATH}/{atlasData.Name}.spriteatlas";
SpriteAtlas spriteAtlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(atlasPath);
bool isNewAtlas = spriteAtlas == null;
if (isNewAtlas)
{
spriteAtlas = new SpriteAtlas();
AssetDatabase.CreateAsset(spriteAtlas, atlasPath);
// 只对新建的图集设置默认参数
SpriteAtlasPackingSettings packingSettings = new SpriteAtlasPackingSettings()
{
blockOffset = 1,
enableRotation = false,
enableTightPacking = false,
padding = 2
};
spriteAtlas.SetPackingSettings(packingSettings);
SpriteAtlasTextureSettings textureSettings = new SpriteAtlasTextureSettings()
{
readable = false,
generateMipMaps = false,
sRGB = true,
filterMode = FilterMode.Bilinear
};
spriteAtlas.SetTextureSettings(textureSettings);
TextureImporterPlatformSettings platformSettings = new TextureImporterPlatformSettings()
{
maxTextureSize = 2048,
format = TextureImporterFormat.Automatic,
textureCompression = TextureImporterCompression.Compressed
};
spriteAtlas.SetPlatformSettings(platformSettings);
}
// 更新 Objects for Packing对所有图集都执行
spriteAtlas.Remove(spriteAtlas.GetPackables());
var validSprites = atlasData.SpritePaths
.Select(path => AssetDatabase.LoadAssetAtPath<Sprite>(path))
.Where(sprite => sprite != null)
.Cast<Object>()
.ToArray();
spriteAtlas.Add(validSprites);
EditorUtility.SetDirty(spriteAtlas);
AssetDatabase.SaveAssets();
SpriteAtlasUtility.PackAtlases(new[] { spriteAtlas }, EditorUserBuildSettings.activeBuildTarget);
}
private void LoadConfig()
{
if (File.Exists(CONFIG_PATH))
{
string json = File.ReadAllText(CONFIG_PATH);
config = JsonUtility.FromJson<AtlasConfig>(json);
}
else
{
config = new AtlasConfig { Atlases = new List<AtlasData>() };
SaveConfig();
}
}
private void SaveConfig()
{
// 保存JSON配置
string dir = Path.GetDirectoryName(CONFIG_PATH);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
string json = JsonUtility.ToJson(config, true);
File.WriteAllText(CONFIG_PATH, json);
// 生成Thrift Bytes配置
SaveConfigToThriftBytes();
AssetDatabase.Refresh();
}
/// <summary>
/// 保存配置到Thrift Bytes格式
/// </summary>
private void SaveConfigToThriftBytes()
{
try
{
var startTime = System.Diagnostics.Stopwatch.StartNew();
// 确保输出目录存在
string dir = Path.GetDirectoryName(BYTES_PATH);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
// 创建Thrift数据结构
var thriftConfig = new Byway.Thrift.Data.ArtAtlasConfig
{
Atlases = new List<Byway.Thrift.Data.ArtAtlasInfo>(),
Version = System.DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
GenerateTime = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
};
// 转换所有Atlas
foreach (var atlas in config.Atlases)
{
var thriftAtlas = new Byway.Thrift.Data.ArtAtlasInfo
{
Name = atlas.Name,
SpritePaths = new List<string>(atlas.SpritePaths)
};
thriftConfig.Atlases.Add(thriftAtlas);
}
// 序列化到bytes
byte[] bytesData;
using (var memoryStream = new MemoryStream())
{
using (var transport = new TStreamTransport(null, memoryStream, new TConfiguration()))
{
using (var protocol = new TBinaryProtocol(transport))
{
thriftConfig.WriteAsync(protocol, System.Threading.CancellationToken.None).GetAwaiter().GetResult();
}
}
bytesData = memoryStream.ToArray();
}
// 写入文件
File.WriteAllBytes(BYTES_PATH, bytesData);
startTime.Stop();
Debug.Log($"[AtlasBuilderEditor] ✅ Atlas Bytes生成成功\n" +
$" 路径: {BYTES_PATH}\n" +
$" 图集数量: {thriftConfig.Atlases.Count}\n" +
$" 文件大小: {bytesData.Length / 1024f:F2} KB\n" +
$" 耗时: {startTime.ElapsedMilliseconds} ms");
}
catch (System.Exception ex)
{
Debug.LogError($"[AtlasBuilderEditor] ❌ 生成Atlas Bytes失败: {ex.Message}\n{ex.StackTrace}");
}
}
private void RefreshSpriteTree()
{
spriteRoot = new SpriteFolder("Art_SubModule", null, 0);
BuildSpriteTree(SPRITE_ROOT_PATH, spriteRoot);
RefreshAssignedCache();
}
private void BuildSpriteTree(string path, SpriteFolder parent)
{
if (!Directory.Exists(path)) return;
// 添加子文件夹
foreach (string dir in Directory.GetDirectories(path))
{
string folderName = Path.GetFileName(dir);
var subFolder = new SpriteFolder(folderName, parent, parent.Depth + 1);
parent.SubFolders.Add(subFolder);
BuildSpriteTree(dir, subFolder);
}
// 添加Sprite文件
var guids = AssetDatabase.FindAssets("t:Sprite", new[] { path });
var spritePaths = guids.Select(guid => AssetDatabase.GUIDToAssetPath(guid))
.Where(p => Path.GetDirectoryName(p).Replace("\\", "/") == path.Replace("\\", "/"))
.OrderBy(p => p);
foreach (string spritePath in spritePaths)
{
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(spritePath);
if (sprite != null)
{
parent.Sprites.Add(new SpriteAsset
{
Name = sprite.name,
Path = spritePath,
Depth = parent.Depth + 1,
Parent = parent
});
}
}
}
private void RefreshAssignedCache()
{
cachedAssignedSprites.Clear();
cachedAssignedFolders.Clear();
HashSet<string> allAssignedPaths = new HashSet<string>();
foreach (var atlas in config.Atlases)
{
foreach (string path in atlas.SpritePaths)
{
allAssignedPaths.Add(path);
}
}
MarkAssignedRecursive(spriteRoot, allAssignedPaths);
}
private bool MarkAssignedRecursive(SpriteFolder folder, HashSet<string> assignedPaths)
{
bool hasAssigned = false;
foreach (var subFolder in folder.SubFolders)
{
if (MarkAssignedRecursive(subFolder, assignedPaths))
{
hasAssigned = true;
}
}
foreach (var sprite in folder.Sprites)
{
if (assignedPaths.Contains(sprite.Path))
{
cachedAssignedSprites.Add(sprite);
hasAssigned = true;
}
}
if (hasAssigned)
{
cachedAssignedFolders.Add(folder);
}
return hasAssigned;
}
// ==================== 辅助方法 ====================
private bool IsSelectedFolder(SpriteFolder folder)
{
foreach (var subFolder in folder.SubFolders)
{
if (!IsSelectedFolder(subFolder))
return false;
}
foreach (var sprite in folder.Sprites)
{
if (!selectedSpriteAssets.Contains(sprite))
return false;
}
return folder.SubFolders.Count > 0 || folder.Sprites.Count > 0;
}
private void SetSelectedFolder(SpriteFolder folder, bool select)
{
if (select)
{
selectedSpriteFolders.Add(folder);
foreach (var sprite in folder.Sprites)
{
selectedSpriteAssets.Add(sprite);
}
}
else
{
selectedSpriteFolders.Remove(folder);
foreach (var sprite in folder.Sprites)
{
selectedSpriteAssets.Remove(sprite);
}
}
foreach (var subFolder in folder.SubFolders)
{
SetSelectedFolder(subFolder, select);
}
}
private void SetSelectedSprite(SpriteAsset sprite, bool select)
{
if (select)
{
selectedSpriteAssets.Add(sprite);
}
else
{
selectedSpriteAssets.Remove(sprite);
}
}
private bool IsAssignedSprite(SpriteAsset sprite)
{
return cachedAssignedSprites.Contains(sprite);
}
private bool IsAssignedFolder(SpriteFolder folder)
{
return cachedAssignedFolders.Contains(folder);
}
private HashSet<SpriteAsset> GetSelectedSprites()
{
if (!hideAssignedSprites)
{
return selectedSpriteAssets;
}
HashSet<SpriteAsset> result = new HashSet<SpriteAsset>();
foreach (var sprite in selectedSpriteAssets)
{
if (!IsAssignedSprite(sprite))
{
result.Add(sprite);
}
}
return result;
}
private bool HasVisibleSprites(SpriteFolder folder)
{
// 检查当前文件夹的Sprite
foreach (var sprite in folder.Sprites)
{
if (!hideAssignedSprites || !IsAssignedSprite(sprite))
{
return true;
}
}
// 递归检查子文件夹
foreach (var subFolder in folder.SubFolders)
{
if (HasVisibleSprites(subFolder))
return true;
}
return false;
}
}
// ==================== 数据类 ====================
public class SpriteFolder
{
public string Name;
public SpriteFolder Parent;
public int Depth;
public List<SpriteFolder> SubFolders = new List<SpriteFolder>();
public List<SpriteAsset> Sprites = new List<SpriteAsset>();
public SpriteFolder(string name, SpriteFolder parent, int depth)
{
Name = name;
Parent = parent;
Depth = depth;
}
}
public class SpriteAsset
{
public string Name;
public string Path;
public int Depth;
public SpriteFolder Parent;
}
}