This commit is contained in:
Jan Racek
2026-03-27 18:31:35 +01:00
parent 1c48b8b425
commit 24d0c1fa07
27 changed files with 779 additions and 609 deletions

View File

@@ -60,6 +60,7 @@ namespace SplashEdit.EditorCode
private const int FUNC_SEEK = 0x107; private const int FUNC_SEEK = 0x107;
public bool IsRunning => _listenTask != null && !_listenTask.IsCompleted; public bool IsRunning => _listenTask != null && !_listenTask.IsCompleted;
public bool HasError { get; private set; }
public PCdrvSerialHost(string portName, int baudRate, string baseDir, Action<string> log, Action<string> psxLog = null) public PCdrvSerialHost(string portName, int baudRate, string baseDir, Action<string> log, Action<string> psxLog = null)
{ {
@@ -157,6 +158,7 @@ namespace SplashEdit.EditorCode
bool lastByteWasEscape = false; bool lastByteWasEscape = false;
var textBuffer = new StringBuilder(); var textBuffer = new StringBuilder();
int totalBytesReceived = 0; int totalBytesReceived = 0;
int consecutiveErrors = 0;
DateTime lastLogTime = DateTime.Now; DateTime lastLogTime = DateTime.Now;
_log?.Invoke("PCdrv monitor: waiting for data from PS1..."); _log?.Invoke("PCdrv monitor: waiting for data from PS1...");
@@ -179,6 +181,7 @@ namespace SplashEdit.EditorCode
} }
int b = _port.ReadByte(); int b = _port.ReadByte();
consecutiveErrors = 0;
totalBytesReceived++; totalBytesReceived++;
// Log first bytes received to help diagnose protocol issues // Log first bytes received to help diagnose protocol issues
@@ -256,8 +259,16 @@ namespace SplashEdit.EditorCode
catch (OperationCanceledException) { break; } catch (OperationCanceledException) { break; }
catch (Exception ex) catch (Exception ex)
{ {
if (!ct.IsCancellationRequested) if (ct.IsCancellationRequested) break;
_log?.Invoke($"PCdrv monitor error: {ex.Message}"); consecutiveErrors++;
_log?.Invoke($"PCdrv monitor error: {ex.Message}");
if (consecutiveErrors >= 3)
{
_log?.Invoke("PCdrv host: too many errors, connection lost. Stopping.");
HasError = true;
break;
}
Thread.Sleep(100); // Back off before retry
} }
} }
} }

View File

@@ -247,9 +247,25 @@ namespace SplashEdit.EditorCode
return tex; return tex;
} }
// Snapshot taken at the start of each OnGUI so Layout and Repaint
// events always see the same line count (prevents "Getting control
// position in a group with only N controls" errors).
private LogLine[] _snapshot = Array.Empty<LogLine>();
private void OnGUI() private void OnGUI()
{ {
EnsureStyles(); EnsureStyles();
// Take a snapshot once per OnGUI so Layout and Repaint see
// identical control counts even if background threads add lines.
if (Event.current.type == EventType.Layout)
{
lock (_lock)
{
_snapshot = _lines.ToArray();
}
}
DrawToolbar(); DrawToolbar();
DrawConsoleOutput(); DrawConsoleOutput();
} }
@@ -310,69 +326,70 @@ namespace SplashEdit.EditorCode
int selMax = Mathf.Max(_selectionAnchor, _selectionEnd); int selMax = Mathf.Max(_selectionAnchor, _selectionEnd);
bool hasSelection = _selectionAnchor >= 0 && _selectionEnd >= 0; bool hasSelection = _selectionAnchor >= 0 && _selectionEnd >= 0;
lock (_lock) // Iterate the snapshot taken during Layout so the control count
// is stable across Layout and Repaint events.
var snapshot = _snapshot;
if (snapshot.Length == 0)
{ {
if (_lines.Count == 0) GUILayout.Label("Waiting for output...", EditorStyles.centeredGreyMiniLabel);
}
for (int i = 0; i < snapshot.Length; i++)
{
var line = snapshot[i];
if (line.isError && !_showStderr) continue;
if (!line.isError && !_showStdout) continue;
if (hasFilter && line.text.ToLowerInvariant().IndexOf(filterLower, StringComparison.Ordinal) < 0)
continue;
bool selected = hasSelection && i >= selMin && i <= selMax;
GUIStyle style = selected ? _monoStyleSelected : (line.isError ? _monoStyleErr : _monoStyle);
string label = $"[{line.timestamp}] {line.text}";
GUILayout.Label(label, style);
// Handle click/right-click on last drawn rect
Rect lineRect = GUILayoutUtility.GetLastRect();
Event evt = Event.current;
if (evt.type == EventType.MouseDown && lineRect.Contains(evt.mousePosition))
{ {
GUILayout.Label("Waiting for output...", EditorStyles.centeredGreyMiniLabel); if (evt.button == 0)
}
for (int i = 0; i < _lines.Count; i++)
{
var line = _lines[i];
if (line.isError && !_showStderr) continue;
if (!line.isError && !_showStdout) continue;
if (hasFilter && line.text.ToLowerInvariant().IndexOf(filterLower, StringComparison.Ordinal) < 0)
continue;
bool selected = hasSelection && i >= selMin && i <= selMax;
GUIStyle style = selected ? _monoStyleSelected : (line.isError ? _monoStyleErr : _monoStyle);
string label = $"[{line.timestamp}] {line.text}";
GUILayout.Label(label, style);
// Handle click/right-click on last drawn rect
Rect lineRect = GUILayoutUtility.GetLastRect();
Event evt = Event.current;
if (evt.type == EventType.MouseDown && lineRect.Contains(evt.mousePosition))
{ {
if (evt.button == 0) if (evt.shift && _selectionAnchor >= 0)
_selectionEnd = i;
else
{ {
if (evt.shift && _selectionAnchor >= 0) _selectionAnchor = i;
_selectionEnd = i; _selectionEnd = i;
else
{
_selectionAnchor = i;
_selectionEnd = i;
}
evt.Use();
Repaint();
} }
else if (evt.button == 1) evt.Use();
Repaint();
}
else if (evt.button == 1)
{
int clickedLine = i;
bool lineInSelection = hasSelection && clickedLine >= selMin && clickedLine <= selMax;
var menu = new GenericMenu();
if (lineInSelection && selMin != selMax)
{ {
int clickedLine = i; menu.AddItem(new GUIContent("Copy selected lines"), false, () => CopyRange(selMin, selMax));
bool lineInSelection = hasSelection && clickedLine >= selMin && clickedLine <= selMax; menu.AddSeparator("");
var menu = new GenericMenu();
if (lineInSelection && selMin != selMax)
{
menu.AddItem(new GUIContent("Copy selected lines"), false, () => CopyRange(selMin, selMax));
menu.AddSeparator("");
}
menu.AddItem(new GUIContent("Copy this line"), false, () =>
{
string text;
lock (_lock)
{
text = clickedLine < _lines.Count
? $"[{_lines[clickedLine].timestamp}] {_lines[clickedLine].text}"
: "";
}
EditorGUIUtility.systemCopyBuffer = text;
});
menu.ShowAsContext();
evt.Use();
} }
menu.AddItem(new GUIContent("Copy this line"), false, () =>
{
string text;
lock (_lock)
{
text = clickedLine < _lines.Count
? $"[{_lines[clickedLine].timestamp}] {_lines[clickedLine].text}"
: "";
}
EditorGUIUtility.systemCopyBuffer = text;
});
menu.ShowAsContext();
evt.Use();
} }
} }
} }

View File

@@ -22,11 +22,12 @@ namespace SplashEdit.EditorCode
{ {
// ───── Constants ───── // ───── Constants ─────
private const string WINDOW_TITLE = "SplashEdit Control Panel"; private const string WINDOW_TITLE = "SplashEdit Control Panel";
private const string MENU_PATH = "PlayStation 1/SplashEdit Control Panel %#p"; private const string MENU_PATH = "PlayStation 1/SplashEdit Control Panel %#l";
// ───── UI State ───── // ───── UI State ─────
private Vector2 _scrollPos; private Vector2 _scrollPos;
private bool _showQuickStart = true; private int _selectedTab = 0;
private static readonly string[] _tabNames = { "Dependencies", "Scenes", "Build" };
private bool _showNativeProject = true; private bool _showNativeProject = true;
private bool _showToolchainSection = true; private bool _showToolchainSection = true;
private bool _showScenesSection = true; private bool _showScenesSection = true;
@@ -84,12 +85,10 @@ namespace SplashEdit.EditorCode
RefreshToolchainStatus(); RefreshToolchainStatus();
LoadSceneList(); LoadSceneList();
_manualNativePath = SplashSettings.NativeProjectPath; _manualNativePath = SplashSettings.NativeProjectPath;
EditorApplication.playModeStateChanged += OnPlayModeChanged;
} }
private void OnDisable() private void OnDisable()
{ {
EditorApplication.playModeStateChanged -= OnPlayModeChanged;
} }
private void OnFocus() private void OnFocus()
@@ -97,61 +96,40 @@ namespace SplashEdit.EditorCode
RefreshToolchainStatus(); RefreshToolchainStatus();
} }
// ═══════════════════════════════════════════════════════════════
// Play Mode Intercept
// ═══════════════════════════════════════════════════════════════
private void OnPlayModeChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.ExitingEditMode && SplashSettings.InterceptPlayMode)
{
EditorApplication.isPlaying = false;
Log("Play Mode intercepted — starting Build & Run instead.", LogType.Log);
BuildAndRun();
}
}
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
// Main GUI // Main GUI
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
private void OnGUI() private void OnGUI()
{ {
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos); if (_isRunning && _pcdrvHost != null && !_pcdrvHost.IsRunning)
{
StopAll();
Log("PCdrv host connection lost.", LogType.Warning);
}
DrawHeader(); DrawHeader();
EditorGUILayout.Space(4); EditorGUILayout.Space(4);
// Show Quick Start prominently if toolchain is not ready _selectedTab = PSXEditorStyles.DrawButtonGroup(_tabNames, _selectedTab, 28);
if (!_hasMIPS || !_hasNativeProject) EditorGUILayout.Space(4);
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
switch (_selectedTab)
{ {
DrawQuickStartSection(); case 0: // Dependencies
EditorGUILayout.Space(2); DrawToolchainSection();
EditorGUILayout.Space(2);
DrawNativeProjectSection();
break;
case 1: // Scenes
DrawScenesSection();
break;
case 2: // Build
DrawBuildSection();
break;
} }
else
{
// Collapsed quick start for experienced users
_showQuickStart = DrawSectionFoldout("Quick Start Guide", _showQuickStart);
if (_showQuickStart)
{
DrawQuickStartContent();
}
EditorGUILayout.Space(2);
}
DrawNativeProjectSection();
EditorGUILayout.Space(2);
DrawToolchainSection();
EditorGUILayout.Space(2);
DrawScenesSection();
EditorGUILayout.Space(2);
DrawVRAMSection();
EditorGUILayout.Space(2);
DrawBuildSection();
EditorGUILayout.EndScrollView(); EditorGUILayout.EndScrollView();
} }
@@ -167,19 +145,9 @@ namespace SplashEdit.EditorCode
GUILayout.Label("SplashEdit", PSXEditorStyles.WindowHeader); GUILayout.Label("SplashEdit", PSXEditorStyles.WindowHeader);
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
// Play mode intercept toggle
bool intercept = SplashSettings.InterceptPlayMode;
var toggleContent = new GUIContent(
intercept ? "▶ Intercept ON" : "▶ Intercept OFF",
"When enabled, pressing Play in Unity triggers Build & Run instead.");
bool newIntercept = GUILayout.Toggle(intercept, toggleContent, EditorStyles.toolbarButton, GUILayout.Width(120));
if (newIntercept != intercept)
SplashSettings.InterceptPlayMode = newIntercept;
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
// Status bar // Status bar
EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
{ {
string statusText; string statusText;
Color statusColor; Color statusColor;
@@ -210,90 +178,13 @@ namespace SplashEdit.EditorCode
statusColor = PSXEditorStyles.Success; statusColor = PSXEditorStyles.Success;
} }
var prevColor = GUI.contentColor; EditorGUILayout.BeginHorizontal(PSXEditorStyles.InfoBox);
GUI.contentColor = statusColor; PSXEditorStyles.DrawStatusBadge(statusColor == PSXEditorStyles.Success ? "OK" :
GUILayout.Label(statusText, EditorStyles.miniLabel); statusColor == PSXEditorStyles.Warning ? "WARN" :
GUI.contentColor = prevColor; statusColor == PSXEditorStyles.Info ? "INFO" : "RUN", statusColor, 50);
GUILayout.Label(statusText, PSXEditorStyles.RichLabel);
EditorGUILayout.EndHorizontal();
} }
EditorGUILayout.EndHorizontal();
}
// ═══════════════════════════════════════════════════════════════
// Quick Start Guide
// ═══════════════════════════════════════════════════════════════
private void DrawQuickStartSection()
{
EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle);
var prevColor = GUI.contentColor;
GUI.contentColor = PSXEditorStyles.AccentGold;
GUILayout.Label("Getting Started with SplashEdit", PSXEditorStyles.CardHeaderStyle);
GUI.contentColor = prevColor;
EditorGUILayout.Space(4);
DrawQuickStartContent();
EditorGUILayout.EndVertical();
}
private void DrawQuickStartContent()
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
DrawQuickStartStep(1, "Install Toolchain",
"Install the MIPS cross-compiler and GNU Make below.",
_hasMIPS && _hasMake);
DrawQuickStartStep(2, "Get Native Project",
"Clone the psxsplash runtime or set a path to your local copy.",
_hasNativeProject);
DrawQuickStartStep(3, "Add Scenes",
"Add Unity scenes containing a PSXSceneExporter to the scene list.",
_sceneList.Count > 0);
DrawQuickStartStep(4, "Configure VRAM",
"Set the framebuffer resolution and texture packing settings.",
true); // Always "done" since defaults are fine
DrawQuickStartStep(5, "Build & Run",
"Click BUILD & RUN to export, compile, and launch on the emulator or real hardware.",
false);
EditorGUILayout.Space(4);
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Open Documentation", EditorStyles.miniButton, GUILayout.Width(140)))
{
Application.OpenURL("https://github.com/psxsplash/splashedit");
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private void DrawQuickStartStep(int step, string title, string description, bool done)
{
EditorGUILayout.BeginHorizontal();
// Checkbox/step indicator
string prefix = done ? "✓" : $"{step}.";
var style = done ? EditorStyles.miniLabel : EditorStyles.boldLabel;
var prevColor = GUI.contentColor;
GUI.contentColor = done ? PSXEditorStyles.Success : PSXEditorStyles.TextPrimary;
GUILayout.Label(prefix, style, GUILayout.Width(20));
GUI.contentColor = prevColor;
EditorGUILayout.BeginVertical();
GUILayout.Label(title, EditorStyles.boldLabel);
GUILayout.Label(description, EditorStyles.wordWrappedMiniLabel);
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(2);
} }
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
@@ -327,7 +218,8 @@ namespace SplashEdit.EditorCode
EditorGUILayout.Space(6); EditorGUILayout.Space(6);
// ── Option 1: Auto-clone from GitHub ── // ── Option 1: Auto-clone from GitHub ──
EditorGUILayout.LabelField("Clone from GitHub", EditorStyles.boldLabel); PSXEditorStyles.DrawSeparator(4, 4);
GUILayout.Label("Clone from GitHub", PSXEditorStyles.SectionHeader);
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.Label(PSXSplashInstaller.RepoUrl, EditorStyles.miniLabel); GUILayout.Label(PSXSplashInstaller.RepoUrl, EditorStyles.miniLabel);
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
@@ -341,7 +233,7 @@ namespace SplashEdit.EditorCode
if (_isInstallingNative) if (_isInstallingNative)
{ {
EditorGUILayout.HelpBox(_nativeInstallStatus, MessageType.Info); GUILayout.Label(_nativeInstallStatus, PSXEditorStyles.InfoBox);
} }
// If already cloned, show version management // If already cloned, show version management
@@ -362,7 +254,8 @@ namespace SplashEdit.EditorCode
EditorGUILayout.Space(6); EditorGUILayout.Space(6);
// ── Option 2: Manual path ── // ── Option 2: Manual path ──
EditorGUILayout.LabelField("Or set path manually", EditorStyles.boldLabel); PSXEditorStyles.DrawSeparator(4, 4);
GUILayout.Label("Or set path manually", PSXEditorStyles.SectionHeader);
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
string newPath = EditorGUILayout.TextField(_manualNativePath); string newPath = EditorGUILayout.TextField(_manualNativePath);
@@ -389,7 +282,7 @@ namespace SplashEdit.EditorCode
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
if (!string.IsNullOrEmpty(_manualNativePath) && !manualPathValid) if (!string.IsNullOrEmpty(_manualNativePath) && !manualPathValid)
{ {
EditorGUILayout.HelpBox("Invalid path. The directory must contain a Makefile.", MessageType.Warning); GUILayout.Label("Invalid path. The directory must contain a Makefile.", PSXEditorStyles.InfoBox);
} }
else if (manualPathValid && _manualNativePath != SplashSettings.NativeProjectPath) else if (manualPathValid && _manualNativePath != SplashSettings.NativeProjectPath)
{ {
@@ -431,15 +324,17 @@ namespace SplashEdit.EditorCode
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
if (!_hasMIPS) if (!_hasMIPS)
{ {
if (GUILayout.Button("Install", GUILayout.Width(60))) if (GUILayout.Button("Install", PSXEditorStyles.SecondaryButton, GUILayout.Width(70)))
InstallMIPS(); InstallMIPS();
} }
else else
{ {
GUILayout.Label("Ready", EditorStyles.miniLabel); PSXEditorStyles.DrawStatusBadge("Ready", PSXEditorStyles.Success);
} }
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
PSXEditorStyles.DrawSeparator(2, 2);
// GNU Make // GNU Make
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
DrawStatusIcon(_hasMake); DrawStatusIcon(_hasMake);
@@ -447,15 +342,17 @@ namespace SplashEdit.EditorCode
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
if (!_hasMake) if (!_hasMake)
{ {
if (GUILayout.Button("Install", GUILayout.Width(60))) if (GUILayout.Button("Install", PSXEditorStyles.SecondaryButton, GUILayout.Width(70)))
InstallMake(); InstallMake();
} }
else else
{ {
GUILayout.Label("Ready", EditorStyles.miniLabel); PSXEditorStyles.DrawStatusBadge("Ready", PSXEditorStyles.Success);
} }
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
PSXEditorStyles.DrawSeparator(2, 2);
// PCSX-Redux // PCSX-Redux
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
DrawStatusIcon(_hasRedux); DrawStatusIcon(_hasRedux);
@@ -463,15 +360,17 @@ namespace SplashEdit.EditorCode
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
if (!_hasRedux) if (!_hasRedux)
{ {
if (GUILayout.Button("Download", GUILayout.Width(80))) if (GUILayout.Button("Download", PSXEditorStyles.SecondaryButton, GUILayout.Width(80)))
DownloadRedux(); DownloadRedux();
} }
else else
{ {
GUILayout.Label(_reduxVersion, EditorStyles.miniLabel); PSXEditorStyles.DrawStatusBadge(_reduxVersion, PSXEditorStyles.Success);
} }
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
PSXEditorStyles.DrawSeparator(2, 2);
// psxavenc (audio encoder) // psxavenc (audio encoder)
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
DrawStatusIcon(_hasPsxavenc); DrawStatusIcon(_hasPsxavenc);
@@ -479,15 +378,17 @@ namespace SplashEdit.EditorCode
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
if (!_hasPsxavenc) if (!_hasPsxavenc)
{ {
if (GUILayout.Button("Download", GUILayout.Width(80))) if (GUILayout.Button("Download", PSXEditorStyles.SecondaryButton, GUILayout.Width(80)))
DownloadPsxavenc(); DownloadPsxavenc();
} }
else else
{ {
GUILayout.Label("Installed", EditorStyles.miniLabel); PSXEditorStyles.DrawStatusBadge("Installed", PSXEditorStyles.Success);
} }
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
PSXEditorStyles.DrawSeparator(2, 2);
// mkpsxiso (ISO builder) // mkpsxiso (ISO builder)
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
DrawStatusIcon(_hasMkpsxiso); DrawStatusIcon(_hasMkpsxiso);
@@ -495,12 +396,12 @@ namespace SplashEdit.EditorCode
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
if (!_hasMkpsxiso) if (!_hasMkpsxiso)
{ {
if (GUILayout.Button("Download", GUILayout.Width(80))) if (GUILayout.Button("Download", PSXEditorStyles.SecondaryButton, GUILayout.Width(80)))
DownloadMkpsxiso(); DownloadMkpsxiso();
} }
else else
{ {
GUILayout.Label("Installed", EditorStyles.miniLabel); PSXEditorStyles.DrawStatusBadge("Installed", PSXEditorStyles.Success);
} }
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
@@ -528,11 +429,11 @@ namespace SplashEdit.EditorCode
if (_sceneList.Count == 0) if (_sceneList.Count == 0)
{ {
EditorGUILayout.HelpBox( GUILayout.Label(
"No scenes added yet.\n" + "No scenes added yet.\n" +
"Each scene needs a GameObject with a PSXSceneExporter component.\n" + "Each scene needs a GameObject with a PSXSceneExporter component.\n" +
"Drag scene assets here, or use the buttons below to add them.", "Drag scene assets here, or use the buttons below to add them.",
MessageType.Info); PSXEditorStyles.InfoBox);
} }
// Draw scene list // Draw scene list
@@ -640,49 +541,19 @@ namespace SplashEdit.EditorCode
EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle); EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle);
// Resolution settings // Framebuffer: hardcoded 320x240, vertical, dual-buffered
EditorGUILayout.LabelField("Framebuffer Settings", EditorStyles.boldLabel); GUILayout.Label("Framebuffer", PSXEditorStyles.SectionHeader);
GUILayout.Label("Resolution: 320x240 (dual-buffered, vertical layout)", PSXEditorStyles.InfoBox);
PSXEditorStyles.DrawSeparator(4, 4);
GUILayout.Label("Advanced Tools", PSXEditorStyles.SectionHeader);
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.Label("Resolution:", GUILayout.Width(75)); if (GUILayout.Button("Open VRAM Editor", PSXEditorStyles.PrimaryButton, GUILayout.Height(26)))
SplashSettings.ResolutionWidth = EditorGUILayout.IntField(SplashSettings.ResolutionWidth, GUILayout.Width(50));
GUILayout.Label("×", GUILayout.Width(14));
SplashSettings.ResolutionHeight = EditorGUILayout.IntField(SplashSettings.ResolutionHeight, GUILayout.Width(50));
// Common resolutions dropdown
if (GUILayout.Button("Presets", EditorStyles.miniButton, GUILayout.Width(55)))
{
var menu = new GenericMenu();
menu.AddItem(new GUIContent("256×240"), false, () => { SplashSettings.ResolutionWidth = 256; SplashSettings.ResolutionHeight = 240; Repaint(); });
menu.AddItem(new GUIContent("320×240"), false, () => { SplashSettings.ResolutionWidth = 320; SplashSettings.ResolutionHeight = 240; Repaint(); });
menu.AddItem(new GUIContent("368×240"), false, () => { SplashSettings.ResolutionWidth = 368; SplashSettings.ResolutionHeight = 240; Repaint(); });
menu.AddItem(new GUIContent("512×240"), false, () => { SplashSettings.ResolutionWidth = 512; SplashSettings.ResolutionHeight = 240; Repaint(); });
menu.AddItem(new GUIContent("640×240"), false, () => { SplashSettings.ResolutionWidth = 640; SplashSettings.ResolutionHeight = 240; Repaint(); });
menu.AddItem(new GUIContent("256×480"), false, () => { SplashSettings.ResolutionWidth = 256; SplashSettings.ResolutionHeight = 480; Repaint(); });
menu.AddItem(new GUIContent("320×480"), false, () => { SplashSettings.ResolutionWidth = 320; SplashSettings.ResolutionHeight = 480; Repaint(); });
menu.AddItem(new GUIContent("512×480"), false, () => { SplashSettings.ResolutionWidth = 512; SplashSettings.ResolutionHeight = 480; Repaint(); });
menu.ShowAsContext();
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
SplashSettings.DualBuffering = EditorGUILayout.Toggle("Dual Buffering", SplashSettings.DualBuffering);
SplashSettings.VerticalLayout = EditorGUILayout.Toggle("Vertical Layout", SplashSettings.VerticalLayout);
EditorGUILayout.Space(4);
EditorGUILayout.LabelField("Export Settings", EditorStyles.boldLabel);
SplashSettings.DefaultGTEScaling = EditorGUILayout.FloatField("Default GTE Scaling", SplashSettings.DefaultGTEScaling);
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("Advanced Tools", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Open VRAM Editor", GUILayout.Height(24)))
{ {
VRAMEditorWindow.ShowWindow(); VRAMEditorWindow.ShowWindow();
} }
if (GUILayout.Button("Quantized Preview", GUILayout.Height(24))) if (GUILayout.Button("Quantized Preview", PSXEditorStyles.PrimaryButton, GUILayout.Height(26)))
{ {
QuantizedPreviewWindow.ShowWindow(); QuantizedPreviewWindow.ShowWindow();
} }
@@ -703,6 +574,7 @@ namespace SplashEdit.EditorCode
EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle); EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle);
// Target & Mode // Target & Mode
GUILayout.Label("Configuration", PSXEditorStyles.SectionHeader);
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.Label("Target:", GUILayout.Width(50)); GUILayout.Label("Target:", GUILayout.Width(50));
SplashSettings.Target = (BuildTarget)EditorGUILayout.EnumPopup(SplashSettings.Target); SplashSettings.Target = (BuildTarget)EditorGUILayout.EnumPopup(SplashSettings.Target);
@@ -710,6 +582,9 @@ namespace SplashEdit.EditorCode
SplashSettings.Mode = (BuildMode)EditorGUILayout.EnumPopup(SplashSettings.Mode); SplashSettings.Mode = (BuildMode)EditorGUILayout.EnumPopup(SplashSettings.Mode);
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
// Clean Build toggle
SplashSettings.CleanBuild = EditorGUILayout.Toggle("Clean Build", SplashSettings.CleanBuild);
// Serial port (only for Real Hardware) // Serial port (only for Real Hardware)
if (SplashSettings.Target == BuildTarget.RealHardware) if (SplashSettings.Target == BuildTarget.RealHardware)
{ {
@@ -749,53 +624,55 @@ namespace SplashEdit.EditorCode
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
} }
EditorGUILayout.Space(8); PSXEditorStyles.DrawSeparator(6, 6);
// Big Build & Run button // Big Build & Run button
EditorGUI.BeginDisabledGroup(_isBuilding); EditorGUI.BeginDisabledGroup(_isBuilding);
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
var buildColor = GUI.backgroundColor; var largeBuildButton = new GUIStyle(PSXEditorStyles.SuccessButton)
GUI.backgroundColor = _isBuilding ? Color.gray : new Color(0.3f, 0.8f, 0.4f); {
fontSize = 14,
fontStyle = FontStyle.Bold,
padding = new RectOffset(20, 20, 10, 10)
};
string buttonLabel = _isBuilding ? "Building..." : "BUILD & RUN"; string buttonLabel = _isBuilding ? "Building..." :
if (GUILayout.Button(buttonLabel, GUILayout.Width(200), GUILayout.Height(36))) (SplashSettings.Target == BuildTarget.ISO ? "BUILD" : "BUILD & RUN");
if (GUILayout.Button(buttonLabel, largeBuildButton, GUILayout.Width(200), GUILayout.Height(38)))
{ {
BuildAndRun(); BuildAndRun();
} }
GUI.backgroundColor = buildColor;
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
EditorGUI.EndDisabledGroup(); EditorGUI.EndDisabledGroup();
// Stop button (if running emulator or hardware PCdrv host) // Stop button (if running - emulator or hardware PCdrv host)
if (_isRunning) if (_isRunning)
{ {
EditorGUILayout.Space(4);
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
GUI.backgroundColor = new Color(0.9f, 0.3f, 0.3f); string stopLabel = _emulatorProcess != null ? "STOP EMULATOR" : "STOP PCdrv HOST";
string stopLabel = _emulatorProcess != null ? "■ STOP EMULATOR" : "■ STOP PCdrv HOST"; if (GUILayout.Button(stopLabel, PSXEditorStyles.DangerButton, GUILayout.Width(200), GUILayout.Height(26)))
if (GUILayout.Button(stopLabel, GUILayout.Width(200), GUILayout.Height(24)))
{ {
StopAll(); StopAll();
} }
GUI.backgroundColor = buildColor;
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
} }
// Export-only / Compile-only // Export-only / Compile-only
EditorGUILayout.Space(4); PSXEditorStyles.DrawSeparator(4, 4);
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
if (GUILayout.Button("Export Only", EditorStyles.miniButton, GUILayout.Width(100))) if (GUILayout.Button("Export Only", PSXEditorStyles.SecondaryButton, GUILayout.Width(100)))
{ {
ExportAllScenes(); ExportAllScenes();
} }
if (GUILayout.Button("Compile Only", EditorStyles.miniButton, GUILayout.Width(100))) if (GUILayout.Button("Compile Only", PSXEditorStyles.SecondaryButton, GUILayout.Width(100)))
{ {
CompileOnly(); CompileOnly();
} }
@@ -815,6 +692,20 @@ namespace SplashEdit.EditorCode
public async void BuildAndRun() public async void BuildAndRun()
{ {
if (_isBuilding) return; if (_isBuilding) return;
if (UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().isDirty)
{
int choice = EditorUtility.DisplayDialogComplex(
"Unsaved Changes",
"The current scene has unsaved changes. Save before building?",
"Save and Build", // 0
"Cancel", // 1
"Build Without Saving" // 2
);
if (choice == 1) return; // Cancel
if (choice == 0) EditorSceneManager.SaveOpenScenes();
}
_isBuilding = true; _isBuilding = true;
var console = EditorWindow.GetWindow<PSXConsoleWindow>(); var console = EditorWindow.GetWindow<PSXConsoleWindow>();
@@ -1126,15 +1017,17 @@ namespace SplashEdit.EditorCode
if (SplashSettings.Target == BuildTarget.ISO) if (SplashSettings.Target == BuildTarget.ISO)
buildArg += " LOADER=cdrom"; buildArg += " LOADER=cdrom";
string makeCmd = $"make clean && make all -j{SystemInfo.processorCount} {buildArg}".Trim(); int jobCount = Math.Max(1, SystemInfo.processorCount - 1);
string cleanPrefix = SplashSettings.CleanBuild ? "make clean && " : "";
string makeCmd = $"{cleanPrefix}make all -j{jobCount} {buildArg}".Trim();
Log($"Running: {makeCmd}", LogType.Log); Log($"Running: {makeCmd}", LogType.Log);
var psi = new ProcessStartInfo var psi = new ProcessStartInfo
{ {
FileName = Application.platform == RuntimePlatform.WindowsEditor ? "cmd.exe" : "/bin/bash", FileName = Application.platform == RuntimePlatform.WindowsEditor ? "cmd.exe" : "/bin/bash",
Arguments = Application.platform == RuntimePlatform.WindowsEditor Arguments = Application.platform == RuntimePlatform.WindowsEditor
? $"/c {makeCmd}" ? $"/c \"cd /d \"{nativeDir}\" && {makeCmd}\""
: $"-c \"{makeCmd}\"", : $"-c \"cd \\\"{nativeDir}\\\" && {makeCmd}\"",
WorkingDirectory = nativeDir, WorkingDirectory = nativeDir,
RedirectStandardOutput = true, RedirectStandardOutput = true,
RedirectStandardError = true, RedirectStandardError = true,
@@ -1269,7 +1162,7 @@ namespace SplashEdit.EditorCode
StopAllQuiet(); StopAllQuiet();
string pcdrvBase = SplashBuildPaths.BuildOutputDir; string pcdrvBase = SplashBuildPaths.BuildOutputDir;
string args = $"-exe \"{exePath}\" -run -fastboot -pcdrv -pcdrvbase \"{pcdrvBase}\" -stdout"; string args = $"-exe \"{exePath}\" -run -fastboot -pcdrv -pcdrvbase \"{pcdrvBase}\" -pad1type dualshock -stdout";
Log($"Launching: {Path.GetFileName(reduxPath)} {args}", LogType.Log); Log($"Launching: {Path.GetFileName(reduxPath)} {args}", LogType.Log);
@@ -1289,6 +1182,15 @@ namespace SplashEdit.EditorCode
try try
{ {
_emulatorProcess = Process.Start(psi); _emulatorProcess = Process.Start(psi);
_emulatorProcess.EnableRaisingEvents = true;
_emulatorProcess.Exited += (s, e) => {
EditorApplication.delayCall += () => {
_isRunning = false;
_emulatorProcess = null;
PSXConsoleWindow.Detach();
Repaint();
};
};
_isRunning = true; _isRunning = true;
Log("PCSX-Redux launched.", LogType.Log); Log("PCSX-Redux launched.", LogType.Log);

View File

@@ -92,29 +92,36 @@ namespace SplashEdit.EditorCode
set => EditorPrefs.SetInt(Prefix + "SerialBaudRate", value); set => EditorPrefs.SetInt(Prefix + "SerialBaudRate", value);
} }
// --- VRAM Layout --- // --- VRAM Layout (hardcoded 320x240, dual-buffered, vertical) ---
public static int ResolutionWidth public static int ResolutionWidth
{ {
get => EditorPrefs.GetInt(Prefix + "ResWidth", 320); get => 320;
set => EditorPrefs.SetInt(Prefix + "ResWidth", value); set { } // no-op, hardcoded
} }
public static int ResolutionHeight public static int ResolutionHeight
{ {
get => EditorPrefs.GetInt(Prefix + "ResHeight", 240); get => 240;
set => EditorPrefs.SetInt(Prefix + "ResHeight", value); set { } // no-op, hardcoded
} }
public static bool DualBuffering public static bool DualBuffering
{ {
get => EditorPrefs.GetBool(Prefix + "DualBuffering", true); get => true;
set => EditorPrefs.SetBool(Prefix + "DualBuffering", value); set { } // no-op, hardcoded
} }
public static bool VerticalLayout public static bool VerticalLayout
{ {
get => EditorPrefs.GetBool(Prefix + "VerticalLayout", true); get => true;
set => EditorPrefs.SetBool(Prefix + "VerticalLayout", value); set { } // no-op, hardcoded
}
// --- Clean Build ---
public static bool CleanBuild
{
get => EditorPrefs.GetBool(Prefix + "CleanBuild", true);
set => EditorPrefs.SetBool(Prefix + "CleanBuild", value);
} }
// --- Export settings --- // --- Export settings ---
@@ -124,13 +131,6 @@ namespace SplashEdit.EditorCode
set => EditorPrefs.SetFloat(Prefix + "GTEScaling", value); set => EditorPrefs.SetFloat(Prefix + "GTEScaling", value);
} }
// --- Play Mode Intercept ---
public static bool InterceptPlayMode
{
get => EditorPrefs.GetBool(Prefix + "InterceptPlayMode", false);
set => EditorPrefs.SetBool(Prefix + "InterceptPlayMode", value);
}
// --- ISO Build --- // --- ISO Build ---
/// <summary> /// <summary>
/// Optional path to a Sony license file (.dat) for the ISO image. /// Optional path to a Sony license file (.dat) for the ISO image.
@@ -162,7 +162,7 @@ namespace SplashEdit.EditorCode
"Target", "Mode", "NativeProjectPath", "MIPSToolchainPath", "Target", "Mode", "NativeProjectPath", "MIPSToolchainPath",
"PCSXReduxPath", "PCSXReduxPCdrvBase", "SerialPort", "SerialBaudRate", "PCSXReduxPath", "PCSXReduxPCdrvBase", "SerialPort", "SerialBaudRate",
"ResWidth", "ResHeight", "DualBuffering", "VerticalLayout", "ResWidth", "ResHeight", "DualBuffering", "VerticalLayout",
"GTEScaling", "AutoValidate", "InterceptPlayMode", "GTEScaling", "AutoValidate",
"LicenseFilePath", "ISOVolumeLabel" "LicenseFilePath", "ISOVolumeLabel"
}; };

View File

@@ -42,15 +42,23 @@ namespace SplashEdit.EditorCode
{ {
serializedObject.Update(); serializedObject.Update();
DrawHeader(); // Header card
PSXEditorStyles.BeginCard();
EditorGUILayout.BeginHorizontal();
GUILayout.Label(EditorGUIUtility.IconContent("d_Selectable Icon"), GUILayout.Width(30), GUILayout.Height(30));
EditorGUILayout.BeginVertical();
EditorGUILayout.LabelField("PSX Interactable", PSXEditorStyles.CardHeaderStyle);
EditorGUILayout.LabelField("Player interaction trigger for PS1", PSXEditorStyles.RichLabel);
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
PSXEditorStyles.EndCard();
EditorGUILayout.Space(5); EditorGUILayout.Space(4);
_interactionFoldout = DrawFoldoutSection("Interaction Settings", _interactionFoldout, () => _interactionFoldout = PSXEditorStyles.DrawFoldoutCard("Interaction Settings", _interactionFoldout, () =>
{ {
EditorGUILayout.PropertyField(_interactionRadius); EditorGUILayout.PropertyField(_interactionRadius);
// Button selector with visual
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("Interact Button"); EditorGUILayout.PrefixLabel("Interact Button");
_interactButton.intValue = EditorGUILayout.Popup(_interactButton.intValue, ButtonNames); _interactButton.intValue = EditorGUILayout.Popup(_interactButton.intValue, ButtonNames);
@@ -63,74 +71,32 @@ namespace SplashEdit.EditorCode
EditorGUI.indentLevel++; EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_cooldownFrames, new GUIContent("Cooldown (frames)")); EditorGUILayout.PropertyField(_cooldownFrames, new GUIContent("Cooldown (frames)"));
// Show cooldown in seconds
float seconds = _cooldownFrames.intValue / 60f; float seconds = _cooldownFrames.intValue / 60f;
EditorGUILayout.LabelField($" {seconds:F2} seconds at 60fps", EditorStyles.miniLabel); EditorGUILayout.LabelField($"~ {seconds:F2} seconds at 60fps", EditorStyles.miniLabel);
EditorGUI.indentLevel--; EditorGUI.indentLevel--;
} }
EditorGUILayout.PropertyField(_showPrompt); EditorGUILayout.PropertyField(_showPrompt);
}); });
_advancedFoldout = DrawFoldoutSection("Advanced", _advancedFoldout, () => EditorGUILayout.Space(2);
_advancedFoldout = PSXEditorStyles.DrawFoldoutCard("Advanced", _advancedFoldout, () =>
{ {
EditorGUILayout.PropertyField(_requireLineOfSight); EditorGUILayout.PropertyField(_requireLineOfSight);
EditorGUILayout.PropertyField(_interactionOffset); EditorGUILayout.PropertyField(_interactionOffset);
}); });
DrawLuaEventsInfo(new[] { "onInteract" }); EditorGUILayout.Space(4);
// Lua events card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("Lua Events", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.DrawSeparator(2, 4);
EditorGUILayout.LabelField("onInteract", PSXEditorStyles.RichLabel);
PSXEditorStyles.EndCard();
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
} }
private void DrawHeader()
{
EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
GUILayout.Label(EditorGUIUtility.IconContent("d_Selectable Icon"), GUILayout.Width(30), GUILayout.Height(30));
EditorGUILayout.BeginVertical();
GUILayout.Label("PSX Interactable", EditorStyles.boldLabel);
GUILayout.Label("Player interaction trigger for PS1", EditorStyles.miniLabel);
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
}
private bool DrawFoldoutSection(string title, bool isExpanded, System.Action drawContent)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
isExpanded = EditorGUILayout.Foldout(isExpanded, title, true, EditorStyles.foldoutHeader);
if (isExpanded)
{
EditorGUI.indentLevel++;
drawContent?.Invoke();
EditorGUI.indentLevel--;
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(3);
return isExpanded;
}
private void DrawLuaEventsInfo(string[] events)
{
EditorGUILayout.Space(5);
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label("Lua Events", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
foreach (var evt in events)
{
GUILayout.Label($"• {evt}", EditorStyles.miniLabel);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
} }
} }

View File

@@ -0,0 +1,268 @@
using UnityEngine;
using UnityEditor;
using SplashEdit.RuntimeCode;
namespace SplashEdit.EditorCode
{
/// <summary>
/// Custom inspector for PSXAudioClip component.
/// </summary>
[CustomEditor(typeof(PSXAudioClip))]
public class PSXAudioClipEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
// Header card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("PSX Audio Clip", PSXEditorStyles.CardHeaderStyle);
PSXAudioClip audioClip = (PSXAudioClip)target;
EditorGUILayout.BeginHorizontal();
if (audioClip.Clip != null)
PSXEditorStyles.DrawStatusBadge("Clip Set", PSXEditorStyles.Success, 70);
else
PSXEditorStyles.DrawStatusBadge("No Clip", PSXEditorStyles.Warning, 70);
if (audioClip.Loop)
PSXEditorStyles.DrawStatusBadge("Loop", PSXEditorStyles.AccentCyan, 50);
EditorGUILayout.EndHorizontal();
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4);
// Properties card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("Clip Settings", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.DrawSeparator(2, 4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("ClipName"), new GUIContent("Clip Name",
"Name used to identify this clip in Lua (Audio.Play(\"name\"))."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("Clip"), new GUIContent("Audio Clip",
"Unity AudioClip to convert to PS1 SPU ADPCM format."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("SampleRate"), new GUIContent("Sample Rate",
"Target sample rate for the PS1 (lower = smaller, max 44100)."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("Loop"), new GUIContent("Loop",
"Whether this clip should loop when played."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("DefaultVolume"), new GUIContent("Volume",
"Default playback volume (0-127)."));
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4);
// Info card
if (audioClip.Clip != null)
{
PSXEditorStyles.BeginCard();
float duration = audioClip.Clip.length;
int srcRate = audioClip.Clip.frequency;
EditorGUILayout.LabelField(
$"Source: {srcRate} Hz, {duration:F2}s, {audioClip.Clip.channels}ch\n" +
$"Target: {audioClip.SampleRate} Hz SPU ADPCM",
PSXEditorStyles.InfoBox);
PSXEditorStyles.EndCard();
}
serializedObject.ApplyModifiedProperties();
}
}
/// <summary>
/// Custom inspector for PSXPlayer component.
/// </summary>
[CustomEditor(typeof(PSXPlayer))]
public class PSXPlayerEditor : Editor
{
private bool _dimensionsFoldout = true;
private bool _movementFoldout = true;
private bool _navigationFoldout = true;
private bool _physicsFoldout = true;
public override void OnInspectorGUI()
{
serializedObject.Update();
// Header card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("PSX Player", PSXEditorStyles.CardHeaderStyle);
EditorGUILayout.LabelField("First-person player controller for PS1", PSXEditorStyles.RichLabel);
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4);
// Dimensions
_dimensionsFoldout = PSXEditorStyles.DrawFoldoutCard("Player Dimensions", _dimensionsFoldout, () =>
{
EditorGUILayout.PropertyField(serializedObject.FindProperty("playerHeight"), new GUIContent("Height",
"Camera eye height above the player's feet."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("playerRadius"), new GUIContent("Radius",
"Collision radius for wall sliding."));
});
EditorGUILayout.Space(2);
// Movement
_movementFoldout = PSXEditorStyles.DrawFoldoutCard("Movement", _movementFoldout, () =>
{
EditorGUILayout.PropertyField(serializedObject.FindProperty("moveSpeed"), new GUIContent("Walk Speed",
"Walk speed in world units per second."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("sprintSpeed"), new GUIContent("Sprint Speed",
"Sprint speed in world units per second."));
});
EditorGUILayout.Space(2);
// Navigation
_navigationFoldout = PSXEditorStyles.DrawFoldoutCard("Navigation", _navigationFoldout, () =>
{
EditorGUILayout.PropertyField(serializedObject.FindProperty("maxStepHeight"), new GUIContent("Max Step Height",
"Maximum height the agent can step up."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("walkableSlopeAngle"), new GUIContent("Walkable Slope",
"Maximum walkable slope angle in degrees."));
PSXEditorStyles.DrawSeparator(4, 4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("navCellSize"), new GUIContent("Cell Size (XZ)",
"Voxel size in XZ plane (smaller = more accurate but slower)."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("navCellHeight"), new GUIContent("Cell Height",
"Voxel height (smaller = more accurate vertical resolution)."));
});
EditorGUILayout.Space(2);
// Jump & Gravity
_physicsFoldout = PSXEditorStyles.DrawFoldoutCard("Jump & Gravity", _physicsFoldout, () =>
{
EditorGUILayout.PropertyField(serializedObject.FindProperty("jumpHeight"), new GUIContent("Jump Height",
"Peak jump height in world units."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("gravity"), new GUIContent("Gravity",
"Downward acceleration in world units per second squared."));
});
serializedObject.ApplyModifiedProperties();
}
}
/// <summary>
/// Custom inspector for PSXPortalLink component.
/// </summary>
[CustomEditor(typeof(PSXPortalLink))]
public class PSXPortalLinkEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
PSXPortalLink portal = (PSXPortalLink)target;
// Header card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("PSX Portal Link", PSXEditorStyles.CardHeaderStyle);
EditorGUILayout.BeginHorizontal();
bool valid = portal.RoomA != null && portal.RoomB != null && portal.RoomA != portal.RoomB;
if (valid)
PSXEditorStyles.DrawStatusBadge("Valid", PSXEditorStyles.Success, 55);
else
PSXEditorStyles.DrawStatusBadge("Invalid", PSXEditorStyles.Error, 60);
EditorGUILayout.EndHorizontal();
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4);
// Room references card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("Connected Rooms", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.DrawSeparator(2, 4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("RoomA"), new GUIContent("Room A",
"First room connected by this portal."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("RoomB"), new GUIContent("Room B",
"Second room connected by this portal."));
// Validation warnings
if (portal.RoomA == null || portal.RoomB == null)
{
EditorGUILayout.Space(4);
EditorGUILayout.LabelField("Both Room A and Room B must be assigned for export.", PSXEditorStyles.InfoBox);
}
else if (portal.RoomA == portal.RoomB)
{
EditorGUILayout.Space(4);
EditorGUILayout.LabelField("Room A and Room B must be different rooms.", PSXEditorStyles.InfoBox);
}
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4);
// Portal size card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("Portal Dimensions", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.DrawSeparator(2, 4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("PortalSize"), new GUIContent("Size (W, H)",
"Size of the portal opening (width, height) in world units."));
PSXEditorStyles.EndCard();
serializedObject.ApplyModifiedProperties();
}
}
/// <summary>
/// Custom inspector for PSXRoom component.
/// </summary>
[CustomEditor(typeof(PSXRoom))]
public class PSXRoomEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
PSXRoom room = (PSXRoom)target;
// Header card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("PSX Room", PSXEditorStyles.CardHeaderStyle);
if (!string.IsNullOrEmpty(room.RoomName))
EditorGUILayout.LabelField(room.RoomName, PSXEditorStyles.RichLabel);
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4);
// Properties card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("Room Settings", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.DrawSeparator(2, 4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("RoomName"), new GUIContent("Room Name",
"Optional display name for this room (used in editor gizmos)."));
PSXEditorStyles.DrawSeparator(4, 4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("VolumeSize"), new GUIContent("Volume Size",
"Size of the room volume in local space."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("VolumeOffset"), new GUIContent("Volume Offset",
"Offset of the volume center relative to the transform position."));
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4);
// Info card
PSXEditorStyles.BeginCard();
Bounds wb = room.GetWorldBounds();
Vector3 size = wb.size;
EditorGUILayout.LabelField(
$"World bounds: {size.x:F1} x {size.y:F1} x {size.z:F1}",
PSXEditorStyles.InfoBox);
PSXEditorStyles.EndCard();
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3fd7a7bcc7d0ff841b158f2744d48010

View File

@@ -232,9 +232,16 @@ namespace SplashEdit.EditorCode
serializedObject.Update(); serializedObject.Update();
Vector2 res = PSXCanvas.PSXResolution; Vector2 res = PSXCanvas.PSXResolution;
EditorGUILayout.LabelField($"PSX Canvas ({res.x}x{res.y})", EditorStyles.boldLabel);
// Header card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField($"PSX Canvas ({res.x}x{res.y})", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4); EditorGUILayout.Space(4);
// Properties card
PSXEditorStyles.BeginCard();
EditorGUILayout.PropertyField(serializedObject.FindProperty("canvasName"), new GUIContent("Canvas Name", EditorGUILayout.PropertyField(serializedObject.FindProperty("canvasName"), new GUIContent("Canvas Name",
"Name used from Lua: UI.FindCanvas(\"name\"). Max 24 chars.")); "Name used from Lua: UI.FindCanvas(\"name\"). Max 24 chars."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"), new GUIContent("Start Visible", EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"), new GUIContent("Start Visible",
@@ -244,18 +251,18 @@ namespace SplashEdit.EditorCode
EditorGUILayout.PropertyField(serializedObject.FindProperty("defaultFont"), new GUIContent("Default Font", EditorGUILayout.PropertyField(serializedObject.FindProperty("defaultFont"), new GUIContent("Default Font",
"Default custom font for text elements. If empty, uses built-in system font (8x16).")); "Default custom font for text elements. If empty, uses built-in system font (8x16)."));
EditorGUILayout.Space(4); PSXEditorStyles.DrawSeparator(6, 6);
// Force Canvas configuration button if (GUILayout.Button($"Reset Canvas to {res.x}x{res.y}", PSXEditorStyles.SecondaryButton))
if (GUILayout.Button($"Reset Canvas to {res.x}x{res.y}"))
{ {
PSXCanvas.InvalidateResolutionCache(); PSXCanvas.InvalidateResolutionCache();
((PSXCanvas)target).ConfigureCanvas(); ((PSXCanvas)target).ConfigureCanvas();
} }
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4); EditorGUILayout.Space(4);
// Element summary // Element summary card
PSXCanvas canvas = (PSXCanvas)target; PSXCanvas canvas = (PSXCanvas)target;
int imageCount = canvas.GetComponentsInChildren<PSXUIImage>(true).Length; int imageCount = canvas.GetComponentsInChildren<PSXUIImage>(true).Length;
int boxCount = canvas.GetComponentsInChildren<PSXUIBox>(true).Length; int boxCount = canvas.GetComponentsInChildren<PSXUIBox>(true).Length;
@@ -263,14 +270,16 @@ namespace SplashEdit.EditorCode
int progressCount = canvas.GetComponentsInChildren<PSXUIProgressBar>(true).Length; int progressCount = canvas.GetComponentsInChildren<PSXUIProgressBar>(true).Length;
int total = imageCount + boxCount + textCount + progressCount; int total = imageCount + boxCount + textCount + progressCount;
EditorGUILayout.HelpBox( PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField(
$"Elements: {total} total\n" + $"Elements: {total} total\n" +
$" Images: {imageCount} | Boxes: {boxCount}\n" + $" Images: {imageCount} | Boxes: {boxCount}\n" +
$" Texts: {textCount} | Progress Bars: {progressCount}", $" Texts: {textCount} | Progress Bars: {progressCount}",
total > 128 ? MessageType.Warning : MessageType.Info); PSXEditorStyles.InfoBox);
if (total > 128) if (total > 128)
EditorGUILayout.HelpBox("PS1 UI system supports max 128 elements total across all canvases.", MessageType.Error); EditorGUILayout.LabelField("PS1 UI system supports max 128 elements total across all canvases.", PSXEditorStyles.InfoBox);
PSXEditorStyles.EndCard();
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
} }
@@ -286,9 +295,15 @@ namespace SplashEdit.EditorCode
{ {
serializedObject.Update(); serializedObject.Update();
EditorGUILayout.LabelField("PSX UI Image", EditorStyles.boldLabel); // Header card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("PSX UI Image", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4); EditorGUILayout.Space(4);
// Properties card
PSXEditorStyles.BeginCard();
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"), new GUIContent("Element Name", EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"), new GUIContent("Element Name",
"Name used from Lua: UI.FindElement(canvas, \"name\"). Max 24 chars.")); "Name used from Lua: UI.FindElement(canvas, \"name\"). Max 24 chars."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("sourceTexture"), new GUIContent("Source Texture", EditorGUILayout.PropertyField(serializedObject.FindProperty("sourceTexture"), new GUIContent("Source Texture",
@@ -298,13 +313,19 @@ namespace SplashEdit.EditorCode
EditorGUILayout.PropertyField(serializedObject.FindProperty("tintColor"), new GUIContent("Tint Color", EditorGUILayout.PropertyField(serializedObject.FindProperty("tintColor"), new GUIContent("Tint Color",
"Color multiply applied to the image (white = no tint).")); "Color multiply applied to the image (white = no tint)."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible")); EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
PSXEditorStyles.EndCard();
// Texture size warning // Texture size warning
PSXUIImage img = (PSXUIImage)target; PSXUIImage img = (PSXUIImage)target;
if (img.SourceTexture != null) if (img.SourceTexture != null)
{ {
if (img.SourceTexture.width > 256 || img.SourceTexture.height > 256) if (img.SourceTexture.width > 256 || img.SourceTexture.height > 256)
EditorGUILayout.HelpBox("Texture exceeds 256x256. It will be resized during export.", MessageType.Warning); {
EditorGUILayout.Space(4);
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("Texture exceeds 256x256. It will be resized during export.", PSXEditorStyles.InfoBox);
PSXEditorStyles.EndCard();
}
} }
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
@@ -321,13 +342,20 @@ namespace SplashEdit.EditorCode
{ {
serializedObject.Update(); serializedObject.Update();
EditorGUILayout.LabelField("PSX UI Box", EditorStyles.boldLabel); // Header card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("PSX UI Box", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4); EditorGUILayout.Space(4);
// Properties card
PSXEditorStyles.BeginCard();
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName")); EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("boxColor"), new GUIContent("Box Color", EditorGUILayout.PropertyField(serializedObject.FindProperty("boxColor"), new GUIContent("Box Color",
"Solid fill color rendered as a FastFill primitive.")); "Solid fill color rendered as a FastFill primitive."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible")); EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
PSXEditorStyles.EndCard();
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
} }
@@ -343,9 +371,15 @@ namespace SplashEdit.EditorCode
{ {
serializedObject.Update(); serializedObject.Update();
EditorGUILayout.LabelField("PSX UI Text", EditorStyles.boldLabel); // Header card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("PSX UI Text", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4); EditorGUILayout.Space(4);
// Properties card
PSXEditorStyles.BeginCard();
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName")); EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("defaultText"), new GUIContent("Default Text", EditorGUILayout.PropertyField(serializedObject.FindProperty("defaultText"), new GUIContent("Default Text",
"Initial text content. Max 63 chars. Change at runtime via UI.SetText().")); "Initial text content. Max 63 chars. Change at runtime via UI.SetText()."));
@@ -354,24 +388,33 @@ namespace SplashEdit.EditorCode
EditorGUILayout.PropertyField(serializedObject.FindProperty("fontOverride"), new GUIContent("Font Override", 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).")); "Custom font for this text element. If empty, uses the canvas default font or built-in system font (8x16)."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible")); EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
PSXEditorStyles.EndCard();
// Character count EditorGUILayout.Space(4);
// Warnings and info
PSXUIText txt = (PSXUIText)target; PSXUIText txt = (PSXUIText)target;
if (!string.IsNullOrEmpty(txt.DefaultText) && txt.DefaultText.Length > 63) if (!string.IsNullOrEmpty(txt.DefaultText) && txt.DefaultText.Length > 63)
EditorGUILayout.HelpBox("Text exceeds 63 characters and will be truncated.", MessageType.Warning); {
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("Text exceeds 63 characters and will be truncated.", PSXEditorStyles.InfoBox);
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4);
}
// Font info PSXEditorStyles.BeginCard();
PSXFontAsset font = txt.GetEffectiveFont(); PSXFontAsset font = txt.GetEffectiveFont();
if (font != null) if (font != null)
{ {
EditorGUILayout.HelpBox( EditorGUILayout.LabelField(
$"Font: {font.name} ({font.GlyphWidth}x{font.GlyphHeight} glyphs)", $"Font: {font.name} ({font.GlyphWidth}x{font.GlyphHeight} glyphs)",
MessageType.Info); PSXEditorStyles.InfoBox);
} }
else else
{ {
EditorGUILayout.HelpBox("Using built-in system font (8x16 glyphs).", MessageType.Info); EditorGUILayout.LabelField("Using built-in system font (8x16 glyphs).", PSXEditorStyles.InfoBox);
} }
PSXEditorStyles.EndCard();
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
} }
@@ -387,9 +430,15 @@ namespace SplashEdit.EditorCode
{ {
serializedObject.Update(); serializedObject.Update();
EditorGUILayout.LabelField("PSX UI Progress Bar", EditorStyles.boldLabel); // Header card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("PSX UI Progress Bar", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4); EditorGUILayout.Space(4);
// Properties card
PSXEditorStyles.BeginCard();
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName")); EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("backgroundColor"), new GUIContent("Background Color", EditorGUILayout.PropertyField(serializedObject.FindProperty("backgroundColor"), new GUIContent("Background Color",
"Color shown behind the fill bar.")); "Color shown behind the fill bar."));
@@ -399,13 +448,12 @@ namespace SplashEdit.EditorCode
"Starting progress (0-100). Change via UI.SetProgress().")); "Starting progress (0-100). Change via UI.SetProgress()."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible")); EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
PSXEditorStyles.DrawSeparator(6, 4);
// Preview bar // Preview bar
Rect r = EditorGUILayout.GetControlRect(false, 16);
PSXUIProgressBar bar = (PSXUIProgressBar)target; PSXUIProgressBar bar = (PSXUIProgressBar)target;
EditorGUI.DrawRect(r, bar.BackgroundColor); PSXEditorStyles.DrawProgressBar(bar.InitialValue / 100f, "Preview", bar.FillColor, 16);
Rect fill = r; PSXEditorStyles.EndCard();
fill.width *= bar.InitialValue / 100f;
EditorGUI.DrawRect(fill, bar.FillColor);
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
} }
@@ -424,11 +472,18 @@ namespace SplashEdit.EditorCode
PSXFontAsset font = (PSXFontAsset)target; PSXFontAsset font = (PSXFontAsset)target;
EditorGUILayout.LabelField("PSX Font Asset", EditorStyles.boldLabel); // Header card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("PSX Font Asset", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4); EditorGUILayout.Space(4);
// ── Source font (TTF/OTF) ── // Source font card
EditorGUILayout.LabelField("Auto-Convert from Font", EditorStyles.miniBoldLabel); PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("Auto-Convert from Font", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.DrawSeparator(2, 4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("sourceFont"), new GUIContent("Source Font (TTF/OTF)", EditorGUILayout.PropertyField(serializedObject.FindProperty("sourceFont"), new GUIContent("Source Font (TTF/OTF)",
"Assign a Unity Font (TrueType/OpenType). Click 'Generate Bitmap' to rasterize it.\n" + "Assign a Unity Font (TrueType/OpenType). Click 'Generate Bitmap' to rasterize it.\n" +
"Glyph cell dimensions are auto-derived from the font metrics.")); "Glyph cell dimensions are auto-derived from the font metrics."));
@@ -440,102 +495,121 @@ namespace SplashEdit.EditorCode
if (font.SourceFont != null) if (font.SourceFont != null)
{ {
EditorGUILayout.Space(2); EditorGUILayout.Space(2);
if (GUILayout.Button("Generate Bitmap from Font", GUILayout.Height(28))) if (GUILayout.Button("Generate Bitmap from Font", PSXEditorStyles.PrimaryButton, GUILayout.Height(28)))
{ {
Undo.RecordObject(font, "Generate PSX Font Bitmap"); Undo.RecordObject(font, "Generate PSX Font Bitmap");
font.GenerateBitmapFromFont(); font.GenerateBitmapFromFont();
} }
if (font.FontTexture == null) if (font.FontTexture == null)
EditorGUILayout.HelpBox( EditorGUILayout.LabelField(
"Click 'Generate Bitmap' to create the font texture.\n" + "Click 'Generate Bitmap' to create the font texture.\n" +
"If generation fails, check that the font's import settings have " + "If generation fails, check that the font's import settings have " +
"'Character' set to 'ASCII Default Set'.", MessageType.Info); "'Character' set to 'ASCII Default Set'.", PSXEditorStyles.InfoBox);
} }
PSXEditorStyles.EndCard();
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); EditorGUILayout.Space(4);
// ── Glyph metrics ── // Manual bitmap card
// If using auto-convert, these are set by GenerateBitmapFromFont. PSXEditorStyles.BeginCard();
// For manual bitmaps, the user sets them directly. EditorGUILayout.LabelField("Manual Bitmap Source", PSXEditorStyles.CardHeaderStyle);
EditorGUILayout.LabelField("Glyph Metrics", EditorStyles.miniBoldLabel); PSXEditorStyles.DrawSeparator(2, 4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("fontTexture"), new GUIContent("Font Texture",
"256px wide bitmap. Glyphs in ASCII order from 0x20 (space). " +
"Transparent = background, opaque = foreground."));
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4);
// Glyph metrics card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("Glyph Metrics", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.DrawSeparator(2, 4);
if (font.SourceFont != null && font.FontTexture != null) if (font.SourceFont != null && font.FontTexture != null)
{ {
// Show as read-only when auto-generated
EditorGUI.BeginDisabledGroup(true); EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.IntField(new GUIContent("Glyph Width", "Auto-derived from font. Re-generate to change."), font.GlyphWidth); EditorGUILayout.IntField(new GUIContent("Glyph Width", "Auto-derived from font. Re-generate to change."), font.GlyphWidth);
EditorGUILayout.IntField(new GUIContent("Glyph Height", "Auto-derived from font. Re-generate to change."), font.GlyphHeight); EditorGUILayout.IntField(new GUIContent("Glyph Height", "Auto-derived from font. Re-generate to change."), font.GlyphHeight);
EditorGUI.EndDisabledGroup(); EditorGUI.EndDisabledGroup();
EditorGUILayout.HelpBox("Glyph dimensions are auto-derived when generating from a font.\n" + EditorGUILayout.LabelField("Glyph dimensions are auto-derived when generating from a font.\n" +
"Change the Font Size slider and re-generate to adjust.", MessageType.Info); "Change the Font Size slider and re-generate to adjust.", PSXEditorStyles.InfoBox);
} }
else else
{ {
// Editable for manual bitmap workflow
EditorGUILayout.PropertyField(serializedObject.FindProperty("glyphWidth"), new GUIContent("Glyph Width", EditorGUILayout.PropertyField(serializedObject.FindProperty("glyphWidth"), new GUIContent("Glyph Width",
"Width of each glyph cell in pixels. Must divide 256 evenly (4, 8, 16, or 32).")); "Width of each glyph cell in pixels. Must divide 256 evenly (4, 8, 16, or 32)."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("glyphHeight"), new GUIContent("Glyph Height", EditorGUILayout.PropertyField(serializedObject.FindProperty("glyphHeight"), new GUIContent("Glyph Height",
"Height of each glyph cell in pixels.")); "Height of each glyph cell in pixels."));
} }
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4); EditorGUILayout.Space(4);
// ── Layout info ── // Layout info card
PSXEditorStyles.BeginCard();
int glyphsPerRow = font.GlyphsPerRow; int glyphsPerRow = font.GlyphsPerRow;
int rowCount = font.RowCount; int rowCount = font.RowCount;
int totalH = font.TextureHeight; int totalH = font.TextureHeight;
int vramBytes = totalH * 128; // 128 bytes per row at 4bpp 256px int vramBytes = totalH * 128;
EditorGUILayout.HelpBox( EditorGUILayout.LabelField(
$"Layout: {glyphsPerRow} glyphs/row, {rowCount} rows\n" + $"Layout: {glyphsPerRow} glyphs/row, {rowCount} rows\n" +
$"Texture: 256 x {totalH} pixels (4bpp)\n" + $"Texture: 256 x {totalH} pixels (4bpp)\n" +
$"VRAM: {vramBytes} bytes ({vramBytes / 1024f:F1} KB)\n" + $"VRAM: {vramBytes} bytes ({vramBytes / 1024f:F1} KB)\n" +
$"Glyph cell: {font.GlyphWidth} x {font.GlyphHeight}", $"Glyph cell: {font.GlyphWidth} x {font.GlyphHeight}",
MessageType.Info); PSXEditorStyles.InfoBox);
// Show advance width status
if (font.AdvanceWidths != null && font.AdvanceWidths.Length >= 95) if (font.AdvanceWidths != null && font.AdvanceWidths.Length >= 95)
{ {
int minAdv = 255, maxAdv = 0; int minAdv = 255, maxAdv = 0;
for (int i = 1; i < 95; i++) // skip space for (int i = 1; i < 95; i++)
{ {
if (font.AdvanceWidths[i] < minAdv) minAdv = font.AdvanceWidths[i]; if (font.AdvanceWidths[i] < minAdv) minAdv = font.AdvanceWidths[i];
if (font.AdvanceWidths[i] > maxAdv) maxAdv = font.AdvanceWidths[i]; if (font.AdvanceWidths[i] > maxAdv) maxAdv = font.AdvanceWidths[i];
} }
EditorGUILayout.HelpBox( EditorGUILayout.LabelField(
$"Advance widths: {minAdv}-{maxAdv}px (proportional spacing stored)", $"Advance widths: {minAdv}-{maxAdv}px (proportional spacing stored)",
MessageType.Info); PSXEditorStyles.InfoBox);
} }
else if (font.FontTexture != null) else if (font.FontTexture != null)
{ {
EditorGUILayout.HelpBox( EditorGUILayout.LabelField(
"No advance widths stored. Click 'Generate Bitmap' to compute them.", "No advance widths stored. Click 'Generate Bitmap' to compute them.",
MessageType.Warning); PSXEditorStyles.InfoBox);
} }
PSXEditorStyles.EndCard();
// ── Validation ── // Validation
if (font.FontTexture != null) if (font.FontTexture != null)
{ {
if (font.FontTexture.width != 256) if (font.FontTexture.width != 256)
EditorGUILayout.HelpBox($"Font texture must be 256 pixels wide (currently {font.FontTexture.width}).", MessageType.Error); {
EditorGUILayout.Space(4);
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField($"Font texture must be 256 pixels wide (currently {font.FontTexture.width}).", PSXEditorStyles.InfoBox);
PSXEditorStyles.EndCard();
}
if (256 % font.GlyphWidth != 0) if (256 % font.GlyphWidth != 0)
EditorGUILayout.HelpBox($"Glyph width ({font.GlyphWidth}) must divide 256 evenly. " + {
"Valid values: 4, 8, 16, 32.", MessageType.Error); EditorGUILayout.Space(4);
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField($"Glyph width ({font.GlyphWidth}) must divide 256 evenly. " +
"Valid values: 4, 8, 16, 32.", PSXEditorStyles.InfoBox);
PSXEditorStyles.EndCard();
}
// Show preview // Preview
EditorGUILayout.Space(4); EditorGUILayout.Space(4);
EditorGUILayout.LabelField("Preview", EditorStyles.miniBoldLabel); PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("Preview", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.DrawSeparator(2, 4);
Rect previewRect = EditorGUILayout.GetControlRect(false, 64); Rect previewRect = EditorGUILayout.GetControlRect(false, 64);
GUI.DrawTexture(previewRect, font.FontTexture, ScaleMode.ScaleToFit); GUI.DrawTexture(previewRect, font.FontTexture, ScaleMode.ScaleToFit);
PSXEditorStyles.EndCard();
} }
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();

View File

@@ -92,7 +92,7 @@ namespace SplashEdit.RuntimeCode
var exporters = Object.FindObjectsByType<PSXObjectExporter>(FindObjectsSortMode.None); var exporters = Object.FindObjectsByType<PSXObjectExporter>(FindObjectsSortMode.None);
foreach (var e in exporters) foreach (var e in exporters)
exporterNames.Add(e.gameObject.name); exporterNames.Add(e.gameObject.name);
var audioSources = Object.FindObjectsByType<PSXAudioSource>(FindObjectsSortMode.None); var audioSources = Object.FindObjectsByType<PSXAudioClip>(FindObjectsSortMode.None);
foreach (var a in audioSources) foreach (var a in audioSources)
if (!string.IsNullOrEmpty(a.ClipName)) if (!string.IsNullOrEmpty(a.ClipName))
audioNames.Add(a.ClipName); audioNames.Add(a.ClipName);
@@ -351,7 +351,7 @@ namespace SplashEdit.RuntimeCode
evt.ClipName = EditorGUILayout.TextField("Clip Name", evt.ClipName); evt.ClipName = EditorGUILayout.TextField("Clip Name", evt.ClipName);
if (!string.IsNullOrEmpty(evt.ClipName) && !audioNames.Contains(evt.ClipName)) if (!string.IsNullOrEmpty(evt.ClipName) && !audioNames.Contains(evt.ClipName))
EditorGUILayout.HelpBox($"No PSXAudioSource with ClipName '{evt.ClipName}' in scene.", MessageType.Error); EditorGUILayout.HelpBox($"No PSXAudioClip with ClipName '{evt.ClipName}' in scene.", MessageType.Error);
evt.Volume = EditorGUILayout.IntSlider("Volume", evt.Volume, 0, 128); evt.Volume = EditorGUILayout.IntSlider("Volume", evt.Volume, 0, 128);
evt.Pan = EditorGUILayout.IntSlider("Pan", evt.Pan, 0, 127); evt.Pan = EditorGUILayout.IntSlider("Pan", evt.Pan, 0, 127);
@@ -505,7 +505,7 @@ namespace SplashEdit.RuntimeCode
// Build audio clip lookup // Build audio clip lookup
_audioClipCache.Clear(); _audioClipCache.Clear();
var audioSources = Object.FindObjectsByType<PSXAudioSource>(FindObjectsSortMode.None); var audioSources = Object.FindObjectsByType<PSXAudioClip>(FindObjectsSortMode.None);
foreach (var a in audioSources) foreach (var a in audioSources)
if (!string.IsNullOrEmpty(a.ClipName) && a.Clip != null) if (!string.IsNullOrEmpty(a.ClipName) && a.Clip != null)
_audioClipCache[a.ClipName] = a.Clip; _audioClipCache[a.ClipName] = a.Clip;

View File

@@ -15,7 +15,7 @@ namespace SplashEdit.EditorCode
// ───── Main Entry Point ───── // ───── Main Entry Point ─────
[MenuItem(MENU_ROOT + "SplashEdit Control Panel %#p", false, 0)] [MenuItem(MENU_ROOT + "SplashEdit Control Panel %#l", false, 0)]
public static void OpenControlPanel() public static void OpenControlPanel()
{ {
SplashControlPanel.ShowWindow(); SplashControlPanel.ShowWindow();

View File

@@ -21,7 +21,7 @@ namespace SplashEdit.EditorCode
private int _selectedRegion = -1; private int _selectedRegion = -1;
private bool _showAdvanced = false; private bool _showAdvanced = false;
[MenuItem("PSX/Nav Region Builder")] [MenuItem("PlayStation 1/Nav Region Builder")]
public static void ShowWindow() public static void ShowWindow()
{ {
GetWindow<PSXNavRegionEditor>("Nav Region Builder"); GetWindow<PSXNavRegionEditor>("Nav Region Builder");

View File

@@ -87,7 +87,7 @@ namespace SplashEdit.EditorCode
{ {
if (meshFilter == null || meshFilter.sharedMesh == null) if (meshFilter == null || meshFilter.sharedMesh == null)
{ {
EditorGUILayout.HelpBox("No mesh on this object.", MessageType.Warning); EditorGUILayout.LabelField("No mesh on this object.", PSXEditorStyles.InfoBox);
return; return;
} }
@@ -125,9 +125,9 @@ namespace SplashEdit.EditorCode
{ {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.Space(EditorGUI.indentLevel * 15); GUILayout.Space(EditorGUI.indentLevel * 15);
if (GUILayout.Button("Edit", EditorStyles.miniButtonLeft, GUILayout.Width(50))) if (GUILayout.Button("Edit", PSXEditorStyles.SecondaryButton, GUILayout.Width(50)))
AssetDatabase.OpenAsset(luaFileProp.objectReferenceValue); AssetDatabase.OpenAsset(luaFileProp.objectReferenceValue);
if (GUILayout.Button("Clear", EditorStyles.miniButtonRight, GUILayout.Width(50))) if (GUILayout.Button("Clear", PSXEditorStyles.SecondaryButton, GUILayout.Width(50)))
luaFileProp.objectReferenceValue = null; luaFileProp.objectReferenceValue = null;
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
@@ -136,7 +136,7 @@ namespace SplashEdit.EditorCode
{ {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.Space(EditorGUI.indentLevel * 15); GUILayout.Space(EditorGUI.indentLevel * 15);
if (GUILayout.Button("Create Lua Script", EditorStyles.miniButton, GUILayout.Width(130))) if (GUILayout.Button("Create Lua Script", PSXEditorStyles.SecondaryButton, GUILayout.Width(130)))
CreateNewLuaScript(); CreateNewLuaScript();
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
@@ -174,7 +174,7 @@ namespace SplashEdit.EditorCode
private void DrawActions() private void DrawActions()
{ {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Select Scene Exporter", EditorStyles.miniButton)) if (GUILayout.Button("Select Scene Exporter", PSXEditorStyles.SecondaryButton))
{ {
var se = FindFirstObjectByType<PSXSceneExporter>(); var se = FindFirstObjectByType<PSXSceneExporter>();
if (se != null) if (se != null)

View File

@@ -17,15 +17,9 @@ namespace SplashEdit.EditorCode
private SerializedProperty cutscenesProp; private SerializedProperty cutscenesProp;
private SerializedProperty loadingScreenProp; private SerializedProperty loadingScreenProp;
private SerializedProperty previewBVHProp; private SerializedProperty previewBVHProp;
private SerializedProperty previewRoomsPortalsProp;
private SerializedProperty bvhDepthProp; private SerializedProperty bvhDepthProp;
private bool _savedFog;
private Color _savedFogColor;
private FogMode _savedFogMode;
private float _savedFogStart;
private float _savedFogEnd;
private bool _previewActive = false;
private bool showFog = true; private bool showFog = true;
private bool showCutscenes = true; private bool showCutscenes = true;
private bool showDebug = false; private bool showDebug = false;
@@ -41,22 +35,12 @@ namespace SplashEdit.EditorCode
cutscenesProp = serializedObject.FindProperty("Cutscenes"); cutscenesProp = serializedObject.FindProperty("Cutscenes");
loadingScreenProp = serializedObject.FindProperty("LoadingScreenPrefab"); loadingScreenProp = serializedObject.FindProperty("LoadingScreenPrefab");
previewBVHProp = serializedObject.FindProperty("PreviewBVH"); previewBVHProp = serializedObject.FindProperty("PreviewBVH");
previewRoomsPortalsProp = serializedObject.FindProperty("PreviewRoomsPortals");
bvhDepthProp = serializedObject.FindProperty("BVHPreviewDepth"); bvhDepthProp = serializedObject.FindProperty("BVHPreviewDepth");
SaveAndApplyFogPreview();
EditorApplication.update += OnEditorUpdate;
} }
private void OnDisable() private void OnDisable()
{ {
EditorApplication.update -= OnEditorUpdate;
RestoreFog();
}
private void OnEditorUpdate()
{
if (_previewActive)
ApplyFogPreview();
} }
public override void OnInspectorGUI() public override void OnInspectorGUI()
@@ -64,7 +48,7 @@ namespace SplashEdit.EditorCode
serializedObject.Update(); serializedObject.Update();
var exporter = (PSXSceneExporter)target; var exporter = (PSXSceneExporter)target;
DrawHeader(); DrawExporterHeader();
EditorGUILayout.Space(4); EditorGUILayout.Space(4);
DrawSceneSettings(); DrawSceneSettings();
@@ -82,7 +66,7 @@ namespace SplashEdit.EditorCode
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
} }
private void DrawHeader() private void DrawExporterHeader()
{ {
EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle); EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle);
EditorGUILayout.LabelField("Scene Exporter", PSXEditorStyles.CardHeaderStyle); EditorGUILayout.LabelField("Scene Exporter", PSXEditorStyles.CardHeaderStyle);
@@ -138,15 +122,9 @@ namespace SplashEdit.EditorCode
EditorGUILayout.Space(2); EditorGUILayout.Space(2);
EditorGUILayout.LabelField( EditorGUILayout.LabelField(
$"<color=#aaaaaa>Preview: {fogNearUnity:F1} {fogFarUnity:F1} units | " + $"<color=#aaaaaa>GTE range: {fogNearUnity:F1} - {fogFarUnity:F1} units | " +
$"GTE: {8000f / (density * 3f):F0} {8000f / density:F0} SZ</color>", $"{8000f / (density * 3f):F0} - {8000f / density:F0} SZ</color>",
PSXEditorStyles.RichLabel); PSXEditorStyles.RichLabel);
ApplyFogPreview();
}
else
{
RenderSettings.fog = false;
} }
EditorGUI.indentLevel--; EditorGUI.indentLevel--;
@@ -186,17 +164,18 @@ namespace SplashEdit.EditorCode
EditorGUILayout.PropertyField(previewBVHProp, new GUIContent("Preview BVH")); EditorGUILayout.PropertyField(previewBVHProp, new GUIContent("Preview BVH"));
if (previewBVHProp.boolValue) if (previewBVHProp.boolValue)
EditorGUILayout.PropertyField(bvhDepthProp, new GUIContent("BVH Depth")); EditorGUILayout.PropertyField(bvhDepthProp, new GUIContent("BVH Depth"));
EditorGUILayout.PropertyField(previewRoomsPortalsProp, new GUIContent("Preview Rooms/Portals"));
EditorGUI.indentLevel--; EditorGUI.indentLevel--;
} }
private void DrawSceneStats() private void DrawSceneStats()
{ {
var exporters = FindObjectsOfType<PSXObjectExporter>(); var exporters = FindObjectsByType<PSXObjectExporter>(FindObjectsSortMode.None);
int total = exporters.Length; int total = exporters.Length;
int active = exporters.Count(e => e.IsActive); int active = exporters.Count(e => e.IsActive);
int staticCol = exporters.Count(e => e.CollisionType == PSXCollisionType.Static); int staticCol = exporters.Count(e => e.CollisionType == PSXCollisionType.Static);
int dynamicCol = exporters.Count(e => e.CollisionType == PSXCollisionType.Dynamic); int dynamicCol = exporters.Count(e => e.CollisionType == PSXCollisionType.Dynamic);
int triggerBoxes = FindObjectsOfType<PSXTriggerBox>().Length; int triggerBoxes = FindObjectsByType<PSXTriggerBox>(FindObjectsSortMode.None).Length;
EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle); EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle);
EditorGUILayout.LabelField( EditorGUILayout.LabelField(
@@ -205,49 +184,5 @@ namespace SplashEdit.EditorCode
EditorGUILayout.EndVertical(); EditorGUILayout.EndVertical();
} }
private void SaveAndApplyFogPreview()
{
_savedFog = RenderSettings.fog;
_savedFogColor = RenderSettings.fogColor;
_savedFogMode = RenderSettings.fogMode;
_savedFogStart = RenderSettings.fogStartDistance;
_savedFogEnd = RenderSettings.fogEndDistance;
_previewActive = true;
ApplyFogPreview();
}
private void ApplyFogPreview()
{
var exporter = (PSXSceneExporter)target;
if (exporter == null) return;
if (!exporter.FogEnabled)
{
RenderSettings.fog = false;
return;
}
float gteScale = exporter.GTEScaling;
int density = Mathf.Clamp(exporter.FogDensity, 1, 10);
float fogFarSZ = 8000f / density;
float fogNearSZ = fogFarSZ / 3f;
RenderSettings.fog = true;
RenderSettings.fogColor = exporter.FogColor;
RenderSettings.fogMode = FogMode.Linear;
RenderSettings.fogStartDistance = fogNearSZ * gteScale / 4096f;
RenderSettings.fogEndDistance = fogFarSZ * gteScale / 4096f;
}
private void RestoreFog()
{
if (!_previewActive) return;
_previewActive = false;
RenderSettings.fog = _savedFog;
RenderSettings.fogColor = _savedFogColor;
RenderSettings.fogMode = _savedFogMode;
RenderSettings.fogStartDistance = _savedFogStart;
RenderSettings.fogEndDistance = _savedFogEnd;
}
} }
} }

View File

@@ -20,19 +20,28 @@ namespace SplashEdit.EditorCode
{ {
serializedObject.Update(); serializedObject.Update();
EditorGUILayout.LabelField("PSX Trigger Box", EditorStyles.boldLabel); // Header card
PSXEditorStyles.BeginCard();
EditorGUILayout.LabelField("PSX Trigger Box", PSXEditorStyles.CardHeaderStyle);
PSXEditorStyles.EndCard();
EditorGUILayout.Space(4); EditorGUILayout.Space(4);
// Properties card
PSXEditorStyles.BeginCard();
EditorGUILayout.PropertyField(sizeProp, new GUIContent("Size")); EditorGUILayout.PropertyField(sizeProp, new GUIContent("Size"));
PSXEditorStyles.DrawSeparator(4, 4);
EditorGUILayout.PropertyField(luaFileProp, new GUIContent("Lua Script")); EditorGUILayout.PropertyField(luaFileProp, new GUIContent("Lua Script"));
if (luaFileProp.objectReferenceValue != null) if (luaFileProp.objectReferenceValue != null)
{ {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.Space(EditorGUI.indentLevel * 15); GUILayout.Space(EditorGUI.indentLevel * 15);
if (GUILayout.Button("Edit", EditorStyles.miniButtonLeft, GUILayout.Width(50))) if (GUILayout.Button("Edit", PSXEditorStyles.SecondaryButton, GUILayout.Width(50)))
AssetDatabase.OpenAsset(luaFileProp.objectReferenceValue); AssetDatabase.OpenAsset(luaFileProp.objectReferenceValue);
if (GUILayout.Button("Clear", EditorStyles.miniButtonRight, GUILayout.Width(50))) if (GUILayout.Button("Clear", PSXEditorStyles.SecondaryButton, GUILayout.Width(50)))
luaFileProp.objectReferenceValue = null; luaFileProp.objectReferenceValue = null;
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
@@ -41,11 +50,12 @@ namespace SplashEdit.EditorCode
{ {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.Space(EditorGUI.indentLevel * 15); GUILayout.Space(EditorGUI.indentLevel * 15);
if (GUILayout.Button("Create Lua Script", EditorStyles.miniButton, GUILayout.Width(130))) if (GUILayout.Button("Create Lua Script", PSXEditorStyles.SecondaryButton, GUILayout.Width(130)))
CreateNewLuaScript(); CreateNewLuaScript();
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
} }
PSXEditorStyles.EndCard();
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
} }

View File

@@ -27,19 +27,25 @@ namespace SplashEdit.EditorCode
private void OnGUI() private void OnGUI()
{ {
GUILayout.Label("Quantized Preview", EditorStyles.boldLabel); GUILayout.Label("Quantized Preview", PSXEditorStyles.WindowHeader);
// Texture input field // Texture input field
PSXEditorStyles.BeginCard();
originalTexture = (Texture2D)EditorGUILayout.ObjectField("Original Texture", originalTexture, typeof(Texture2D), false); originalTexture = (Texture2D)EditorGUILayout.ObjectField("Original Texture", originalTexture, typeof(Texture2D), false);
// Dropdown for bit depth selection // Dropdown for bit depth selection
bpp = (PSXBPP)EditorGUILayout.EnumPopup("Bit Depth", bpp); bpp = (PSXBPP)EditorGUILayout.EnumPopup("Bit Depth", bpp);
EditorGUILayout.Space(4);
// Button to generate the quantized preview // Button to generate the quantized preview
if (GUILayout.Button("Generate Quantized Preview") && originalTexture != null) if (GUILayout.Button("Generate Quantized Preview", PSXEditorStyles.PrimaryButton, GUILayout.Height(26)) && originalTexture != null)
{ {
GenerateQuantizedPreview(); GenerateQuantizedPreview();
} }
PSXEditorStyles.EndCard();
PSXEditorStyles.DrawSeparator(4, 4);
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
@@ -47,7 +53,7 @@ namespace SplashEdit.EditorCode
if (originalTexture != null) if (originalTexture != null)
{ {
GUILayout.BeginVertical(); GUILayout.BeginVertical();
GUILayout.Label("Original Texture"); GUILayout.Label("Original Texture", PSXEditorStyles.CardHeaderStyle);
DrawTexturePreview(originalTexture, previewSize, false); DrawTexturePreview(originalTexture, previewSize, false);
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
@@ -56,7 +62,7 @@ namespace SplashEdit.EditorCode
if (vramTexture != null) if (vramTexture != null)
{ {
GUILayout.BeginVertical(); GUILayout.BeginVertical();
GUILayout.Label("VRAM View (Indexed Data as 16bpp)"); GUILayout.Label("VRAM View (Indexed Data as 16bpp)", PSXEditorStyles.CardHeaderStyle);
DrawTexturePreview(vramTexture, previewSize); DrawTexturePreview(vramTexture, previewSize);
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
@@ -65,7 +71,7 @@ namespace SplashEdit.EditorCode
if (quantizedTexture != null) if (quantizedTexture != null)
{ {
GUILayout.BeginVertical(); GUILayout.BeginVertical();
GUILayout.Label("Quantized Texture"); GUILayout.Label("Quantized Texture", PSXEditorStyles.CardHeaderStyle);
DrawTexturePreview(quantizedTexture, previewSize); DrawTexturePreview(quantizedTexture, previewSize);
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
@@ -75,16 +81,17 @@ namespace SplashEdit.EditorCode
// Display the Color Lookup Table (CLUT) // Display the Color Lookup Table (CLUT)
if (clut != null) if (clut != null)
{ {
GUILayout.Label("Color Lookup Table (CLUT)"); PSXEditorStyles.DrawSeparator(4, 4);
GUILayout.Label("Color Lookup Table (CLUT)", PSXEditorStyles.SectionHeader);
DrawCLUT(); DrawCLUT();
} }
GUILayout.Space(10); PSXEditorStyles.DrawSeparator(4, 4);
// Export indexed pixel data // Export indexed pixel data
if (indexedPixelData != null) if (indexedPixelData != null)
{ {
if (GUILayout.Button("Export texture data")) if (GUILayout.Button("Export texture data", PSXEditorStyles.SecondaryButton, GUILayout.Height(24)))
{ {
string path = EditorUtility.SaveFilePanel("Save texture data", "", "pixel_data", "bin"); string path = EditorUtility.SaveFilePanel("Save texture data", "", "pixel_data", "bin");
@@ -105,7 +112,7 @@ namespace SplashEdit.EditorCode
// Export CLUT data // Export CLUT data
if (clut != null) if (clut != null)
{ {
if (GUILayout.Button("Export CLUT data")) if (GUILayout.Button("Export CLUT data", PSXEditorStyles.SecondaryButton, GUILayout.Height(24)))
{ {
string path = EditorUtility.SaveFilePanel("Save CLUT data", "", "clut_data", "bin"); string path = EditorUtility.SaveFilePanel("Save CLUT data", "", "clut_data", "bin");

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using SplashEdit.RuntimeCode; using SplashEdit.RuntimeCode;
using Unity.Collections; using Unity.Collections;
@@ -19,25 +18,15 @@ namespace SplashEdit.EditorCode
private List<ProhibitedArea> prohibitedAreas = new List<ProhibitedArea>(); private List<ProhibitedArea> prohibitedAreas = new List<ProhibitedArea>();
private Vector2 scrollPosition; private Vector2 scrollPosition;
private Texture2D vramImage; private Texture2D vramImage;
private Vector2 selectedResolution = new Vector2(320, 240); private static readonly Vector2 selectedResolution = new Vector2(320, 240);
private bool dualBuffering = true; private const bool dualBuffering = true;
private bool verticalLayout = true; private const bool verticalLayout = true;
private Color bufferColor1 = new Color(1, 0, 0, 0.5f); private Color bufferColor1 = new Color(1, 0, 0, 0.5f);
private Color bufferColor2 = new Color(0, 1, 0, 0.5f); private Color bufferColor2 = new Color(0, 1, 0, 0.5f);
private Color prohibitedColor = new Color(1, 0, 0, 0.3f); private Color prohibitedColor = new Color(1, 0, 0, 0.3f);
private PSXData _psxData; private PSXData _psxData;
private PSXFontData[] _cachedFonts; private PSXFontData[] _cachedFonts;
private static readonly Vector2[] resolutions =
{
new Vector2(256, 240), new Vector2(256, 480),
new Vector2(320, 240), new Vector2(320, 480),
new Vector2(368, 240), new Vector2(368, 480),
new Vector2(512, 240), new Vector2(512, 480),
new Vector2(640, 240), new Vector2(640, 480)
};
private static string[] resolutionsStrings => resolutions.Select(c => $"{c.x}x{c.y}").ToArray();
[MenuItem("PlayStation 1/VRAM Editor")] [MenuItem("PlayStation 1/VRAM Editor")]
public static void ShowWindow() public static void ShowWindow()
{ {
@@ -58,7 +47,9 @@ namespace SplashEdit.EditorCode
// Ensure minimum window size is applied. // Ensure minimum window size is applied.
this.minSize = MinSize; this.minSize = MinSize;
_psxData = DataStorage.LoadData(out selectedResolution, out dualBuffering, out verticalLayout, out prohibitedAreas); Vector2 ignoredRes;
bool ignoredDb, ignoredVl;
_psxData = DataStorage.LoadData(out ignoredRes, out ignoredDb, out ignoredVl, out prohibitedAreas);
} }
/// <summary> /// <summary>
@@ -202,64 +193,18 @@ namespace SplashEdit.EditorCode
} }
vramImage.Apply(); vramImage.Apply();
// Prompt the user to select a file location and save the VRAM data.
string path = EditorUtility.SaveFilePanel("Select Output File", "", "output", "bin");
if (path != string.Empty)
{
using (BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.Create)))
{
for (int y = 0; y < VramHeight; y++)
{
for (int x = 0; x < VramWidth; x++)
{
writer.Write(packed.vramPixels[x, y].Pack());
}
}
}
}
} }
private void OnGUI() private void OnGUI()
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
GUILayout.BeginVertical(); GUILayout.BeginVertical();
GUILayout.Label("VRAM Editor", EditorStyles.boldLabel); GUILayout.Label("VRAM Editor", PSXEditorStyles.WindowHeader);
GUILayout.Label("320x240, dual-buffered, vertical layout", PSXEditorStyles.InfoBox);
// Dropdown for resolution selection. PSXEditorStyles.DrawSeparator(6, 6);
selectedResolution = resolutions[EditorGUILayout.Popup("Resolution", System.Array.IndexOf(resolutions, selectedResolution), resolutionsStrings)]; GUILayout.Label("Prohibited Areas", PSXEditorStyles.SectionHeader);
GUILayout.Space(4);
// Check resolution constraints for dual buffering.
bool canDBHorizontal = selectedResolution.x * 2 <= VramWidth;
bool canDBVertical = selectedResolution.y * 2 <= VramHeight;
if (canDBHorizontal || canDBVertical)
{
dualBuffering = EditorGUILayout.Toggle("Dual Buffering", dualBuffering);
}
else
{
dualBuffering = false;
}
if (canDBVertical && canDBHorizontal)
{
verticalLayout = EditorGUILayout.Toggle("Vertical", verticalLayout);
}
else if (canDBVertical)
{
verticalLayout = true;
}
else
{
verticalLayout = false;
}
GUILayout.Space(10);
GUILayout.Label("Prohibited Areas", EditorStyles.boldLabel);
GUILayout.Space(10);
scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true, GUILayout.MinHeight(300f), GUILayout.ExpandWidth(true)); scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true, GUILayout.MinHeight(300f), GUILayout.ExpandWidth(true));
@@ -270,10 +215,7 @@ namespace SplashEdit.EditorCode
{ {
var area = prohibitedAreas[i]; var area = prohibitedAreas[i];
GUI.backgroundColor = new Color(0.95f, 0.95f, 0.95f); PSXEditorStyles.BeginCard();
GUILayout.BeginVertical("box");
GUI.backgroundColor = Color.white;
// Display fields for editing the area // Display fields for editing the area
area.X = EditorGUILayout.IntField("X Coordinate", area.X); area.X = EditorGUILayout.IntField("X Coordinate", area.X);
@@ -281,17 +223,16 @@ namespace SplashEdit.EditorCode
area.Width = EditorGUILayout.IntField("Width", area.Width); area.Width = EditorGUILayout.IntField("Width", area.Width);
area.Height = EditorGUILayout.IntField("Height", area.Height); area.Height = EditorGUILayout.IntField("Height", area.Height);
EditorGUILayout.Space(2);
if (GUILayout.Button("Remove", GUILayout.Height(30))) if (GUILayout.Button("Remove", PSXEditorStyles.DangerButton, GUILayout.Height(24)))
{ {
toRemove.Add(i); // Mark for removal toRemove.Add(i); // Mark for removal
} }
prohibitedAreas[i] = area; prohibitedAreas[i] = area;
GUILayout.EndVertical(); PSXEditorStyles.EndCard();
GUILayout.Space(10); GUILayout.Space(4);
} }
// Remove the areas marked for deletion outside the loop to avoid skipping elements // Remove the areas marked for deletion outside the loop to avoid skipping elements
@@ -303,19 +244,23 @@ namespace SplashEdit.EditorCode
GUILayout.EndScrollView(); GUILayout.EndScrollView();
GUILayout.Space(10); GUILayout.Space(10);
if (GUILayout.Button("Add Prohibited Area")) if (GUILayout.Button("Add Prohibited Area", PSXEditorStyles.SecondaryButton))
{ {
prohibitedAreas.Add(new ProhibitedArea()); prohibitedAreas.Add(new ProhibitedArea());
} }
// Button to initiate texture packing. PSXEditorStyles.DrawSeparator(4, 4);
if (GUILayout.Button("Pack Textures"))
// Button to pack and preview VRAM layout.
if (GUILayout.Button("Pack Preview", PSXEditorStyles.PrimaryButton, GUILayout.Height(28)))
{ {
PackTextures(); PackTextures();
} }
// Button to save settings; saving now occurs only on button press. EditorGUILayout.Space(2);
if (GUILayout.Button("Save Settings"))
// Button to save prohibited areas.
if (GUILayout.Button("Save Settings", PSXEditorStyles.SuccessButton, GUILayout.Height(28)))
{ {
_psxData.OutputResolution = selectedResolution; _psxData.OutputResolution = selectedResolution;
_psxData.DualBuffering = dualBuffering; _psxData.DualBuffering = dualBuffering;

View File

@@ -20,8 +20,8 @@ namespace SplashEdit.RuntimeCode
/// At export time, the AudioClip is converted to SPU ADPCM and packed /// At export time, the AudioClip is converted to SPU ADPCM and packed
/// into the splashpack binary. Use Audio.Play(clipIndex) from Lua. /// into the splashpack binary. Use Audio.Play(clipIndex) from Lua.
/// </summary> /// </summary>
[AddComponentMenu("PSX/Audio Source")] [AddComponentMenu("PSX/Audio Clip")]
public class PSXAudioSource : MonoBehaviour public class PSXAudioClip : MonoBehaviour
{ {
[Tooltip("Name used to identify this clip in Lua (Audio.Play(\"name\"))." )] [Tooltip("Name used to identify this clip in Lua (Audio.Play(\"name\"))." )]
public string ClipName = ""; public string ClipName = "";

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0da2803235be654438e86fe9d9a954d4

View File

@@ -13,7 +13,7 @@ namespace SplashEdit.RuntimeCode
[Tooltip("Frame at which to trigger this audio clip.")] [Tooltip("Frame at which to trigger this audio clip.")]
public int Frame; public int Frame;
[Tooltip("Name of the audio clip (must match a PSXAudioSource ClipName in the scene).")] [Tooltip("Name of the audio clip (must match a PSXAudioClip ClipName in the scene).")]
public string ClipName = ""; public string ClipName = "";
[Tooltip("Playback volume (0 = silent, 128 = max).")] [Tooltip("Playback volume (0 = silent, 128 = max).")]

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 3c4c3feb30e8c264baddc3a5e774473b

View File

@@ -45,7 +45,7 @@ namespace SplashEdit.RuntimeCode
BinaryWriter writer, BinaryWriter writer,
PSXCutsceneClip[] cutscenes, PSXCutsceneClip[] cutscenes,
PSXObjectExporter[] exporters, PSXObjectExporter[] exporters,
PSXAudioSource[] audioSources, PSXAudioClip[] audioSources,
float gteScaling, float gteScaling,
out long cutsceneTableStart, out long cutsceneTableStart,
Action<string, LogType> log = null) Action<string, LogType> log = null)

View File

@@ -12,7 +12,8 @@ namespace SplashEdit.RuntimeCode
Dynamic = 2 Dynamic = 2
} }
[RequireComponent(typeof(Renderer))] [RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class PSXObjectExporter : MonoBehaviour, IPSXExportable public class PSXObjectExporter : MonoBehaviour, IPSXExportable
{ {
public LuaFile LuaFile => luaFile; public LuaFile LuaFile => luaFile;

View File

@@ -26,6 +26,9 @@ namespace SplashEdit.RuntimeCode
void OnDrawGizmos() void OnDrawGizmos()
{ {
var exporter = FindFirstObjectByType<PSXSceneExporter>();
if (exporter != null && !exporter.PreviewRoomsPortals) return;
Gizmos.color = new Color(1f, 0.5f, 0f, 0.3f); Gizmos.color = new Color(1f, 0.5f, 0f, 0.3f);
Gizmos.matrix = transform.localToWorldMatrix; Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawCube(Vector3.zero, new Vector3(PortalSize.x, PortalSize.y, 0.05f)); Gizmos.DrawCube(Vector3.zero, new Vector3(PortalSize.x, PortalSize.y, 0.05f));

View File

@@ -56,6 +56,9 @@ namespace SplashEdit.RuntimeCode
void OnDrawGizmos() void OnDrawGizmos()
{ {
var exporter = FindFirstObjectByType<PSXSceneExporter>();
if (exporter != null && !exporter.PreviewRoomsPortals) return;
Gizmos.color = new Color(0.2f, 0.8f, 0.4f, 0.15f); Gizmos.color = new Color(0.2f, 0.8f, 0.4f, 0.15f);
Gizmos.matrix = transform.localToWorldMatrix; Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawCube(VolumeOffset, VolumeSize); Gizmos.DrawCube(VolumeOffset, VolumeSize);

View File

@@ -57,7 +57,7 @@ namespace SplashEdit.RuntimeCode
// Component arrays // Component arrays
private PSXInteractable[] _interactables; private PSXInteractable[] _interactables;
private PSXAudioSource[] _audioSources; private PSXAudioClip[] _audioSources;
private PSXTriggerBox[] _triggerBoxes; private PSXTriggerBox[] _triggerBoxes;
// Phase 3+4: World collision and nav regions // Phase 3+4: World collision and nav regions
@@ -90,6 +90,8 @@ namespace SplashEdit.RuntimeCode
private BVH _bvh; private BVH _bvh;
public bool PreviewBVH = true; public bool PreviewBVH = true;
public bool PreviewRoomsPortals = true;
public int BVHPreviewDepth = 9999; public int BVHPreviewDepth = 9999;
/// <summary> /// <summary>
@@ -120,7 +122,7 @@ namespace SplashEdit.RuntimeCode
// Collect components // Collect components
_interactables = FindObjectsByType<PSXInteractable>(FindObjectsSortMode.None); _interactables = FindObjectsByType<PSXInteractable>(FindObjectsSortMode.None);
_audioSources = FindObjectsByType<PSXAudioSource>(FindObjectsSortMode.None); _audioSources = FindObjectsByType<PSXAudioClip>(FindObjectsSortMode.None);
_triggerBoxes = FindObjectsByType<PSXTriggerBox>(FindObjectsSortMode.None); _triggerBoxes = FindObjectsByType<PSXTriggerBox>(FindObjectsSortMode.None);
// Collect UI image textures for VRAM packing alongside 3D textures // Collect UI image textures for VRAM packing alongside 3D textures

View File

@@ -31,7 +31,7 @@ namespace SplashEdit.RuntimeCode
// Cutscene data (v12) // Cutscene data (v12)
public PSXCutsceneClip[] cutscenes; public PSXCutsceneClip[] cutscenes;
public PSXAudioSource[] audioSources; public PSXAudioClip[] audioSources;
// UI canvases (v13) // UI canvases (v13)
public PSXCanvasData[] canvases; public PSXCanvasData[] canvases;

View File

@@ -154,6 +154,18 @@ namespace SplashEdit.RuntimeCode
G = (ushort)(pixel.g * 31), G = (ushort)(pixel.g * 31),
B = (ushort)(pixel.b * 31) B = (ushort)(pixel.b * 31)
}; };
// PS1: color 0x0000 is transparent. If the source pixel is opaque
// but quantized to pure black, bump to near-black (1,1,1) with bit15
// set so the hardware doesn't treat it as see-through.
if (vramPixel.Pack() == 0x0000 && pixel.a > 0f)
{
vramPixel.R = 1;
vramPixel.G = 1;
vramPixel.B = 1;
vramPixel.SemiTransparent = true;
}
psxTex.ImageData[x, y] = vramPixel; psxTex.ImageData[x, y] = vramPixel;
} }
} }
@@ -169,6 +181,18 @@ namespace SplashEdit.RuntimeCode
{ {
Color pixel = new Color(color.x, color.y, color.z); Color pixel = new Color(color.x, color.y, color.z);
VRAMPixel vramPixel = new VRAMPixel { R = (ushort)(pixel.r * 31), G = (ushort)(pixel.g * 31), B = (ushort)(pixel.b * 31) }; VRAMPixel vramPixel = new VRAMPixel { R = (ushort)(pixel.r * 31), G = (ushort)(pixel.g * 31), B = (ushort)(pixel.b * 31) };
// PS1: palette entry 0x0000 is transparent. Any non-transparent palette
// color that quantizes to pure black must be bumped to near-black (1,1,1)
// with bit15 set to avoid the hardware treating it as see-through.
if (vramPixel.Pack() == 0x0000)
{
vramPixel.R = 1;
vramPixel.G = 1;
vramPixel.B = 1;
vramPixel.SemiTransparent = true;
}
psxTex.ColorPalette.Add(vramPixel); psxTex.ColorPalette.Add(vramPixel);
} }