This commit is contained in:
2026-05-22 19:30:02 +08:00
parent 9b5b309ccf
commit 8d8594e8ae
1274 changed files with 69799 additions and 850 deletions
@@ -0,0 +1,154 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
public class AtlasAutoSplitter : EditorWindow
{
Texture2D sourceTexture;
Color32[] pixels;
int width, height;
bool[,] visited;
[MenuItem("Tools/Atlas Auto Splitter")]
static void Open()
{
GetWindow<AtlasAutoSplitter>("Atlas Splitter");
}
void OnGUI()
{
sourceTexture = (Texture2D)EditorGUILayout.ObjectField("图集 Texture2D", sourceTexture, typeof(Texture2D), false);
if (sourceTexture == null)
return;
if (GUILayout.Button("开始自动分割"))
{
SplitAtlas();
}
}
void SplitAtlas()
{
string path = AssetDatabase.GetAssetPath(sourceTexture);
TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer == null)
{
Debug.LogError("无法获取 TextureImporter");
return;
}
// 强制读取像素
importer.isReadable = true;
// 如果不是 Multiple,自动改
if (importer.spriteImportMode != SpriteImportMode.Multiple)
{
importer.spriteImportMode = SpriteImportMode.Multiple;
}
// 应用修改并重新加载
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
sourceTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
width = sourceTexture.width;
height = sourceTexture.height;
pixels = sourceTexture.GetPixels32();
visited = new bool[width, height];
List<SpriteMetaData> metas = new List<SpriteMetaData>();
for (int y = height - 1; y >= 0; y--)
{
for (int x = 0; x < width; x++)
{
if (visited[x, y])
continue;
if (!IsSolidPixel(x, y))
{
visited[x, y] = true;
continue;
}
// 找到一个区域,开始 FloodFill
Rect rect = FloodFill(x, y);
SpriteMetaData meta = new SpriteMetaData();
meta.name = $"sprite_{metas.Count}";
// 修正坐标系:Unity 的 rect 原点在左下角
meta.rect = new Rect(rect.x, height - rect.y - rect.height, rect.width, rect.height);
meta.pivot = new Vector2(0.5f, 0.5f);
meta.alignment = (int)SpriteAlignment.Center; // 必须设置
metas.Add(meta);
}
}
importer.spritesheet = metas.ToArray();
// 确保写入并刷新
EditorUtility.SetDirty(importer);
AssetDatabase.WriteImportSettingsIfDirty(path);
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
AssetDatabase.Refresh();
Debug.Log($"分割完成,共生成 {metas.Count} 个 Sprite");
}
bool IsSolidPixel(int x, int y)
{
Color32 c = pixels[y * width + x];
return c.a > 10; // 透明度阈值
}
Rect FloodFill(int startX, int startY)
{
Queue<Vector2Int> q = new Queue<Vector2Int>();
q.Enqueue(new Vector2Int(startX, startY));
int minX = startX, maxX = startX;
int minY = startY, maxY = startY;
visited[startX, startY] = true;
while (q.Count > 0)
{
Vector2Int p = q.Dequeue();
int x = p.x;
int y = p.y;
// 更新边界
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
// 四方向扩散
TryAdd(x + 1, y, q);
TryAdd(x - 1, y, q);
TryAdd(x, y + 1, q);
TryAdd(x, y - 1, q);
}
// 注意 Unity 的 rect 是左下角为原点
return new Rect(minX, minY, maxX - minX + 1, maxY - minY + 1);
}
void TryAdd(int x, int y, Queue<Vector2Int> q)
{
if (x < 0 || x >= width || y < 0 || y >= height)
return;
if (visited[x, y])
return;
visited[x, y] = true;
if (!IsSolidPixel(x, y))
return;
q.Enqueue(new Vector2Int(x, y));
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9e1311fb27279f549b1260dade1cab15
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -40,6 +40,10 @@ public class SpriteMetaRestorer : EditorWindow
/// <summary>写入完成后是否删除已融合的 .asset 文件及其 .meta</summary>
private bool deleteAssetsAfterWrite = true;
/// <summary>是否允许写入与 PNG 不匹配的 Sprite(textureGuid 不同)</summary>
private bool allowMismatchedSprites = false;
#endregion
#region 数据结构
@@ -324,6 +328,15 @@ public class SpriteMetaRestorer : EditorWindow
logText = "";
}
// 新增:删除所有与 PNG 不匹配的 Sprite
if (targetTexture != null && assetList.Count > 0)
{
if (GUILayout.Button("删除不匹配的 Sprite", GUILayout.Height(32)))
{
RemoveMismatchedSprites();
}
}
EditorGUILayout.EndHorizontal();
// 自动检测按钮
@@ -341,9 +354,17 @@ public class SpriteMetaRestorer : EditorWindow
deleteAssetsAfterWrite
);
// 新增:是否允许写入与 PNG 不匹配的 Sprite
allowMismatchedSprites = EditorGUILayout.Toggle(
new GUIContent("允许写入与 PNG 不匹配的 Sprite",
"勾选:即使 Sprite 引用的纹理 guid 与当前 PNG 不同,也照样按其 rect 写入。\n不勾选:写入时会跳过这些不匹配的 Sprite。"),
allowMismatchedSprites
);
EditorGUILayout.Space(4);
}
/// <summary>
/// 日志区域
/// </summary>
@@ -623,6 +644,28 @@ public class SpriteMetaRestorer : EditorWindow
#endregion
#region Meta 文件操作
/// <summary>
/// 删除所有与当前 PNG 不匹配的 Sprite(textureGuid 不同)
/// </summary>
private void RemoveMismatchedSprites()
{
if (targetTexture == null || assetList.Count == 0)
return;
string pngGuid = GetAssetGuid(AssetDatabase.GetAssetPath(targetTexture) + ".meta");
if (string.IsNullOrEmpty(pngGuid))
return;
int before = assetList.Count;
assetList.RemoveAll(a => a.textureGuid != pngGuid);
int removed = before - assetList.Count;
if (removed > 0)
{
Debug.Log($"[SpriteMetaRestorer] 已删除 {removed} 个与目标 PNG 不匹配的 Sprite");
Repaint();
}
}
/// <summary>
/// 将解析到的 sprite 列表写入 .png.meta 的 spriteSheet.sprites 块
@@ -644,26 +687,7 @@ public class SpriteMetaRestorer : EditorWindow
string metaText = File.ReadAllText(metaPath, Encoding.UTF8);
StringBuilder sb = new StringBuilder();
// 检查当前 sprites 状态
if (metaText.Contains("sprites: []"))
{
sb.AppendLine("[INFO] 当前 sprites: [],将写入新数据");
}
else
{
// 检查是否已有 sprite 条目
bool hasSprites = System.Text.RegularExpressions.Regex.IsMatch(metaText, @" sprites:\n - ");
if (hasSprites)
{
// 询问是否覆盖
if (!EditorUtility.DisplayDialog("确认",
"当前 PNG.meta 已有 sprite 数据,是否覆盖?",
"覆盖", "取消"))
{
return;
}
}
}
string pngGuid = GetAssetGuid(metaPath);
// 构建 sprites YAML 文本
StringBuilder spritesYaml = new StringBuilder();
@@ -671,8 +695,18 @@ public class SpriteMetaRestorer : EditorWindow
foreach (var info in assetList)
{
if (!info.valid) continue;
if (!info.valid)
continue;
// ★★★ 新增:不匹配 PNG 时的处理逻辑 ★★★
bool matched = info.textureGuid == pngGuid;
if (!matched && !allowMismatchedSprites)
{
sb.AppendLine($" [跳过] {info.name}(textureGuid 不匹配当前 PNG)");
continue;
}
// ★★★ 原有写入逻辑保持不变 ★★★
spritesYaml.AppendLine(" - serializedVersion: 2");
spritesYaml.AppendLine($" name: {info.name}");
spritesYaml.AppendLine(" rect:");
@@ -695,43 +729,35 @@ public class SpriteMetaRestorer : EditorWindow
spritesYaml.AppendLine(" edges: []");
spritesYaml.AppendLine(" weights: []");
// 收集 nameFileIdTable 条目(internalID 暂时为 0,Unity reimport 后会自动分配)
nameFileIdEntries.Add($" {info.name}: 0");
sb.AppendLine($" [+] {info.name} rect=({info.rect.x:F0},{info.rect.y:F0},{info.rect.width:F0}x{info.rect.height:F0})");
}
string spritesBlock = spritesYaml.ToString().TrimEnd('\n', '\r');
// 替换 meta 文件中的 sprites 块
// 注意:不能用字符串拼接作为 Regex.Replace 的 replacement 参数,
// 因为 replacement 中的 $ 符号会被解析为反向引用导致异常。
// 统一用纯字符串替换(不依赖正则),避免转义问题。
string result;
string spritesContent = " sprites:\n" + spritesBlock + "\n";
// 替换 meta 中的 sprites 块(保持你原来的逻辑)
string result;
if (metaText.Contains("sprites: []"))
{
// 替换空数组 " sprites: []" → 完整 sprites 块
result = ReplaceFirst(metaText, " sprites: []", spritesContent);
}
else
{
// 替换已有的 sprites 多行块
int idx = metaText.IndexOf(" sprites:");
if (idx >= 0)
{
// 找到 sprites 块的结束位置(下一个同级字段,即缩进为4个空格且不是 "- " 开头的行)
int endIdx = FindBlockEnd(metaText, idx);
result = metaText.Substring(0, idx) + spritesContent + metaText.Substring(endIdx);
}
else
{
result = metaText; // 找不到 sprites 块,不替换
result = metaText;
}
}
// 更新 nameFileIdTable
// 更新 nameFileIdTable(保持你原来的逻辑)
if (nameFileIdEntries.Count > 0)
{
string tableBlock = " nameFileIdTable:\n" + string.Join("\n", nameFileIdEntries) + "\n";
@@ -743,9 +769,7 @@ public class SpriteMetaRestorer : EditorWindow
}
}
// 写入文件
File.WriteAllText(metaPath, result, Encoding.UTF8);
// 刷新 AssetDatabase 以触发 Unity 重新导入
AssetDatabase.ImportAsset(pngPath);
int validCount = assetList.Count(a => a.valid);
@@ -753,48 +777,11 @@ public class SpriteMetaRestorer : EditorWindow
sb.AppendLine($" {metaPath}");
sb.AppendLine("\n→ Unity 已自动 Reimport,请在 Inspector 中确认。");
// ── 删除已融合的 .asset 文件 ──
if (deleteAssetsAfterWrite)
{
int deletedCount = 0;
// 倒序遍历避免索引问题
for (int i = assetList.Count - 1; i >= 0; i--)
{
var info = assetList[i];
if (!info.valid) continue;
string assetFullPath = Path.GetFullPath(info.assetPath);
string metaFullPath = assetFullPath + ".meta";
// 删除 .asset 文件
if (File.Exists(assetFullPath))
{
File.Delete(assetFullPath);
deletedCount++;
}
// 删除 .asset.meta 文件
if (File.Exists(metaFullPath))
{
File.Delete(metaFullPath);
}
}
if (deletedCount > 0)
{
// 刷新 AssetDatabase,移除已删除文件的缓存
AssetDatabase.Refresh();
sb.AppendLine($"\n[删除] 已删除 {deletedCount} 个 .asset 文件及其 .meta");
// 清空列表(文件已删除)
assetList.Clear();
}
}
logText = sb.ToString();
showLog = true;
Debug.Log($"[SpriteMetaRestorer] 写入完成: {pngPath}");
}
/// <summary>
/// 替换字符串中第一次出现的 oldText
/// </summary>