Files
5.24BuYu/Assets/MyGame/Framework/Editor/PSBToUIConverter.cs
T
2026-10-20 18:30:03 +08:00

940 lines
34 KiB
C#

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
#region 单线程
public class PsbToUGUIWindow : EditorWindow
{
[Header("Inputs")]
private GameObject sourceRoot;
private Canvas targetCanvas;
[Header("Config")]
private float referencePPU = 100f; // Match PSD Importer Pixels Per Unit
private bool copyAlphaFromRenderer = true;
private bool preserveSortingOrder = true;
private bool addRaycastTargets = false; // For UI images; off if purely visual
private bool autoCreateCanvasIfMissing = true;
private string parentName = "PSB_UI";
// 新增 Toggle
private bool exportAsPng = false;
[MenuItem("Tools/PSB → UGUI Converter")]
public static void ShowWindow()
{
var win = GetWindow<PsbToUGUIWindow>("PSB → UGUI");
win.minSize = new Vector2(420, 360);
}
private void OnGUI()
{
EditorGUILayout.LabelField("Source", EditorStyles.boldLabel);
sourceRoot = (GameObject)EditorGUILayout.ObjectField(
new GUIContent("PSB root (scene GameObject)", "Select the instantiated PSB root that contains SpriteRenderers"),
sourceRoot, typeof(GameObject), true);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Target", EditorStyles.boldLabel);
targetCanvas = (Canvas)EditorGUILayout.ObjectField(
new GUIContent("Target Canvas", "Canvas to place generated UI under"),
targetCanvas, typeof(Canvas), true);
autoCreateCanvasIfMissing = EditorGUILayout.Toggle(
new GUIContent("Auto-create canvas if missing", "Create an Overlay Canvas+EventSystem when none provided"),
autoCreateCanvasIfMissing
);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Conversion settings", EditorStyles.boldLabel);
referencePPU = EditorGUILayout.FloatField(
new GUIContent("Reference Pixels Per Unit", "Use the PSD Importer PPU (often 100). Controls world→UI pixel mapping"),
referencePPU
);
copyAlphaFromRenderer = EditorGUILayout.Toggle(
new GUIContent("Copy alpha from SpriteRenderer color", "Image color.a = SpriteRenderer.color.a"),
copyAlphaFromRenderer
);
addRaycastTargets = EditorGUILayout.Toggle(
new GUIContent("Enable raycast target on Images", "Turn off for purely decorative layers"),
addRaycastTargets
);
preserveSortingOrder = EditorGUILayout.Toggle(
new GUIContent("Preserve sorting order", "Use sortingOrder to set sibling order"),
preserveSortingOrder
);
parentName = EditorGUILayout.TextField(
new GUIContent("Parent name", "Name of the generated UI root under the Canvas"),
parentName
);
// 新增 Toggle
exportAsPng = EditorGUILayout.Toggle(
new GUIContent("Export sprites as PNG", "If checked, each sprite will be exported as a PNG file"),
exportAsPng
);
EditorGUILayout.Space();
using (new EditorGUI.DisabledScope(sourceRoot == null))
{
if (GUILayout.Button("Convert to UGUI"))
{
Convert();
}
}
}
private void Convert()
{
if (sourceRoot == null)
{
ShowNotification(new GUIContent("Select a source root."));
return;
}
Canvas canvas = targetCanvas;
if (canvas == null && autoCreateCanvasIfMissing)
{
var canvasGO = new GameObject("Canvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
canvas = canvasGO.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
var scaler = canvasGO.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920, 1080);
scaler.referencePixelsPerUnit = referencePPU;
if (FindObjectOfType<UnityEngine.EventSystems.EventSystem>() == null)
{
var es = new GameObject("EventSystem", typeof(UnityEngine.EventSystems.EventSystem), typeof(UnityEngine.EventSystems.StandaloneInputModule));
Undo.RegisterCreatedObjectUndo(es, "Create EventSystem");
}
Undo.RegisterCreatedObjectUndo(canvasGO, "Create Canvas");
}
if (canvas == null)
{
ShowNotification(new GUIContent("Provide a Canvas or enable auto-create."));
return;
}
GameObject uiRootGO = new GameObject(string.IsNullOrEmpty(parentName) ? "PSB_UI" : parentName, typeof(RectTransform));
Undo.RegisterCreatedObjectUndo(uiRootGO, "Create UI Root");
uiRootGO.transform.SetParent(canvas.transform, false);
var uiRootRT = uiRootGO.GetComponent<RectTransform>();
uiRootRT.anchorMin = new Vector2(0.5f, 0.5f);
uiRootRT.anchorMax = new Vector2(0.5f, 0.5f);
uiRootRT.pivot = new Vector2(0.5f, 0.5f);
uiRootRT.anchoredPosition = Vector2.zero;
uiRootRT.localScale = Vector3.one;
var mapping = new Dictionary<Transform, RectTransform>();
mapping[sourceRoot.transform] = uiRootRT;
var all = sourceRoot.GetComponentsInChildren<Transform>(true)
.OrderBy(t => t == sourceRoot.transform ? -1 : 0)
.ThenBy(t => t.GetSiblingIndex())
.ToList();
foreach (var t in all)
{
if (t == sourceRoot.transform) continue;
var parent = t.parent;
if (!mapping.TryGetValue(parent, out var parentRT))
{
Debug.LogWarning($"Parent mapping missing for {t.name}. Skipping.");
continue;
}
var containerGO = new GameObject(t.name, typeof(RectTransform));
Undo.RegisterCreatedObjectUndo(containerGO, "Create UI Container");
var rt = containerGO.GetComponent<RectTransform>();
containerGO.transform.SetParent(parentRT, false);
rt.localPosition = t.localPosition * referencePPU;
rt.localRotation = t.localRotation;
rt.localScale = t.localScale;
rt.anchorMin = new Vector2(0.5f, 0.5f);
rt.anchorMax = new Vector2(0.5f, 0.5f);
rt.pivot = new Vector2(0.5f, 0.5f);
mapping[t] = rt;
}
// Create UI Images for SpriteRenderers
foreach (var kv in mapping.ToList())
{
var src = kv.Key;
var dstRT = kv.Value;
var sr = src.GetComponent<SpriteRenderer>();
if (sr == null || sr.sprite == null)
continue;
Sprite copiedSprite = null;
if (exportAsPng)
{
// ===== 导出 PNG 文件(修复尺寸问题)=====
copiedSprite = ExportSpriteAsPNG(sr.sprite, sourceRoot);
if (copiedSprite == null)
{
Debug.LogWarning($"导出 PNG 失败,使用原始 Sprite。源对象: {src.name}");
copiedSprite = sr.sprite;
}
}
else
{
copiedSprite = sr.sprite;
if (copiedSprite == null)
{
Debug.LogWarning($"原始 Sprite 为空,跳过。源对象: {src.name}");
continue;
}
}
// Add Image
var img = dstRT.gameObject.AddComponent<Image>();
img.sprite = copiedSprite;
img.preserveAspect = false;
img.raycastTarget = addRaycastTargets;
if (copyAlphaFromRenderer)
{
var c = img.color;
c.a = sr.color.a;
img.color = c;
}
img.SetNativeSize();
dstRT.pivot = copiedSprite.pivot / new Vector2(copiedSprite.rect.width, copiedSprite.rect.height);
}
if (preserveSortingOrder)
{
var parents = mapping.Values.Select(rt => rt.parent).Distinct().Where(p => p != null);
foreach (var p in parents)
{
var children = new List<RectTransform>();
for (int i = 0; i < p.childCount; i++)
{
children.Add(p.GetChild(i) as RectTransform);
}
children = children
.OrderBy(rt =>
{
var correspondingSrc = mapping.FirstOrDefault(x => x.Value == rt).Key;
var sr = correspondingSrc != null ? correspondingSrc.GetComponent<SpriteRenderer>() : null;
int order = sr != null ? sr.sortingOrder : 0;
return order;
})
.ThenBy(rt => rt.name)
.ToList();
for (int i = 0; i < children.Count; i++)
{
children[i].SetSiblingIndex(i);
}
}
}
Selection.activeObject = uiRootGO;
EditorGUIUtility.PingObject(uiRootGO);
Debug.Log(exportAsPng
? "PSB → UGUI conversion completed (sprites exported as PNG and assigned)."
: "PSB → UGUI conversion completed (using original psb sprites).");
}
/// <summary>
/// 将 Sprite 导出为 PNG 文件,并保持原始尺寸和 PPU 设置
/// </summary>
private Sprite ExportSpriteAsPNG(Sprite originalSprite, GameObject sourceRoot)
{
try
{
// 获取原始 Sprite 的信息
Texture2D srcTex = originalSprite.texture;
Rect rect = originalSprite.rect;
int w = Mathf.RoundToInt(rect.width);
int h = Mathf.RoundToInt(rect.height);
// 获取原始 PPU 和 pivot
float originalPPU = originalSprite.pixelsPerUnit;
Vector2 pivotNormalized = new Vector2(
originalSprite.pivot.x / rect.width,
originalSprite.pivot.y / rect.height
);
// 确定保存路径
Object prefab = PrefabUtility.GetCorrespondingObjectFromSource(sourceRoot);
string psbPath = AssetDatabase.GetAssetPath(prefab);
if (string.IsNullOrEmpty(psbPath))
{
psbPath = AssetDatabase.GetAssetPath(originalSprite);
}
string psbFolder = Path.GetDirectoryName(psbPath);
if (string.IsNullOrEmpty(psbFolder))
{
Debug.LogError($"无法获取有效的 psb 文件夹路径,源对象: {sourceRoot.name}");
return null;
}
string psbName = Path.GetFileNameWithoutExtension(psbPath);
string targetFolder = Path.Combine(psbFolder, psbName);
if (!AssetDatabase.IsValidFolder(targetFolder))
{
AssetDatabase.CreateFolder(psbFolder, psbName);
}
// 使用 RenderTexture 导出 PNG
RenderTexture rt = RenderTexture.GetTemporary(srcTex.width, srcTex.height, 0, RenderTextureFormat.ARGB32);
Graphics.Blit(srcTex, rt);
RenderTexture.active = rt;
Texture2D outTex = new Texture2D(w, h, TextureFormat.RGBA32, false);
outTex.ReadPixels(new Rect(rect.x, rect.y, rect.width, rect.height), 0, 0);
outTex.Apply();
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(rt);
// 保存 PNG 文件
byte[] pngData = outTex.EncodeToPNG();
string pngPath = Path.Combine(targetFolder, originalSprite.name + ".png");
pngPath = AssetDatabase.GenerateUniqueAssetPath(pngPath);
if (string.IsNullOrEmpty(pngPath))
{
Debug.LogWarning($"PNG 路径为空,跳过保存。源对象: {sourceRoot.name}");
return null;
}
File.WriteAllBytes(pngPath, pngData);
DestroyImmediate(outTex);
// 导入并配置纹理
AssetDatabase.ImportAsset(pngPath);
var texImp = AssetImporter.GetAtPath(pngPath) as TextureImporter;
if (texImp != null)
{
texImp.textureType = TextureImporterType.Sprite;
texImp.spritePixelsPerUnit = originalPPU; // 关键:使用原始 PPU
texImp.alphaIsTransparency = true;
texImp.spritePivot = pivotNormalized;
texImp.mipmapEnabled = false;
texImp.filterMode = FilterMode.Bilinear;
texImp.textureCompression = TextureImporterCompression.Uncompressed;
texImp.SaveAndReimport();
}
Sprite result = AssetDatabase.LoadAssetAtPath<Sprite>(pngPath);
if (result == null)
{
Debug.LogError($"无法加载 Sprite: {pngPath}");
return null;
}
return result;
}
catch (System.Exception e)
{
Debug.LogError($"导出 PNG 时发生错误: {e.Message}\n{e.StackTrace}");
return null;
}
}
}
#endregion
#region 多线程Jobs
public class PsbToUGUIWindowOptimized : EditorWindow
{
[Header("Inputs")]
private GameObject sourceRoot;
private Canvas targetCanvas;
[Header("Config")]
private float referencePPU = 100f;
private bool copyAlphaFromRenderer = true;
private bool preserveSortingOrder = true;
private bool addRaycastTargets = false;
private bool autoCreateCanvasIfMissing = true;
private string parentName = "PSB_UI";
private bool exportAsPng = false;
// 性能优化选项
private bool useParallelExport = true;
private int maxParallelTasks = 4;
[MenuItem("Tools/PSB → UGUI Converter (Jobs)")]
public static void ShowWindow()
{
var win = GetWindow<PsbToUGUIWindowOptimized>("PSB → UGUI (Jobs)");
win.minSize = new Vector2(450, 420);
}
private void OnGUI()
{
EditorGUILayout.LabelField("Source", EditorStyles.boldLabel);
sourceRoot = (GameObject)EditorGUILayout.ObjectField(
new GUIContent("PSB root (scene GameObject)", "Select the instantiated PSB root that contains SpriteRenderers"),
sourceRoot, typeof(GameObject), true);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Target", EditorStyles.boldLabel);
targetCanvas = (Canvas)EditorGUILayout.ObjectField(
new GUIContent("Target Canvas", "Canvas to place generated UI under"),
targetCanvas, typeof(Canvas), true);
autoCreateCanvasIfMissing = EditorGUILayout.Toggle(
new GUIContent("Auto-create canvas if missing", "Create an Overlay Canvas+EventSystem when none provided"),
autoCreateCanvasIfMissing
);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Conversion settings", EditorStyles.boldLabel);
referencePPU = EditorGUILayout.FloatField(
new GUIContent("Reference Pixels Per Unit", "Use the PSD Importer PPU (often 100). Controls world→UI pixel mapping"),
referencePPU
);
copyAlphaFromRenderer = EditorGUILayout.Toggle(
new GUIContent("Copy alpha from SpriteRenderer color", "Image color.a = SpriteRenderer.color.a"),
copyAlphaFromRenderer
);
addRaycastTargets = EditorGUILayout.Toggle(
new GUIContent("Enable raycast target on Images", "Turn off for purely decorative layers"),
addRaycastTargets
);
preserveSortingOrder = EditorGUILayout.Toggle(
new GUIContent("Preserve sorting order", "Use sortingOrder to set sibling order"),
preserveSortingOrder
);
parentName = EditorGUILayout.TextField(
new GUIContent("Parent name", "Name of the generated UI root under the Canvas"),
parentName
);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Performance", EditorStyles.boldLabel);
exportAsPng = EditorGUILayout.Toggle(
new GUIContent("Export sprites as PNG", "If checked, each sprite will be exported as a PNG file"),
exportAsPng
);
if (exportAsPng)
{
useParallelExport = EditorGUILayout.Toggle(
new GUIContent("Parallel export (faster)", "Use multiple threads to export PNGs simultaneously"),
useParallelExport
);
if (useParallelExport)
{
maxParallelTasks = EditorGUILayout.IntSlider(
new GUIContent("Max parallel tasks", "Number of concurrent export threads"),
maxParallelTasks, 1, 8
);
}
}
EditorGUILayout.Space();
using (new EditorGUI.DisabledScope(sourceRoot == null))
{
if (GUILayout.Button("Convert to UGUI (Optimized)"))
{
Convert();
}
}
}
private void Convert()
{
if (sourceRoot == null)
{
ShowNotification(new GUIContent("Select a source root."));
return;
}
EditorUtility.DisplayProgressBar("Converting to UGUI (Optimized)", "Setting up canvas...", 0f);
try
{
Canvas canvas = SetupCanvas();
if (canvas == null) return;
GameObject uiRootGO = CreateUIRoot(canvas);
var mapping = CreateTransformMapping(uiRootGO);
EditorUtility.DisplayProgressBar("Converting to UGUI (Optimized)", "Collecting sprite data...", 0.2f);
// 收集所有需要处理的SpriteRenderer(在主线程)
var spriteDataList = new List<SpriteExportData>();
foreach (var kv in mapping)
{
var sr = kv.Key.GetComponent<SpriteRenderer>();
if (sr != null && sr.sprite != null)
{
spriteDataList.Add(new SpriteExportData
{
src = kv.Key,
dstRT = kv.Value,
sr = sr,
originalSprite = sr.sprite,
spriteName = sr.sprite.name,
// 在主线程获取所有需要的数据
texture = sr.sprite.texture,
rect = sr.sprite.rect,
originalPPU = sr.sprite.pixelsPerUnit,
pivot = sr.sprite.pivot
});
}
}
var spriteMapping = new ConcurrentDictionary<Transform, Sprite>();
if (exportAsPng && useParallelExport && spriteDataList.Count > 0)
{
EditorUtility.DisplayProgressBar("Converting to UGUI (Optimized)", "Exporting PNGs (parallel)...", 0.3f);
// 准备并行任务
var tasks = new List<Task<(Transform src, string pngPath, SpriteExportData data)>>();
var semaphore = new SemaphoreSlim(maxParallelTasks);
foreach (var data in spriteDataList)
{
tasks.Add(Task.Run(() => ExportSpritePNGAsync(data, semaphore)));
}
// 等待所有导出任务完成
Task.WaitAll(tasks.ToArray());
// 在主线程导入所有PNG
EditorUtility.DisplayProgressBar("Converting to UGUI (Optimized)", "Importing PNGs...", 0.6f);
foreach (var task in tasks)
{
var result = task.Result;
if (!string.IsNullOrEmpty(result.pngPath))
{
var importedSprite = ImportPNGAsSprite(result.pngPath, result.data);
if (importedSprite != null)
{
spriteMapping[result.src] = importedSprite;
}
else
{
spriteMapping[result.src] = result.data.originalSprite;
}
}
else
{
spriteMapping[result.src] = result.data.originalSprite;
}
}
}
else
{
// 顺序处理
for (int i = 0; i < spriteDataList.Count; i++)
{
var data = spriteDataList[i];
float progress = 0.3f + (0.5f * i / spriteDataList.Count);
EditorUtility.DisplayProgressBar("Converting to UGUI (Optimized)", $"Processing {data.spriteName}...", progress);
Sprite targetSprite = data.originalSprite;
if (exportAsPng)
{
targetSprite = ExportSpriteAsPNGSequential(data) ?? targetSprite;
}
spriteMapping[data.src] = targetSprite;
}
}
EditorUtility.DisplayProgressBar("Converting to UGUI (Optimized)", "Creating UI components...", 0.8f);
// 创建UI组件(必须在主线程)
foreach (var data in spriteDataList)
{
if (spriteMapping.TryGetValue(data.src, out var targetSprite))
{
CreateUIImage(data.dstRT, targetSprite, data.sr);
}
}
if (preserveSortingOrder)
{
ApplySortingOrder(mapping);
}
Selection.activeObject = uiRootGO;
EditorGUIUtility.PingObject(uiRootGO);
Debug.Log(exportAsPng
? $"PSB → UGUI conversion completed (sprites exported as PNG using {(useParallelExport ? "parallel" : "sequential")} mode)."
: "PSB → UGUI conversion completed (using original psb sprites).");
}
finally
{
EditorUtility.ClearProgressBar();
}
}
private Canvas SetupCanvas()
{
Canvas canvas = targetCanvas;
if (canvas == null && autoCreateCanvasIfMissing)
{
var canvasGO = new GameObject("Canvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
canvas = canvasGO.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
var scaler = canvasGO.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920, 1080);
scaler.referencePixelsPerUnit = referencePPU;
if (FindObjectOfType<UnityEngine.EventSystems.EventSystem>() == null)
{
var es = new GameObject("EventSystem", typeof(UnityEngine.EventSystems.EventSystem), typeof(UnityEngine.EventSystems.StandaloneInputModule));
Undo.RegisterCreatedObjectUndo(es, "Create EventSystem");
}
Undo.RegisterCreatedObjectUndo(canvasGO, "Create Canvas");
}
if (canvas == null)
{
ShowNotification(new GUIContent("Provide a Canvas or enable auto-create."));
return null;
}
return canvas;
}
private GameObject CreateUIRoot(Canvas canvas)
{
GameObject uiRootGO = new GameObject(string.IsNullOrEmpty(parentName) ? "PSB_UI" : parentName, typeof(RectTransform));
Undo.RegisterCreatedObjectUndo(uiRootGO, "Create UI Root");
uiRootGO.transform.SetParent(canvas.transform, false);
var uiRootRT = uiRootGO.GetComponent<RectTransform>();
uiRootRT.anchorMin = new Vector2(0.5f, 0.5f);
uiRootRT.anchorMax = new Vector2(0.5f, 0.5f);
uiRootRT.pivot = new Vector2(0.5f, 0.5f);
uiRootRT.anchoredPosition = Vector2.zero;
uiRootRT.localScale = Vector3.one;
return uiRootGO;
}
private Dictionary<Transform, RectTransform> CreateTransformMapping(GameObject uiRootGO)
{
var mapping = new Dictionary<Transform, RectTransform>();
mapping[sourceRoot.transform] = uiRootGO.GetComponent<RectTransform>();
var all = sourceRoot.GetComponentsInChildren<Transform>(true)
.OrderBy(t => t == sourceRoot.transform ? -1 : 0)
.ThenBy(t => t.GetSiblingIndex())
.ToList();
foreach (var t in all)
{
if (t == sourceRoot.transform) continue;
if (!mapping.TryGetValue(t.parent, out var parentRT))
{
Debug.LogWarning($"Parent mapping missing for {t.name}. Skipping.");
continue;
}
var containerGO = new GameObject(t.name, typeof(RectTransform));
Undo.RegisterCreatedObjectUndo(containerGO, "Create UI Container");
var rt = containerGO.GetComponent<RectTransform>();
containerGO.transform.SetParent(parentRT, false);
rt.localPosition = t.localPosition * referencePPU;
rt.localRotation = t.localRotation;
rt.localScale = t.localScale;
rt.anchorMin = new Vector2(0.5f, 0.5f);
rt.anchorMax = new Vector2(0.5f, 0.5f);
rt.pivot = new Vector2(0.5f, 0.5f);
mapping[t] = rt;
}
return mapping;
}
private void CreateUIImage(RectTransform dstRT, Sprite sprite, SpriteRenderer sr)
{
var img = dstRT.gameObject.AddComponent<Image>();
img.sprite = sprite;
img.preserveAspect = false;
img.raycastTarget = addRaycastTargets;
if (copyAlphaFromRenderer)
{
var c = img.color;
c.a = sr.color.a;
img.color = c;
}
img.SetNativeSize();
dstRT.pivot = sprite.pivot / new Vector2(sprite.rect.width, sprite.rect.height);
}
private void ApplySortingOrder(Dictionary<Transform, RectTransform> mapping)
{
var parents = mapping.Values.Select(rt => rt.parent).Distinct().Where(p => p != null);
foreach (var p in parents)
{
var children = new List<RectTransform>();
for (int i = 0; i < p.childCount; i++)
{
children.Add(p.GetChild(i) as RectTransform);
}
children = children
.OrderBy(rt =>
{
var correspondingSrc = mapping.FirstOrDefault(x => x.Value == rt).Key;
var sr = correspondingSrc != null ? correspondingSrc.GetComponent<SpriteRenderer>() : null;
return sr != null ? sr.sortingOrder : 0;
})
.ThenBy(rt => rt.name)
.ToList();
for (int i = 0; i < children.Count; i++)
{
children[i].SetSiblingIndex(i);
}
}
}
// 异步导出PNG(在工作线程执行编码)
private (Transform src, string pngPath, SpriteExportData data) ExportSpritePNGAsync(SpriteExportData data, SemaphoreSlim semaphore)
{
semaphore.Wait();
try
{
string pngPath = ExportSpritePNGFromData(data);
return (data.src, pngPath, data);
}
finally
{
semaphore.Release();
}
}
// 从已收集的数据导出PNG(可以在工作线程执行)
private string ExportSpritePNGFromData(SpriteExportData data)
{
try
{
int w = Mathf.RoundToInt(data.rect.width);
int h = Mathf.RoundToInt(data.rect.height);
// 读取纹理数据
Color[] pixels = data.texture.GetPixels((int)data.rect.x, (int)data.rect.y, w, h);
// 创建纹理并编码
Texture2D outTex = new Texture2D(w, h, TextureFormat.RGBA32, false);
outTex.SetPixels(pixels);
outTex.Apply();
byte[] pngData = outTex.EncodeToPNG();
// 确定保存路径
string targetFolder = GetOrCreateTargetFolder(data);
if (string.IsNullOrEmpty(targetFolder))
{
UnityEngine.Object.DestroyImmediate(outTex);
return null;
}
string pngPath = Path.Combine(targetFolder, data.spriteName + ".png");
// 确保文件名唯一
int counter = 1;
string originalPath = pngPath;
while (File.Exists(pngPath))
{
pngPath = Path.Combine(targetFolder, $"{data.spriteName}_{counter}.png");
counter++;
}
File.WriteAllBytes(pngPath, pngData);
UnityEngine.Object.DestroyImmediate(outTex);
return pngPath;
}
catch (System.Exception e)
{
Debug.LogError($"导出 PNG 时发生错误 ({data.spriteName}): {e.Message}");
return null;
}
}
// 在主线程导入PNG为Sprite
private Sprite ImportPNGAsSprite(string pngPath, SpriteExportData data)
{
try
{
AssetDatabase.ImportAsset(pngPath);
var texImp = AssetImporter.GetAtPath(pngPath) as TextureImporter;
if (texImp != null)
{
texImp.textureType = TextureImporterType.Sprite;
texImp.spritePixelsPerUnit = data.originalPPU;
texImp.alphaIsTransparency = true;
Vector2 pivotNormalized = new Vector2(
data.pivot.x / data.rect.width,
data.pivot.y / data.rect.height
);
texImp.spritePivot = pivotNormalized;
texImp.mipmapEnabled = false;
texImp.filterMode = FilterMode.Bilinear;
texImp.textureCompression = TextureImporterCompression.Uncompressed;
texImp.SaveAndReimport();
}
return AssetDatabase.LoadAssetAtPath<Sprite>(pngPath);
}
catch (System.Exception e)
{
Debug.LogError($"导入 PNG 时发生错误 ({pngPath}): {e.Message}");
return null;
}
}
// 顺序版本 - 完整功能
private Sprite ExportSpriteAsPNGSequential(SpriteExportData data)
{
try
{
int w = Mathf.RoundToInt(data.rect.width);
int h = Mathf.RoundToInt(data.rect.height);
RenderTexture rt = RenderTexture.GetTemporary(data.texture.width, data.texture.height, 0, RenderTextureFormat.ARGB32);
Graphics.Blit(data.texture, rt);
RenderTexture.active = rt;
Texture2D outTex = new Texture2D(w, h, TextureFormat.RGBA32, false);
outTex.ReadPixels(new Rect(data.rect.x, data.rect.y, data.rect.width, data.rect.height), 0, 0);
outTex.Apply();
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(rt);
byte[] pngData = outTex.EncodeToPNG();
string targetFolder = GetOrCreateTargetFolder(data);
if (string.IsNullOrEmpty(targetFolder))
{
UnityEngine.Object.DestroyImmediate(outTex);
return null;
}
string pngPath = Path.Combine(targetFolder, data.spriteName + ".png");
pngPath = AssetDatabase.GenerateUniqueAssetPath(pngPath);
if (string.IsNullOrEmpty(pngPath))
{
UnityEngine.Object.DestroyImmediate(outTex);
return null;
}
File.WriteAllBytes(pngPath, pngData);
UnityEngine.Object.DestroyImmediate(outTex);
AssetDatabase.ImportAsset(pngPath);
var texImp = AssetImporter.GetAtPath(pngPath) as TextureImporter;
if (texImp != null)
{
texImp.textureType = TextureImporterType.Sprite;
texImp.spritePixelsPerUnit = data.originalPPU;
texImp.alphaIsTransparency = true;
Vector2 pivotNormalized = new Vector2(
data.pivot.x / data.rect.width,
data.pivot.y / data.rect.height
);
texImp.spritePivot = pivotNormalized;
texImp.mipmapEnabled = false;
texImp.filterMode = FilterMode.Bilinear;
texImp.textureCompression = TextureImporterCompression.Uncompressed;
texImp.SaveAndReimport();
}
return AssetDatabase.LoadAssetAtPath<Sprite>(pngPath);
}
catch (System.Exception e)
{
Debug.LogError($"导出 PNG 时发生错误 ({data.spriteName}): {e.Message}");
return null;
}
}
private string GetOrCreateTargetFolder(SpriteExportData data)
{
Object prefab = PrefabUtility.GetCorrespondingObjectFromSource(sourceRoot);
string psbPath = AssetDatabase.GetAssetPath(prefab);
if (string.IsNullOrEmpty(psbPath))
{
psbPath = AssetDatabase.GetAssetPath(data.originalSprite);
}
string psbFolder = Path.GetDirectoryName(psbPath);
if (string.IsNullOrEmpty(psbFolder))
{
Debug.LogError($"无法获取有效的 psb 文件夹路径,源对象: {sourceRoot.name}");
return null;
}
string psbName = Path.GetFileNameWithoutExtension(psbPath);
string targetFolder = Path.Combine(psbFolder, psbName);
// 确保在主线程创建文件夹
if (!AssetDatabase.IsValidFolder(targetFolder))
{
// 这个操作必须在主线程,所以如果当前在子线程就返回null
if (System.Threading.Thread.CurrentThread.ManagedThreadId != 1)
{
return null;
}
AssetDatabase.CreateFolder(psbFolder, psbName);
}
return targetFolder;
}
// 数据容器,用于传递Sprite信息
private class SpriteExportData
{
public Transform src;
public RectTransform dstRT;
public SpriteRenderer sr;
public Sprite originalSprite;
public string spriteName;
public Texture2D texture;
public Rect rect;
public float originalPPU;
public Vector2 pivot;
}
}
#endregion