Broken UI system

This commit is contained in:
Jan Racek
2026-03-25 12:25:48 +01:00
parent bb8e0804f5
commit 8914ba35cc
28 changed files with 2094 additions and 25 deletions

View File

@@ -0,0 +1,446 @@
using UnityEngine;
using UnityEditor;
using SplashEdit.RuntimeCode;
namespace SplashEdit.EditorCode
{
// ─── Scene Preview Gizmos ───
// These draw filled rectangles in the Scene view for WYSIWYG UI preview.
public static class PSXUIGizmos
{
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected)]
static void DrawBoxGizmo(PSXUIBox box, GizmoType gizmoType)
{
RectTransform rt = box.GetComponent<RectTransform>();
if (rt == null) return;
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
Color fill = box.BoxColor;
fill.a = (gizmoType & GizmoType.Selected) != 0 ? 0.8f : 0.5f;
Color border = Color.white;
border.a = (gizmoType & GizmoType.Selected) != 0 ? 1f : 0.4f;
Handles.DrawSolidRectangleWithOutline(corners, fill, border);
}
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected)]
static void DrawImageGizmo(PSXUIImage image, GizmoType gizmoType)
{
RectTransform rt = image.GetComponent<RectTransform>();
if (rt == null) return;
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
bool selected = (gizmoType & GizmoType.Selected) != 0;
// Draw texture preview if available
if (image.SourceTexture != null)
{
Color tint = image.TintColor;
tint.a = selected ? 0.9f : 0.6f;
Handles.DrawSolidRectangleWithOutline(corners, tint * 0.3f, tint);
// Draw texture in GUI overlay
Handles.BeginGUI();
Vector2 min = HandleUtility.WorldToGUIPoint(corners[0]);
Vector2 max = HandleUtility.WorldToGUIPoint(corners[2]);
Rect screenRect = new Rect(
Mathf.Min(min.x, max.x), Mathf.Min(min.y, max.y),
Mathf.Abs(max.x - min.x), Mathf.Abs(max.y - min.y));
if (screenRect.width > 2 && screenRect.height > 2)
{
GUI.color = new Color(tint.r, tint.g, tint.b, selected ? 0.9f : 0.5f);
GUI.DrawTexture(screenRect, image.SourceTexture, ScaleMode.StretchToFill);
GUI.color = Color.white;
}
Handles.EndGUI();
}
else
{
Color fill = new Color(0.4f, 0.4f, 0.8f, selected ? 0.4f : 0.2f);
Handles.DrawSolidRectangleWithOutline(corners, fill, Color.cyan);
}
}
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected)]
static void DrawTextGizmo(PSXUIText text, GizmoType gizmoType)
{
RectTransform rt = text.GetComponent<RectTransform>();
if (rt == null) return;
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
bool selected = (gizmoType & GizmoType.Selected) != 0;
Color border = text.TextColor;
border.a = selected ? 1f : 0.5f;
Color fill = new Color(0, 0, 0, selected ? 0.3f : 0.1f);
Handles.DrawSolidRectangleWithOutline(corners, fill, border);
// Draw text preview at actual PSX glyph size.
// On the PS1, chainprintf renders each glyph top-left aligned at the
// element's position — no centering. Mimic that here.
string label = string.IsNullOrEmpty(text.DefaultText) ? "[empty]" : text.DefaultText;
PSXFontAsset font = text.GetEffectiveFont();
int glyphW = font != null ? font.GlyphWidth : 8;
int glyphH = font != null ? font.GlyphHeight : 16;
Handles.BeginGUI();
// Convert top-left corner (corners[1] in Unity is top-left after GetWorldCorners)
Vector2 topLeft = HandleUtility.WorldToGUIPoint(corners[1]);
Vector2 botRight = HandleUtility.WorldToGUIPoint(corners[3]);
// Calculate pixel scale: how many screen pixels represent one PSX pixel
float rectWorldW = Vector3.Distance(corners[0], corners[3]);
float rectScreenW = Mathf.Abs(botRight.x - topLeft.x);
float rectW = rt.rect.width;
float psxPixelScale = (rectW > 0.01f) ? rectScreenW / rectW : 1f;
float charScreenW = glyphW * psxPixelScale;
float charScreenH = glyphH * psxPixelScale;
int fontSize = Mathf.Clamp(Mathf.RoundToInt(charScreenH * 0.75f), 6, 72);
GUIStyle style = new GUIStyle(EditorStyles.label);
style.normal.textColor = text.TextColor;
style.alignment = TextAnchor.UpperLeft;
style.fontSize = fontSize;
style.wordWrap = false;
style.clipping = TextClipping.Clip;
style.padding = new RectOffset(0, 0, 0, 0);
// Render position = top-left of the rect (matching PSX chainprintf behavior)
float guiW = Mathf.Abs(botRight.x - topLeft.x);
float guiH = Mathf.Abs(botRight.y - topLeft.y);
Rect guiRect = new Rect(
Mathf.Min(topLeft.x, botRight.x),
Mathf.Min(topLeft.y, botRight.y),
guiW, guiH);
GUI.color = new Color(text.TextColor.r, text.TextColor.g, text.TextColor.b, selected ? 1f : 0.7f);
GUI.Label(guiRect, label, style);
GUI.color = Color.white;
Handles.EndGUI();
}
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected)]
static void DrawProgressBarGizmo(PSXUIProgressBar bar, GizmoType gizmoType)
{
RectTransform rt = bar.GetComponent<RectTransform>();
if (rt == null) return;
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
bool selected = (gizmoType & GizmoType.Selected) != 0;
// Background
Color bgColor = bar.BackgroundColor;
bgColor.a = selected ? 0.8f : 0.5f;
Handles.DrawSolidRectangleWithOutline(corners, bgColor, Color.white * (selected ? 1f : 0.4f));
// Fill portion
float t = bar.InitialValue / 100f;
if (t > 0.001f)
{
Vector3[] fillCorners = new Vector3[4];
fillCorners[0] = corners[0]; // bottom-left
fillCorners[1] = corners[1]; // top-left
fillCorners[2] = Vector3.Lerp(corners[1], corners[2], t); // top-right (partial)
fillCorners[3] = Vector3.Lerp(corners[0], corners[3], t); // bottom-right (partial)
Color fillColor = bar.FillColor;
fillColor.a = selected ? 0.9f : 0.6f;
Handles.DrawSolidRectangleWithOutline(fillCorners, fillColor, Color.clear);
}
}
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected)]
static void DrawCanvasGizmo(PSXCanvas canvas, GizmoType gizmoType)
{
RectTransform rt = canvas.GetComponent<RectTransform>();
if (rt == null) return;
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
bool selected = (gizmoType & GizmoType.Selected) != 0;
Color border = selected ? Color.yellow : new Color(1, 1, 0, 0.3f);
Handles.DrawSolidRectangleWithOutline(corners, Color.clear, border);
// Label
if (selected)
{
Vector2 res = PSXCanvas.PSXResolution;
Vector3 topMid = (corners[1] + corners[2]) * 0.5f;
string label = $"PSX Canvas: {canvas.CanvasName} ({res.x}x{res.y})";
GUIStyle style = new GUIStyle(EditorStyles.boldLabel);
style.normal.textColor = Color.yellow;
Handles.Label(topMid, label, style);
}
}
}
/// <summary>
/// Custom inspector for PSXCanvas component.
/// Shows canvas name, visibility, sort order, font, and a summary of child elements.
/// </summary>
[CustomEditor(typeof(PSXCanvas))]
public class PSXCanvasEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
Vector2 res = PSXCanvas.PSXResolution;
EditorGUILayout.LabelField($"PSX Canvas ({res.x}x{res.y})", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("canvasName"), new GUIContent("Canvas Name",
"Name used from Lua: UI.FindCanvas(\"name\"). Max 24 chars."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"), new GUIContent("Start Visible",
"Whether the canvas is visible when the scene loads."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("sortOrder"), new GUIContent("Sort Order",
"Render priority (0 = back, 255 = front)."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("defaultFont"), new GUIContent("Default Font",
"Default custom font for text elements. If empty, uses built-in system font (8x16)."));
EditorGUILayout.Space(4);
// Force Canvas configuration button
if (GUILayout.Button($"Reset Canvas to {res.x}x{res.y}"))
{
PSXCanvas.InvalidateResolutionCache();
((PSXCanvas)target).ConfigureCanvas();
}
EditorGUILayout.Space(4);
// Element summary
PSXCanvas canvas = (PSXCanvas)target;
int imageCount = canvas.GetComponentsInChildren<PSXUIImage>(true).Length;
int boxCount = canvas.GetComponentsInChildren<PSXUIBox>(true).Length;
int textCount = canvas.GetComponentsInChildren<PSXUIText>(true).Length;
int progressCount = canvas.GetComponentsInChildren<PSXUIProgressBar>(true).Length;
int total = imageCount + boxCount + textCount + progressCount;
EditorGUILayout.HelpBox(
$"Elements: {total} total\n" +
$" Images: {imageCount} | Boxes: {boxCount}\n" +
$" Texts: {textCount} | Progress Bars: {progressCount}",
total > 128 ? MessageType.Warning : MessageType.Info);
if (total > 128)
EditorGUILayout.HelpBox("PS1 UI system supports max 128 elements total across all canvases.", MessageType.Error);
serializedObject.ApplyModifiedProperties();
}
}
/// <summary>
/// Custom inspector for PSXUIImage component.
/// </summary>
[CustomEditor(typeof(PSXUIImage))]
public class PSXUIImageEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("PSX UI Image", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"), new GUIContent("Element Name",
"Name used from Lua: UI.FindElement(canvas, \"name\"). Max 24 chars."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("sourceTexture"), new GUIContent("Source Texture",
"Texture to quantize and pack into VRAM."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("bitDepth"), new GUIContent("Bit Depth",
"VRAM storage depth. 4-bit = 16 colors, 8-bit = 256 colors, 16-bit = direct color."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("tintColor"), new GUIContent("Tint Color",
"Color multiply applied to the image (white = no tint)."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
// Texture size warning
PSXUIImage img = (PSXUIImage)target;
if (img.SourceTexture != null)
{
if (img.SourceTexture.width > 256 || img.SourceTexture.height > 256)
EditorGUILayout.HelpBox("Texture exceeds 256×256. It will be resized during export.", MessageType.Warning);
}
serializedObject.ApplyModifiedProperties();
}
}
/// <summary>
/// Custom inspector for PSXUIBox component.
/// </summary>
[CustomEditor(typeof(PSXUIBox))]
public class PSXUIBoxEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("PSX UI Box", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("boxColor"), new GUIContent("Box Color",
"Solid fill color rendered as a FastFill primitive."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
serializedObject.ApplyModifiedProperties();
}
}
/// <summary>
/// Custom inspector for PSXUIText component.
/// </summary>
[CustomEditor(typeof(PSXUIText))]
public class PSXUITextEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("PSX UI Text", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("defaultText"), new GUIContent("Default Text",
"Initial text content. Max 63 chars. Change at runtime via UI.SetText()."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("textColor"), new GUIContent("Text Color",
"Text render color."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("fontOverride"), new GUIContent("Font Override",
"Custom font for this text element. If empty, uses the canvas default font or built-in system font (8x16)."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
// Character count
PSXUIText txt = (PSXUIText)target;
if (!string.IsNullOrEmpty(txt.DefaultText) && txt.DefaultText.Length > 63)
EditorGUILayout.HelpBox("Text exceeds 63 characters and will be truncated.", MessageType.Warning);
// Font info
PSXFontAsset font = txt.GetEffectiveFont();
if (font != null)
{
EditorGUILayout.HelpBox(
$"Font: {font.name} ({font.GlyphWidth}x{font.GlyphHeight} glyphs)",
MessageType.Info);
}
else
{
EditorGUILayout.HelpBox("Using built-in system font (8x16 glyphs).", MessageType.Info);
}
serializedObject.ApplyModifiedProperties();
}
}
/// <summary>
/// Custom inspector for PSXUIProgressBar component.
/// </summary>
[CustomEditor(typeof(PSXUIProgressBar))]
public class PSXUIProgressBarEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("PSX UI Progress Bar", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("backgroundColor"), new GUIContent("Background Color",
"Color shown behind the fill bar."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("fillColor"), new GUIContent("Fill Color",
"Color of the progress fill."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("initialValue"), new GUIContent("Initial Value",
"Starting progress (0-100). Change via UI.SetProgress()."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
// Preview bar
Rect r = EditorGUILayout.GetControlRect(false, 16);
PSXUIProgressBar bar = (PSXUIProgressBar)target;
EditorGUI.DrawRect(r, bar.BackgroundColor);
Rect fill = r;
fill.width *= bar.InitialValue / 100f;
EditorGUI.DrawRect(fill, bar.FillColor);
serializedObject.ApplyModifiedProperties();
}
}
/// <summary>
/// Custom inspector for PSXFontAsset ScriptableObject.
/// Shows font metrics, auto-conversion from TTF/OTF, and a preview of the glyph layout.
/// </summary>
[CustomEditor(typeof(PSXFontAsset))]
public class PSXFontAssetEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("PSX Font Asset", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
// Source font (TTF/OTF)
EditorGUILayout.LabelField("Auto-Convert from Font", EditorStyles.miniBoldLabel);
EditorGUILayout.PropertyField(serializedObject.FindProperty("sourceFont"), new GUIContent("Source Font (TTF/OTF)",
"Assign a Unity Font (TrueType/OpenType). Click 'Generate Bitmap' to rasterize it."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("fontSize"), new GUIContent("Font Size",
"Pixel height for rasterization. Determines glyph cell height."));
PSXFontAsset font = (PSXFontAsset)target;
if (font.SourceFont != null)
{
if (GUILayout.Button("Generate Bitmap from Font"))
{
font.GenerateBitmapFromFont();
}
if (font.FontTexture == null)
EditorGUILayout.HelpBox("Click 'Generate Bitmap' to create the font texture.", MessageType.Info);
}
EditorGUILayout.Space(8);
// Manual bitmap
EditorGUILayout.LabelField("Manual Bitmap Source", EditorStyles.miniBoldLabel);
EditorGUILayout.PropertyField(serializedObject.FindProperty("fontTexture"), new GUIContent("Font Texture",
"256px wide bitmap. Glyphs in ASCII order from 0x20 (space). " +
"Transparent = background, opaque = foreground."));
EditorGUILayout.Space(4);
// Glyph metrics
EditorGUILayout.LabelField("Glyph Metrics", EditorStyles.miniBoldLabel);
EditorGUILayout.PropertyField(serializedObject.FindProperty("glyphWidth"), new GUIContent("Glyph Width",
"Width of each glyph cell in pixels."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("glyphHeight"), new GUIContent("Glyph Height",
"Height of each glyph cell in pixels."));
EditorGUILayout.Space(4);
int glyphsPerRow = font.GlyphsPerRow;
int rowCount = font.RowCount;
int totalH = font.TextureHeight;
int vramBytes = totalH * 128; // 128 bytes per row at 4bpp 256px
EditorGUILayout.HelpBox(
$"Layout: {glyphsPerRow} glyphs/row, {rowCount} rows\n" +
$"Texture: 256 x {totalH} pixels (4bpp)\n" +
$"VRAM: {vramBytes} bytes ({vramBytes / 1024f:F1} KB)\n" +
$"Glyph size: {font.GlyphWidth} x {font.GlyphHeight}",
MessageType.Info);
if (font.FontTexture != null)
{
if (font.FontTexture.width != 256)
EditorGUILayout.HelpBox($"Font texture must be 256 pixels wide (currently {font.FontTexture.width}).", MessageType.Error);
// Show preview
Rect previewRect = EditorGUILayout.GetControlRect(false, 64);
GUI.DrawTexture(previewRect, font.FontTexture, ScaleMode.ScaleToFit);
}
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 385b3916e29dc0e48b2866851d1fc1a9

View File

@@ -87,6 +87,8 @@ namespace SplashEdit.RuntimeCode
// Collect scene references for validation
var exporterNames = new HashSet<string>();
var audioNames = new HashSet<string>();
var canvasNames = new HashSet<string>();
var elementNames = new Dictionary<string, HashSet<string>>(); // canvas → element names
var exporters = Object.FindObjectsByType<PSXObjectExporter>(FindObjectsSortMode.None);
foreach (var e in exporters)
exporterNames.Add(e.gameObject.name);
@@ -94,6 +96,26 @@ namespace SplashEdit.RuntimeCode
foreach (var a in audioSources)
if (!string.IsNullOrEmpty(a.ClipName))
audioNames.Add(a.ClipName);
var canvases = Object.FindObjectsByType<PSXCanvas>(FindObjectsSortMode.None);
foreach (var c in canvases)
{
string cName = c.CanvasName ?? "";
if (!string.IsNullOrEmpty(cName))
{
canvasNames.Add(cName);
if (!elementNames.ContainsKey(cName))
elementNames[cName] = new HashSet<string>();
// Gather all UI element names under this canvas
foreach (var box in c.GetComponentsInChildren<PSXUIBox>())
if (!string.IsNullOrEmpty(box.ElementName)) elementNames[cName].Add(box.ElementName);
foreach (var txt in c.GetComponentsInChildren<PSXUIText>())
if (!string.IsNullOrEmpty(txt.ElementName)) elementNames[cName].Add(txt.ElementName);
foreach (var bar in c.GetComponentsInChildren<PSXUIProgressBar>())
if (!string.IsNullOrEmpty(bar.ElementName)) elementNames[cName].Add(bar.ElementName);
foreach (var img in c.GetComponentsInChildren<PSXUIImage>())
if (!string.IsNullOrEmpty(img.ElementName)) elementNames[cName].Add(img.ElementName);
}
}
// ── Tracks ──
EditorGUILayout.Space(8);
@@ -114,13 +136,39 @@ namespace SplashEdit.RuntimeCode
EditorGUILayout.EndHorizontal();
bool isCameraTrack = track.TrackType == PSXTrackType.CameraPosition || track.TrackType == PSXTrackType.CameraRotation;
EditorGUI.BeginDisabledGroup(isCameraTrack);
track.ObjectName = EditorGUILayout.TextField("Object Name", isCameraTrack ? "(camera)" : track.ObjectName);
EditorGUI.EndDisabledGroup();
bool isUITrack = track.IsUITrack;
bool isUIElementTrack = track.IsUIElementTrack;
// Validation
if (!isCameraTrack && !string.IsNullOrEmpty(track.ObjectName) && !exporterNames.Contains(track.ObjectName))
EditorGUILayout.HelpBox($"No PSXObjectExporter found for '{track.ObjectName}' in scene.", MessageType.Error);
if (isCameraTrack)
{
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.TextField("Target", "(camera)");
EditorGUI.EndDisabledGroup();
}
else if (isUITrack)
{
track.UICanvasName = EditorGUILayout.TextField("Canvas Name", track.UICanvasName);
if (!string.IsNullOrEmpty(track.UICanvasName) && !canvasNames.Contains(track.UICanvasName))
EditorGUILayout.HelpBox($"No PSXCanvas with name '{track.UICanvasName}' in scene.", MessageType.Error);
if (isUIElementTrack)
{
track.UIElementName = EditorGUILayout.TextField("Element Name", track.UIElementName);
if (!string.IsNullOrEmpty(track.UICanvasName) && !string.IsNullOrEmpty(track.UIElementName))
{
if (elementNames.TryGetValue(track.UICanvasName, out var elNames) && !elNames.Contains(track.UIElementName))
EditorGUILayout.HelpBox($"No UI element '{track.UIElementName}' found under canvas '{track.UICanvasName}'.", MessageType.Error);
}
}
}
else
{
track.ObjectName = EditorGUILayout.TextField("Object Name", track.ObjectName);
// Validation
if (!string.IsNullOrEmpty(track.ObjectName) && !exporterNames.Contains(track.ObjectName))
EditorGUILayout.HelpBox($"No PSXObjectExporter found for '{track.ObjectName}' in scene.", MessageType.Error);
}
// ── Keyframes ──
if (track.Keyframes == null) track.Keyframes = new List<PSXKeyframe>();
@@ -152,7 +200,7 @@ namespace SplashEdit.RuntimeCode
else Debug.LogWarning("No active Scene View.");
}
}
else if (track.TrackType == PSXTrackType.ObjectPosition || track.TrackType == PSXTrackType.ObjectRotationY)
else if (!isUITrack && (track.TrackType == PSXTrackType.ObjectPosition || track.TrackType == PSXTrackType.ObjectRotationY))
{
if (GUILayout.Button("From Sel.", GUILayout.Width(70)))
{
@@ -173,13 +221,46 @@ namespace SplashEdit.RuntimeCode
switch (track.TrackType)
{
case PSXTrackType.ObjectActive:
bool active = EditorGUILayout.Toggle("Active", kf.Value.x > 0.5f);
case PSXTrackType.UICanvasVisible:
case PSXTrackType.UIElementVisible:
{
string label = track.TrackType == PSXTrackType.ObjectActive ? "Active" : "Visible";
bool active = EditorGUILayout.Toggle(label, kf.Value.x > 0.5f);
kf.Value = new Vector3(active ? 1f : 0f, 0, 0);
break;
}
case PSXTrackType.ObjectRotationY:
{
float yRot = EditorGUILayout.FloatField("Y\u00b0", kf.Value.y);
kf.Value = new Vector3(0, yRot, 0);
break;
}
case PSXTrackType.UIProgress:
{
float progress = EditorGUILayout.Slider("Progress %", kf.Value.x, 0f, 100f);
kf.Value = new Vector3(progress, 0, 0);
break;
}
case PSXTrackType.UIPosition:
{
Vector2 pos = EditorGUILayout.Vector2Field("Position (px)", new Vector2(kf.Value.x, kf.Value.y));
kf.Value = new Vector3(pos.x, pos.y, 0);
break;
}
case PSXTrackType.UIColor:
{
// Show as RGB 0-255 integers
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("R", GUILayout.Width(14));
float r = EditorGUILayout.IntField(Mathf.Clamp(Mathf.RoundToInt(kf.Value.x), 0, 255), GUILayout.Width(40));
EditorGUILayout.LabelField("G", GUILayout.Width(14));
float g = EditorGUILayout.IntField(Mathf.Clamp(Mathf.RoundToInt(kf.Value.y), 0, 255), GUILayout.Width(40));
EditorGUILayout.LabelField("B", GUILayout.Width(14));
float b = EditorGUILayout.IntField(Mathf.Clamp(Mathf.RoundToInt(kf.Value.z), 0, 255), GUILayout.Width(40));
EditorGUILayout.EndHorizontal();
kf.Value = new Vector3(r, g, b);
break;
}
default:
kf.Value = EditorGUILayout.Vector3Field("Value", kf.Value);
break;
@@ -216,7 +297,7 @@ namespace SplashEdit.RuntimeCode
track.Keyframes.Add(new PSXKeyframe { Frame = frame, Value = val });
}
}
else if (track.TrackType == PSXTrackType.ObjectPosition || track.TrackType == PSXTrackType.ObjectRotationY)
else if (!isUITrack && (track.TrackType == PSXTrackType.ObjectPosition || track.TrackType == PSXTrackType.ObjectRotationY))
{
if (GUILayout.Button("+ from Selected", GUILayout.Width(120)))
{
@@ -511,6 +592,15 @@ namespace SplashEdit.RuntimeCode
if (_savedObjectActive.ContainsKey(track.ObjectName ?? ""))
initialVal = new Vector3(_savedObjectActive[track.ObjectName] ? 1f : 0f, 0, 0);
break;
// UI tracks: initial values stay zero (no scene preview state to capture)
case PSXTrackType.UICanvasVisible:
case PSXTrackType.UIElementVisible:
initialVal = new Vector3(1f, 0, 0); // assume visible by default
break;
case PSXTrackType.UIProgress:
case PSXTrackType.UIPosition:
case PSXTrackType.UIColor:
break; // zero is fine
}
Vector3 val = EvaluateTrack(track, frame, initialVal);
@@ -541,6 +631,13 @@ namespace SplashEdit.RuntimeCode
if (go != null) go.SetActive(val.x > 0.5f);
break;
}
// UI tracks: no scene preview, values are applied on PS1 only
case PSXTrackType.UICanvasVisible:
case PSXTrackType.UIElementVisible:
case PSXTrackType.UIProgress:
case PSXTrackType.UIPosition:
case PSXTrackType.UIColor:
break;
}
}
}
@@ -582,10 +679,11 @@ namespace SplashEdit.RuntimeCode
if (track.Keyframes == null || track.Keyframes.Count == 0)
return Vector3.zero;
// ObjectActive always uses step interpolation regardless of InterpMode
if (track.TrackType == PSXTrackType.ObjectActive)
// Step interpolation tracks: ObjectActive, UICanvasVisible, UIElementVisible
if (track.TrackType == PSXTrackType.ObjectActive ||
track.TrackType == PSXTrackType.UICanvasVisible ||
track.TrackType == PSXTrackType.UIElementVisible)
{
// Use initial state if before first keyframe
if (track.Keyframes.Count > 0 && track.Keyframes[0].Frame > 0 && frame < track.Keyframes[0].Frame)
return initialValue;
return EvaluateStep(track.Keyframes, frame);

View File

@@ -26,6 +26,7 @@ namespace SplashEdit.EditorCode
private Color bufferColor2 = new Color(0, 1, 0, 0.5f);
private Color prohibitedColor = new Color(1, 0, 0, 0.3f);
private PSXData _psxData;
private PSXFontData[] _cachedFonts;
private static readonly Vector2[] resolutions =
{
@@ -144,6 +145,62 @@ namespace SplashEdit.EditorCode
vramImage.SetPixel(x, VramHeight - y - 1, packed.vramPixels[x, y].GetUnityColor());
}
}
// Overlay custom font textures into the VRAM preview.
// Fonts live at x=960 (4bpp = 64 VRAM hwords wide), stacking from y=0.
PSXFontData[] fonts;
PSXUIExporter.CollectCanvases(selectedResolution, out fonts);
_cachedFonts = fonts;
if (fonts != null && fonts.Length > 0)
{
foreach (var font in fonts)
{
if (font.PixelData == null || font.PixelData.Length == 0) continue;
int vramX = font.VramX;
int vramY = font.VramY;
int texH = font.TextureHeight;
int bytesPerRow = 256 / 2; // 4bpp: 2 pixels per byte, 256 pixels wide = 128 bytes/row
// Each byte holds two 4bpp pixels. In VRAM, 4 4bpp pixels = 1 16-bit hword.
// So 256 4bpp pixels = 64 VRAM hwords.
for (int y = 0; y < texH && (vramY + y) < VramHeight; y++)
{
for (int x = 0; x < 64 && (vramX + x) < VramWidth; x++)
{
// Read 4 4bpp pixels from this VRAM hword position
int byteIdx = y * bytesPerRow + x * 2;
if (byteIdx + 1 >= font.PixelData.Length) continue;
byte b0 = font.PixelData[byteIdx];
byte b1 = font.PixelData[byteIdx + 1];
// Each byte: low nibble = first pixel, high nibble = second
// 4 pixels per hword: b0 low, b0 high, b1 low, b1 high
bool anyOpaque = ((b0 & 0x0F) | (b0 >> 4) | (b1 & 0x0F) | (b1 >> 4)) != 0;
if (anyOpaque)
{
int px = vramX + x;
int py = VramHeight - 1 - (vramY + y);
if (px < VramWidth && py >= 0)
vramImage.SetPixel(px, py, new Color(0.8f, 0.8f, 1f));
}
}
}
}
}
// Also show system font area (960, 464)-(1023, 511) = 64x48
for (int y = 464; y < 512 && y < VramHeight; y++)
{
for (int x = 960; x < 1024 && x < VramWidth; x++)
{
int py = VramHeight - 1 - y;
Color existing = vramImage.GetPixel(x, py);
if (existing.r < 0.01f && existing.g < 0.01f && existing.b < 0.01f)
vramImage.SetPixel(x, py, new Color(0.3f, 0.3f, 0.5f));
}
}
vramImage.Apply();
// Prompt the user to select a file location and save the VRAM data.
@@ -297,6 +354,24 @@ namespace SplashEdit.EditorCode
EditorGUI.DrawRect(areaRect, prohibitedColor);
}
// Draw font region overlays.
if (_cachedFonts != null)
{
Color fontColor = new Color(0.2f, 0.4f, 0.9f, 0.25f);
foreach (var font in _cachedFonts)
{
if (font.PixelData == null || font.PixelData.Length == 0) continue;
Rect fontRect = new Rect(vramRect.x + font.VramX, vramRect.y + font.VramY, 64, font.TextureHeight);
EditorGUI.DrawRect(fontRect, fontColor);
GUI.Label(new Rect(fontRect.x + 2, fontRect.y + 2, 60, 16), "Font", EditorStyles.miniLabel);
}
// System font overlay
Rect sysFontRect = new Rect(vramRect.x + 960, vramRect.y + 464, 64, 48);
EditorGUI.DrawRect(sysFontRect, new Color(0.4f, 0.2f, 0.9f, 0.25f));
GUI.Label(new Rect(sysFontRect.x + 2, sysFontRect.y + 2, 60, 16), "SysFont", EditorStyles.miniLabel);
}
GUILayout.EndHorizontal();
}
}