277 lines
11 KiB
C#
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).");
|
|
}
|
|
}
|