每日自动提交 2026-04-10 18:30
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
// File: Assets/Editor/PsbToUGUIWindow.cs
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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")]
|
||||
@@ -182,74 +184,12 @@ public class PsbToUGUIWindow : EditorWindow
|
||||
|
||||
if (exportAsPng)
|
||||
{
|
||||
// ===== 导出 PNG 文件 =====
|
||||
Object prefab = PrefabUtility.GetCorrespondingObjectFromSource(sourceRoot);
|
||||
string psbPath = AssetDatabase.GetAssetPath(prefab);
|
||||
if (string.IsNullOrEmpty(psbPath))
|
||||
{
|
||||
psbPath = AssetDatabase.GetAssetPath(sr.sprite);
|
||||
}
|
||||
|
||||
string psbFolder = Path.GetDirectoryName(psbPath);
|
||||
if (string.IsNullOrEmpty(psbFolder))
|
||||
{
|
||||
Debug.LogError($"无法获取有效的 psb 文件夹路径,源对象: {sourceRoot.name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
string psbName = Path.GetFileNameWithoutExtension(psbPath);
|
||||
string targetFolder = Path.Combine(psbFolder, psbName);
|
||||
if (!AssetDatabase.IsValidFolder(targetFolder))
|
||||
{
|
||||
AssetDatabase.CreateFolder(psbFolder, psbName);
|
||||
}
|
||||
|
||||
// 使用 RenderTexture 拷贝再导出 PNG
|
||||
Sprite srcSprite = sr.sprite;
|
||||
Texture2D srcTex = srcSprite.texture;
|
||||
Rect rect = srcSprite.rect;
|
||||
int w = Mathf.RoundToInt(rect.width);
|
||||
int h = Mathf.RoundToInt(rect.height);
|
||||
|
||||
RenderTexture rt = RenderTexture.GetTemporary(srcTex.width, srcTex.height, 0);
|
||||
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);
|
||||
|
||||
byte[] pngData = outTex.EncodeToPNG();
|
||||
string pngPath = Path.Combine(targetFolder, srcSprite.name + ".png");
|
||||
pngPath = AssetDatabase.GenerateUniqueAssetPath(pngPath);
|
||||
|
||||
if (string.IsNullOrEmpty(pngPath))
|
||||
{
|
||||
Debug.LogWarning($"PNG 路径为空,跳过保存。源对象: {sourceRoot.name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
File.WriteAllBytes(pngPath, pngData);
|
||||
|
||||
AssetDatabase.ImportAsset(pngPath);
|
||||
var texImp = AssetImporter.GetAtPath(pngPath) as TextureImporter;
|
||||
if (texImp != null)
|
||||
{
|
||||
texImp.textureType = TextureImporterType.Sprite;
|
||||
texImp.spritePixelsPerUnit = referencePPU;
|
||||
texImp.alphaIsTransparency = true;
|
||||
texImp.spritePivot = srcSprite.pivot / new Vector2(rect.width, rect.height);
|
||||
texImp.SaveAndReimport();
|
||||
}
|
||||
|
||||
copiedSprite = AssetDatabase.LoadAssetAtPath<Sprite>(pngPath);
|
||||
// ===== 导出 PNG 文件(修复尺寸问题)=====
|
||||
copiedSprite = ExportSpriteAsPNG(sr.sprite, sourceRoot);
|
||||
if (copiedSprite == null)
|
||||
{
|
||||
Debug.LogError($"无法加载 Sprite: {pngPath}");
|
||||
continue;
|
||||
Debug.LogWarning($"导出 PNG 失败,使用原始 Sprite。源对象: {src.name}");
|
||||
copiedSprite = sr.sprite;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -315,4 +255,686 @@ public class PsbToUGUIWindow : EditorWindow
|
||||
: "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
|
||||
Reference in New Issue
Block a user