1
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user