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,