Files
5.24BuYu/Assets/Framework/Tool/UITool/Editor/PSBToUIConverter.cs
T
2025-12-11 16:40:24 +08:00

277 lines
11 KiB
C#

// File: Assets/Editor/PsbToUGUIWindow.cs
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Linq;
using System.IO;
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";
[MenuItem("Tools/PSB → UGUI Converter")]
public static void ShowWindow()
{
var win = GetWindow<PsbToUGUIWindow>("PSB → UGUI");
win.minSize = new Vector2(420, 320);
}
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();
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 and export PNG files
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;
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);
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();
}
Sprite copiedSprite = AssetDatabase.LoadAssetAtPath<Sprite>(pngPath);
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(rect.width, 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("PSB → UGUI conversion completed (sprites exported as PNG and assigned).");
}
}