diff --git a/Runtime/PSXUIExporter.cs b/Runtime/PSXUIExporter.cs index b11282e..6d69a61 100644 --- a/Runtime/PSXUIExporter.cs +++ b/Runtime/PSXUIExporter.cs @@ -164,19 +164,12 @@ namespace SplashEdit.RuntimeCode Debug.Log($"[UIExporter] Canvas '{canvas.CanvasName}' on '{canvas.gameObject.name}' " + $"canvasW={canvasW} canvasH={canvasH} childCount={canvas.transform.childCount}"); - // Log what each collector finds - int prevCount = elements.Count; - CollectImages(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements); - Debug.Log($"[UIExporter] Images: {elements.Count - prevCount}"); - prevCount = elements.Count; - CollectBoxes(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements); - Debug.Log($"[UIExporter] Boxes: {elements.Count - prevCount}"); - prevCount = elements.Count; - CollectTexts(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements, uniqueFonts); - Debug.Log($"[UIExporter] Texts: {elements.Count - prevCount}"); - prevCount = elements.Count; - CollectProgressBars(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements); - Debug.Log($"[UIExporter] ProgressBars: {elements.Count - prevCount}"); + // Collect all UI elements in hierarchy order (depth-first, sibling index). + // GetComponentsInChildren returns top-of-hierarchy first, but the C++ + // renderer draws index 0 first (back). Reverse so that top-of-hierarchy + // elements are rendered last (on top), matching Unity's visual stacking. + CollectAllElementsInHierarchyOrder(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements, uniqueFonts); + elements.Reverse(); Debug.Log($"[UIExporter] TOTAL elements: {elements.Count}"); string name = canvas.CanvasName ?? "canvas"; @@ -258,6 +251,214 @@ namespace SplashEdit.RuntimeCode // ─── Collectors ─── + /// + /// Walk the hierarchy depth-first in sibling order, collecting every + /// PSX UI component into so that draw order + /// matches the Unity scene tree (top-to-bottom = back-to-front). + /// + private static void CollectAllElementsInHierarchyOrder( + Transform root, RectTransform canvasRect, + float scaleX, float scaleY, Vector2 resolution, + List elements, + List uniqueFonts) + { + // GetComponentsInChildren iterates depth-first in sibling order — + // exactly the hierarchy ordering we want. + Transform[] allTransforms = root.GetComponentsInChildren(true); + foreach (Transform t in allTransforms) + { + if (t == root) continue; // skip the canvas root itself + + // Check each supported component type on this transform. + // A single GameObject should only have one PSX UI component, + // but we check all to be safe. + PSXUIImage img = t.GetComponent(); + if (img != null) + { + CollectSingleImage(img, canvasRect, scaleX, scaleY, resolution, elements); + continue; + } + + PSXUIBox box = t.GetComponent(); + if (box != null) + { + CollectSingleBox(box, canvasRect, scaleX, scaleY, resolution, elements); + continue; + } + + PSXUIText txt = t.GetComponent(); + if (txt != null) + { + CollectSingleText(txt, canvasRect, scaleX, scaleY, resolution, elements, uniqueFonts); + continue; + } + + PSXUIProgressBar bar = t.GetComponent(); + if (bar != null) + { + CollectSingleProgressBar(bar, canvasRect, scaleX, scaleY, resolution, elements); + continue; + } + } + } + + private static void CollectSingleImage( + PSXUIImage img, RectTransform canvasRect, + float scaleX, float scaleY, Vector2 resolution, + List elements) + { + RectTransform rt = img.GetComponent(); + if (rt == null) return; + + BakeLayout(rt, canvasRect, scaleX, scaleY, resolution, + out short x, out short y, out short w, out short h, + out byte amin_x, out byte amin_y, out byte amax_x, out byte amax_y); + + var data = new PSXUIElementData + { + Type = PSXUIElementType.Image, + StartVisible = img.StartVisible, + Name = TruncateName(img.ElementName), + X = x, Y = y, W = w, H = h, + AnchorMinX = amin_x, AnchorMinY = amin_y, + AnchorMaxX = amax_x, AnchorMaxY = amax_y, + ColorR = (byte)Mathf.Clamp(Mathf.RoundToInt(img.TintColor.r * 255f), 0, 255), + ColorG = (byte)Mathf.Clamp(Mathf.RoundToInt(img.TintColor.g * 255f), 0, 255), + ColorB = (byte)Mathf.Clamp(Mathf.RoundToInt(img.TintColor.b * 255f), 0, 255), + }; + + if (img.PackedTexture != null) + { + PSXTexture2D tex = img.PackedTexture; + int expander = 16 / (int)tex.BitDepth; + data.TexpageX = tex.TexpageX; + data.TexpageY = tex.TexpageY; + data.ClutX = (ushort)tex.ClutPackingX; + data.ClutY = (ushort)tex.ClutPackingY; + data.U0 = (byte)(tex.PackingX * expander); + data.V0 = (byte)tex.PackingY; + data.U1 = (byte)(tex.PackingX * expander + tex.Width - 1); + data.V1 = (byte)(tex.PackingY + tex.Height - 1); + data.BitDepthIndex = tex.BitDepth switch + { + PSXBPP.TEX_4BIT => 0, + PSXBPP.TEX_8BIT => 1, + PSXBPP.TEX_16BIT => 2, + _ => 2 + }; + + Debug.Log($"[UIImage] '{img.ElementName}' src='{(tex.OriginalTexture ? tex.OriginalTexture.name : "null")}' " + + $"bpp={(int)tex.BitDepth} W={tex.Width} H={tex.Height} QW={tex.QuantizedWidth} " + + $"packXY=({tex.PackingX},{tex.PackingY}) tpage=({tex.TexpageX},{tex.TexpageY}) " + + $"clutXY=({tex.ClutPackingX},{tex.ClutPackingY}) " + + $"UV=({data.U0},{data.V0})->({data.U1},{data.V1}) expander={expander} bitIdx={data.BitDepthIndex}"); + } + else + { + Debug.LogWarning($"[UIImage] '{img.ElementName}' has NULL PackedTexture!"); + } + + elements.Add(data); + } + + private static void CollectSingleBox( + PSXUIBox box, RectTransform canvasRect, + float scaleX, float scaleY, Vector2 resolution, + List elements) + { + RectTransform rt = box.GetComponent(); + if (rt == null) return; + + BakeLayout(rt, canvasRect, scaleX, scaleY, resolution, + out short x, out short y, out short w, out short h, + out byte amin_x, out byte amin_y, out byte amax_x, out byte amax_y); + + elements.Add(new PSXUIElementData + { + Type = PSXUIElementType.Box, + StartVisible = box.StartVisible, + Name = TruncateName(box.ElementName), + X = x, Y = y, W = w, H = h, + AnchorMinX = amin_x, AnchorMinY = amin_y, + AnchorMaxX = amax_x, AnchorMaxY = amax_y, + ColorR = (byte)Mathf.Clamp(Mathf.RoundToInt(box.BoxColor.r * 255f), 0, 255), + ColorG = (byte)Mathf.Clamp(Mathf.RoundToInt(box.BoxColor.g * 255f), 0, 255), + ColorB = (byte)Mathf.Clamp(Mathf.RoundToInt(box.BoxColor.b * 255f), 0, 255), + }); + } + + private static void CollectSingleText( + PSXUIText txt, RectTransform canvasRect, + float scaleX, float scaleY, Vector2 resolution, + List elements, + List uniqueFonts) + { + RectTransform rt = txt.GetComponent(); + if (rt == null) return; + + BakeLayout(rt, canvasRect, scaleX, scaleY, resolution, + out short x, out short y, out short w, out short h, + out byte amin_x, out byte amin_y, out byte amax_x, out byte amax_y); + + string defaultText = txt.DefaultText ?? ""; + if (defaultText.Length > 63) defaultText = defaultText.Substring(0, 63); + + byte fontIndex = 0; + PSXFontAsset effectiveFont = txt.GetEffectiveFont(); + if (effectiveFont != null && uniqueFonts != null) + { + int idx = uniqueFonts.IndexOf(effectiveFont); + if (idx >= 0) fontIndex = (byte)(idx + 1); + } + + elements.Add(new PSXUIElementData + { + Type = PSXUIElementType.Text, + StartVisible = txt.StartVisible, + Name = TruncateName(txt.ElementName), + X = x, Y = y, W = w, H = h, + AnchorMinX = amin_x, AnchorMinY = amin_y, + AnchorMaxX = amax_x, AnchorMaxY = amax_y, + ColorR = (byte)Mathf.Clamp(Mathf.RoundToInt(txt.TextColor.r * 255f), 0, 255), + ColorG = (byte)Mathf.Clamp(Mathf.RoundToInt(txt.TextColor.g * 255f), 0, 255), + ColorB = (byte)Mathf.Clamp(Mathf.RoundToInt(txt.TextColor.b * 255f), 0, 255), + DefaultText = defaultText, + FontIndex = fontIndex, + }); + } + + private static void CollectSingleProgressBar( + PSXUIProgressBar bar, RectTransform canvasRect, + float scaleX, float scaleY, Vector2 resolution, + List elements) + { + RectTransform rt = bar.GetComponent(); + if (rt == null) return; + + BakeLayout(rt, canvasRect, scaleX, scaleY, resolution, + out short x, out short y, out short w, out short h, + out byte amin_x, out byte amin_y, out byte amax_x, out byte amax_y); + + elements.Add(new PSXUIElementData + { + Type = PSXUIElementType.Progress, + StartVisible = bar.StartVisible, + Name = TruncateName(bar.ElementName), + X = x, Y = y, W = w, H = h, + AnchorMinX = amin_x, AnchorMinY = amin_y, + AnchorMaxX = amax_x, AnchorMaxY = amax_y, + ColorR = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.FillColor.r * 255f), 0, 255), + ColorG = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.FillColor.g * 255f), 0, 255), + ColorB = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.FillColor.b * 255f), 0, 255), + BgR = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.BackgroundColor.r * 255f), 0, 255), + BgG = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.BackgroundColor.g * 255f), 0, 255), + BgB = (byte)Mathf.Clamp(Mathf.RoundToInt(bar.BackgroundColor.b * 255f), 0, 255), + ProgressValue = (byte)bar.InitialValue, + }); + } + + // ─── Legacy per-type collectors (kept for reference, no longer called) ─── + private static void CollectImages( Transform root, RectTransform canvasRect, float scaleX, float scaleY, Vector2 resolution,