diff --git a/Editor/PSXObjectExporterEditor.cs b/Editor/PSXObjectExporterEditor.cs
index ba3797f..ccc8db3 100644
--- a/Editor/PSXObjectExporterEditor.cs
+++ b/Editor/PSXObjectExporterEditor.cs
@@ -13,11 +13,6 @@ namespace SplashEdit.EditorCode
private SerializedProperty bitDepthProp;
private SerializedProperty luaFileProp;
private SerializedProperty collisionTypeProp;
- private SerializedProperty staticColliderProp;
- private SerializedProperty exportCollisionMeshProp;
- private SerializedProperty customCollisionMeshProp;
- private SerializedProperty collisionLayerProp;
- private SerializedProperty generateNavigationProp;
private MeshFilter meshFilter;
private MeshRenderer meshRenderer;
@@ -33,11 +28,6 @@ namespace SplashEdit.EditorCode
bitDepthProp = serializedObject.FindProperty("bitDepth");
luaFileProp = serializedObject.FindProperty("luaFile");
collisionTypeProp = serializedObject.FindProperty("collisionType");
- staticColliderProp = serializedObject.FindProperty("staticCollider");
- exportCollisionMeshProp = serializedObject.FindProperty("exportCollisionMesh");
- customCollisionMeshProp = serializedObject.FindProperty("customCollisionMesh");
- collisionLayerProp = serializedObject.FindProperty("collisionLayer");
- generateNavigationProp = serializedObject.FindProperty("generateNavigation");
CacheMeshInfo();
}
@@ -165,32 +155,18 @@ namespace SplashEdit.EditorCode
EditorGUILayout.PropertyField(collisionTypeProp, new GUIContent("Type"));
var collType = (PSXCollisionType)collisionTypeProp.enumValueIndex;
- if (collType != PSXCollisionType.None)
+ if (collType == PSXCollisionType.Static)
{
- EditorGUILayout.PropertyField(staticColliderProp, new GUIContent("Static"));
-
- bool isStatic = staticColliderProp.boolValue;
- if (isStatic)
- {
- EditorGUILayout.LabelField(
- "Baked into world collision mesh. No runtime cost.",
- PSXEditorStyles.RichLabel);
- }
- else
- {
- EditorGUILayout.LabelField(
- "Runtime AABB collider. Fires Lua collision events.",
- PSXEditorStyles.RichLabel);
- }
-
- EditorGUILayout.Space(2);
- EditorGUILayout.PropertyField(exportCollisionMeshProp, new GUIContent("Export Collision Mesh"));
- EditorGUILayout.PropertyField(customCollisionMeshProp, new GUIContent("Custom Mesh"));
- EditorGUILayout.PropertyField(collisionLayerProp, new GUIContent("Layer"));
+ EditorGUILayout.LabelField(
+ "Baked into world collision mesh. No runtime cost.",
+ PSXEditorStyles.RichLabel);
+ }
+ else if (collType == PSXCollisionType.Dynamic)
+ {
+ EditorGUILayout.LabelField(
+ "Runtime AABB collider. Pushes player back + fires Lua events.",
+ PSXEditorStyles.RichLabel);
}
-
- EditorGUILayout.Space(4);
- EditorGUILayout.PropertyField(generateNavigationProp, new GUIContent("Generate Navigation"));
EditorGUI.indentLevel--;
}
@@ -231,5 +207,41 @@ namespace SplashEdit.EditorCode
serializedObject.ApplyModifiedProperties();
}
}
+
+ [DrawGizmo(GizmoType.Selected | GizmoType.NonSelected)]
+ private static void DrawColliderGizmo(PSXObjectExporter exporter, GizmoType gizmoType)
+ {
+ if (exporter.CollisionType != PSXCollisionType.Dynamic) return;
+
+ MeshFilter mf = exporter.GetComponent();
+ Mesh mesh = mf?.sharedMesh;
+ if (mesh == null) return;
+
+ Bounds local = mesh.bounds;
+ Matrix4x4 worldMatrix = exporter.transform.localToWorldMatrix;
+
+ Vector3 ext = local.extents;
+ Vector3 center = local.center;
+ Vector3 aabbMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
+ Vector3 aabbMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
+
+ for (int i = 0; i < 8; i++)
+ {
+ Vector3 corner = center + new Vector3(
+ (i & 1) != 0 ? ext.x : -ext.x,
+ (i & 2) != 0 ? ext.y : -ext.y,
+ (i & 4) != 0 ? ext.z : -ext.z
+ );
+ Vector3 world = worldMatrix.MultiplyPoint3x4(corner);
+ aabbMin = Vector3.Min(aabbMin, world);
+ aabbMax = Vector3.Max(aabbMax, world);
+ }
+
+ bool selected = (gizmoType & GizmoType.Selected) != 0;
+ Gizmos.color = selected ? new Color(0.2f, 0.8f, 1f, 0.8f) : new Color(0.2f, 0.8f, 1f, 0.3f);
+ Vector3 c = (aabbMin + aabbMax) * 0.5f;
+ Vector3 s = aabbMax - aabbMin;
+ Gizmos.DrawWireCube(c, s);
+ }
}
}
diff --git a/Editor/PSXSceneExporterEditor.cs b/Editor/PSXSceneExporterEditor.cs
index e91b51e..c53a360 100644
--- a/Editor/PSXSceneExporterEditor.cs
+++ b/Editor/PSXSceneExporterEditor.cs
@@ -194,12 +194,13 @@ namespace SplashEdit.EditorCode
var exporters = FindObjectsOfType();
int total = exporters.Length;
int active = exporters.Count(e => e.IsActive);
- int withCollision = exporters.Count(e => e.CollisionType != PSXCollisionType.None);
- int staticCol = exporters.Count(e => e.StaticCollider && e.CollisionType != PSXCollisionType.None);
+ int staticCol = exporters.Count(e => e.CollisionType == PSXCollisionType.Static);
+ int dynamicCol = exporters.Count(e => e.CollisionType == PSXCollisionType.Dynamic);
+ int triggerBoxes = FindObjectsOfType().Length;
EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle);
EditorGUILayout.LabelField(
- $"{active}/{total} objects | {withCollision} colliders ({staticCol} static)",
+ $"{active}/{total} objects | {staticCol} static {dynamicCol} dynamic {triggerBoxes} triggers",
PSXEditorStyles.RichLabel);
EditorGUILayout.EndVertical();
}
diff --git a/Editor/PSXTriggerBoxEditor.cs b/Editor/PSXTriggerBoxEditor.cs
new file mode 100644
index 0000000..91221ba
--- /dev/null
+++ b/Editor/PSXTriggerBoxEditor.cs
@@ -0,0 +1,94 @@
+using UnityEngine;
+using UnityEditor;
+using SplashEdit.RuntimeCode;
+
+namespace SplashEdit.EditorCode
+{
+ [CustomEditor(typeof(PSXTriggerBox))]
+ public class PSXTriggerBoxEditor : UnityEditor.Editor
+ {
+ private SerializedProperty sizeProp;
+ private SerializedProperty luaFileProp;
+
+ private void OnEnable()
+ {
+ sizeProp = serializedObject.FindProperty("size");
+ luaFileProp = serializedObject.FindProperty("luaFile");
+ }
+
+ public override void OnInspectorGUI()
+ {
+ serializedObject.Update();
+
+ EditorGUILayout.LabelField("PSX Trigger Box", EditorStyles.boldLabel);
+ EditorGUILayout.Space(4);
+
+ EditorGUILayout.PropertyField(sizeProp, new GUIContent("Size"));
+ EditorGUILayout.PropertyField(luaFileProp, new GUIContent("Lua Script"));
+
+ if (luaFileProp.objectReferenceValue != null)
+ {
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.Space(EditorGUI.indentLevel * 15);
+ if (GUILayout.Button("Edit", EditorStyles.miniButtonLeft, GUILayout.Width(50)))
+ AssetDatabase.OpenAsset(luaFileProp.objectReferenceValue);
+ if (GUILayout.Button("Clear", EditorStyles.miniButtonRight, GUILayout.Width(50)))
+ luaFileProp.objectReferenceValue = null;
+ GUILayout.FlexibleSpace();
+ EditorGUILayout.EndHorizontal();
+ }
+ else
+ {
+ EditorGUILayout.BeginHorizontal();
+ GUILayout.Space(EditorGUI.indentLevel * 15);
+ if (GUILayout.Button("Create Lua Script", EditorStyles.miniButton, GUILayout.Width(130)))
+ CreateNewLuaScript();
+ GUILayout.FlexibleSpace();
+ EditorGUILayout.EndHorizontal();
+ }
+
+ serializedObject.ApplyModifiedProperties();
+ }
+
+ private void CreateNewLuaScript()
+ {
+ var trigger = target as PSXTriggerBox;
+ string defaultName = trigger.gameObject.name.ToLower().Replace(" ", "_");
+ string path = EditorUtility.SaveFilePanelInProject(
+ "Create Lua Script", defaultName + ".lua", "lua",
+ "Create a new Lua script for this trigger box");
+
+ if (string.IsNullOrEmpty(path)) return;
+
+ string template =
+ "function onTriggerEnter(triggerIndex)\nend\n\nfunction onTriggerExit(triggerIndex)\nend\n";
+ System.IO.File.WriteAllText(path, template);
+ AssetDatabase.Refresh();
+
+ var luaFile = AssetDatabase.LoadAssetAtPath(path);
+ if (luaFile != null)
+ {
+ luaFileProp.objectReferenceValue = luaFile;
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+
+ [DrawGizmo(GizmoType.Selected | GizmoType.NonSelected)]
+ private static void DrawTriggerGizmo(PSXTriggerBox trigger, GizmoType gizmoType)
+ {
+ bool selected = (gizmoType & GizmoType.Selected) != 0;
+
+ Gizmos.color = selected ? new Color(0.2f, 1f, 0.3f, 0.8f) : new Color(0.2f, 1f, 0.3f, 0.25f);
+ Gizmos.matrix = trigger.transform.localToWorldMatrix;
+ Gizmos.DrawWireCube(Vector3.zero, trigger.Size);
+
+ if (selected)
+ {
+ Gizmos.color = new Color(0.2f, 1f, 0.3f, 0.08f);
+ Gizmos.DrawCube(Vector3.zero, trigger.Size);
+ }
+
+ Gizmos.matrix = Matrix4x4.identity;
+ }
+ }
+}
diff --git a/Editor/PSXTriggerBoxEditor.cs.meta b/Editor/PSXTriggerBoxEditor.cs.meta
new file mode 100644
index 0000000..103dd8f
--- /dev/null
+++ b/Editor/PSXTriggerBoxEditor.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 5bdf647efcaa11a469e2e99025e3a20e
\ No newline at end of file
diff --git a/Runtime/PSXCollisionExporter.cs b/Runtime/PSXCollisionExporter.cs
index 5df9af9..4b61693 100644
--- a/Runtime/PSXCollisionExporter.cs
+++ b/Runtime/PSXCollisionExporter.cs
@@ -15,7 +15,6 @@ namespace SplashEdit.RuntimeCode
Solid = 0x01,
Slope = 0x02,
Stairs = 0x04,
- Trigger = 0x08,
NoWalk = 0x10,
}
@@ -84,8 +83,8 @@ namespace SplashEdit.RuntimeCode
foreach (var exporter in exporters)
{
- // Dynamic objects are handled by the runtime collision system, skip them
- if (!exporter.StaticCollider && exporter.CollisionType != PSXCollisionType.None)
+ // Dynamic objects use runtime AABB colliders, skip them
+ if (exporter.CollisionType == PSXCollisionType.Dynamic)
continue;
PSXCollisionType effectiveType = exporter.CollisionType;
@@ -94,7 +93,7 @@ namespace SplashEdit.RuntimeCode
{
if (autoIncludeSolid)
{
- effectiveType = PSXCollisionType.Solid;
+ effectiveType = PSXCollisionType.Static;
autoIncluded++;
}
else
@@ -103,11 +102,8 @@ namespace SplashEdit.RuntimeCode
}
}
- // Get the collision mesh (custom or render mesh)
MeshFilter mf = exporter.GetComponent();
- Mesh collisionMesh = exporter.CustomCollisionMesh != null
- ? exporter.CustomCollisionMesh
- : mf?.sharedMesh;
+ Mesh collisionMesh = mf?.sharedMesh;
if (collisionMesh == null)
continue;
@@ -133,37 +129,25 @@ namespace SplashEdit.RuntimeCode
// Determine surface flags
byte flags = 0;
- if (effectiveType == PSXCollisionType.Trigger)
+ // Floor-like: normal.y > cosWalkable
+ float dotUp = normal.y;
+
+ if (dotUp > cosWalkable)
{
- flags = (byte)PSXSurfaceFlag.Trigger;
+ flags = (byte)PSXSurfaceFlag.Solid;
+
+ if (dotUp < 0.95f && dotUp > cosWalkable)
+ {
+ flags |= (byte)PSXSurfaceFlag.Stairs;
+ }
+ }
+ else if (dotUp > 0.0f)
+ {
+ flags = (byte)(PSXSurfaceFlag.Solid | PSXSurfaceFlag.Slope);
}
else
{
- // Floor-like: normal.y > cosWalkable
- // Note: Unity Y is up; PS1 Y is down. We export in Unity space
- // and convert to PS1 space during WriteToBinary.
- float dotUp = normal.y;
-
- if (dotUp > cosWalkable)
- {
- flags = (byte)PSXSurfaceFlag.Solid;
-
- // Check if stairs (tagged on exporter or steep-ish)
- if (dotUp < 0.95f && dotUp > cosWalkable)
- {
- flags |= (byte)PSXSurfaceFlag.Stairs;
- }
- }
- else if (dotUp > 0.0f)
- {
- // Slope too steep to walk on
- flags = (byte)(PSXSurfaceFlag.Solid | PSXSurfaceFlag.Slope);
- }
- else
- {
- // Wall or ceiling
- flags = (byte)PSXSurfaceFlag.Solid;
- }
+ flags = (byte)PSXSurfaceFlag.Solid;
}
_allTriangles.Add(new CollisionTriExport
diff --git a/Runtime/PSXLoaderPackWriter.cs b/Runtime/PSXLoaderPackWriter.cs
index 2594898..ec57451 100644
--- a/Runtime/PSXLoaderPackWriter.cs
+++ b/Runtime/PSXLoaderPackWriter.cs
@@ -187,23 +187,48 @@ namespace SplashEdit.RuntimeCode
}
}
+ // ── Font pixel data (written BEFORE the UI table, alongside atlas/CLUT data) ──
+ // The C++ parser expects canvas descriptors immediately after font descriptors
+ // (font pixel data is at absolute offsets, not inline). Write pixel data here
+ // so it doesn't sit between font descriptors and canvas descriptors.
+ List fontDataOffsetPositions = new List();
+ List fontPixelDataPositions = new List();
+ if (fonts != null)
+ {
+ for (int fi = 0; fi < fonts.Length; fi++)
+ {
+ var font = fonts[fi];
+ if (font.PixelData == null || font.PixelData.Length == 0)
+ {
+ fontPixelDataPositions.Add(0);
+ continue;
+ }
+
+ AlignToFourBytes(writer);
+ long dataPos = writer.BaseStream.Position;
+ writer.Write(font.PixelData);
+ fontPixelDataPositions.Add(dataPos);
+ }
+ }
+
// ── UI table (same format as splashpack UI section) ──
AlignToFourBytes(writer);
long uiTableStart = writer.BaseStream.Position;
// ── Font descriptors (112 bytes each) ──
- List fontDataOffsetPositions = new List();
if (fonts != null)
{
- foreach (var font in fonts)
+ for (int fi = 0; fi < fonts.Length; fi++)
{
+ var font = fonts[fi];
writer.Write(font.GlyphWidth); // [0]
writer.Write(font.GlyphHeight); // [1]
writer.Write(font.VramX); // [2-3]
writer.Write(font.VramY); // [4-5]
writer.Write(font.TextureHeight); // [6-7]
- fontDataOffsetPositions.Add(writer.BaseStream.Position);
- writer.Write((uint)0); // [8-11] dataOffset placeholder
+ // dataOffset: use the pre-written pixel data position
+ long pixPos = fontPixelDataPositions[fi];
+ writer.Write((uint)pixPos); // [8-11] dataOffset (0 if no data)
writer.Write((uint)(font.PixelData?.Length ?? 0)); // [12-15] dataSize
if (font.AdvanceWidths != null && font.AdvanceWidths.Length >= 96)
writer.Write(font.AdvanceWidths, 0, 96);
@@ -212,26 +237,13 @@ namespace SplashEdit.RuntimeCode
}
}
- // ── Font pixel data ──
- if (fonts != null)
- {
- for (int fi = 0; fi < fonts.Length; fi++)
- {
- var font = fonts[fi];
- if (font.PixelData == null || font.PixelData.Length == 0) continue;
-
- AlignToFourBytes(writer);
- long dataPos = writer.BaseStream.Position;
- writer.Write(font.PixelData);
-
- long curPos = writer.BaseStream.Position;
- writer.Seek((int)fontDataOffsetPositions[fi], SeekOrigin.Begin);
- writer.Write((uint)dataPos);
- writer.Seek((int)curPos, SeekOrigin.Begin);
- }
- }
+ // Canvas descriptors now follow immediately after font descriptors
+ // (no font pixel data in between — it was written above).
// ── Canvas descriptor (12 bytes) ──
+ // Must align here: the C++ parser aligns fontDataEnd to 4 bytes
+ // when skipping past font pixel data to find the canvas descriptor.
+ AlignToFourBytes(writer);
var elements = canvas.Elements ?? new PSXUIElementData[0];
string cvName = canvas.Name ?? "loading";
if (cvName.Length > 24) cvName = cvName.Substring(0, 24);
diff --git a/Runtime/PSXNavRegionBuilder.cs b/Runtime/PSXNavRegionBuilder.cs
index 084f596..1057896 100644
--- a/Runtime/PSXNavRegionBuilder.cs
+++ b/Runtime/PSXNavRegionBuilder.cs
@@ -312,6 +312,8 @@ namespace SplashEdit.RuntimeCode
{
foreach (var exporter in exporters)
{
+ if (exporter.CollisionType == PSXCollisionType.Dynamic)
+ continue;
MeshFilter mf = exporter.GetComponent();
Mesh mesh = mf?.sharedMesh;
diff --git a/Runtime/PSXObjectExporter.cs b/Runtime/PSXObjectExporter.cs
index 10510fe..78c7f2d 100644
--- a/Runtime/PSXObjectExporter.cs
+++ b/Runtime/PSXObjectExporter.cs
@@ -8,9 +8,8 @@ namespace SplashEdit.RuntimeCode
public enum PSXCollisionType
{
None = 0,
- Solid = 1,
- Trigger = 2,
- Platform = 3
+ Static = 1,
+ Dynamic = 2
}
[RequireComponent(typeof(Renderer))]
@@ -29,22 +28,11 @@ namespace SplashEdit.RuntimeCode
[SerializeField] private PSXBPP bitDepth = PSXBPP.TEX_8BIT;
[SerializeField] private LuaFile luaFile;
+ [FormerlySerializedAs("collisionType")]
[SerializeField] private PSXCollisionType collisionType = PSXCollisionType.None;
- [SerializeField] private bool staticCollider = true;
- [SerializeField] private bool exportCollisionMesh = false;
- [SerializeField] private Mesh customCollisionMesh;
- [Range(1, 8)]
- [SerializeField] private int collisionLayer = 1;
-
- [SerializeField] private bool generateNavigation = false;
public PSXBPP BitDepth => bitDepth;
public PSXCollisionType CollisionType => collisionType;
- public bool StaticCollider => staticCollider;
- public bool ExportCollisionMesh => exportCollisionMesh;
- public Mesh CustomCollisionMesh => customCollisionMesh;
- public int CollisionLayer => collisionLayer;
- public bool GenerateNavigation => generateNavigation;
private readonly Dictionary<(int, PSXBPP), PSXTexture2D> cache = new();
diff --git a/Runtime/PSXSceneExporter.cs b/Runtime/PSXSceneExporter.cs
index a439c30..15da127 100644
--- a/Runtime/PSXSceneExporter.cs
+++ b/Runtime/PSXSceneExporter.cs
@@ -58,6 +58,7 @@ namespace SplashEdit.RuntimeCode
// Component arrays
private PSXInteractable[] _interactables;
private PSXAudioSource[] _audioSources;
+ private PSXTriggerBox[] _triggerBoxes;
// Phase 3+4: World collision and nav regions
private PSXCollisionExporter _collisionExporter;
@@ -120,6 +121,7 @@ namespace SplashEdit.RuntimeCode
// Collect components
_interactables = FindObjectsByType(FindObjectsSortMode.None);
_audioSources = FindObjectsByType(FindObjectsSortMode.None);
+ _triggerBoxes = FindObjectsByType(FindObjectsSortMode.None);
// Collect UI image textures for VRAM packing alongside 3D textures
PSXUIImage[] uiImages = FindObjectsByType(FindObjectsSortMode.None);
@@ -164,7 +166,7 @@ namespace SplashEdit.RuntimeCode
_collisionExporter = new PSXCollisionExporter();
_collisionExporter.Build(_exporters, GTEScaling);
if (_collisionExporter.MeshCount == 0)
- Debug.LogWarning("No collision meshes! Set CollisionType=Solid on your floor/wall objects.");
+ Debug.LogWarning("No collision meshes! Set CollisionType=Static on your floor/wall objects.");
// Phase 4+5: Room volumes are needed by BOTH the nav region builder
// (for spatial room assignment) and the room builder (for triangle assignment).
@@ -295,6 +297,7 @@ namespace SplashEdit.RuntimeCode
audioSources = _audioSources,
canvases = _canvases,
fonts = _fonts,
+ triggerBoxes = _triggerBoxes,
};
PSXSceneWriter.Write(path, in scene, (msg, type) =>
diff --git a/Runtime/PSXSceneWriter.cs b/Runtime/PSXSceneWriter.cs
index ff2ebcf..c13ce88 100644
--- a/Runtime/PSXSceneWriter.cs
+++ b/Runtime/PSXSceneWriter.cs
@@ -39,6 +39,9 @@ namespace SplashEdit.RuntimeCode
// Custom fonts (v13, embedded in UI block)
public PSXFontData[] fonts;
+ // Trigger boxes (v16)
+ public PSXTriggerBox[] triggerBoxes;
+
// Player
public Vector3 playerPos;
public Quaternion playerRot;
@@ -105,32 +108,40 @@ namespace SplashEdit.RuntimeCode
}
if (scene.sceneLuaFile != null && !luaFiles.Contains(scene.sceneLuaFile))
luaFiles.Add(scene.sceneLuaFile);
+ // Trigger box Lua files
+ if (scene.triggerBoxes != null)
+ {
+ foreach (var tb in scene.triggerBoxes)
+ {
+ if (tb.LuaFile != null && !luaFiles.Contains(tb.LuaFile))
+ luaFiles.Add(tb.LuaFile);
+ }
+ }
using (BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.Create)))
{
int colliderCount = 0;
foreach (var e in scene.exporters)
{
- if (e.CollisionType == PSXCollisionType.None || e.StaticCollider)
- continue;
- Mesh cm = e.CustomCollisionMesh != null
- ? e.CustomCollisionMesh
- : e.GetComponent()?.sharedMesh;
- if (cm != null)
+ if (e.CollisionType != PSXCollisionType.Dynamic) continue;
+ MeshFilter mf = e.GetComponent();
+ if (mf?.sharedMesh != null)
colliderCount++;
}
+ int triggerBoxCount = scene.triggerBoxes?.Length ?? 0;
+
// Build exporter index lookup for components
Dictionary exporterIndex = new Dictionary();
for (int i = 0; i < scene.exporters.Length; i++)
exporterIndex[scene.exporters[i]] = i;
// ──────────────────────────────────────────────────────
- // Header (104 bytes — splashpack v15)
+ // Header (104 bytes — splashpack v16)
// ──────────────────────────────────────────────────────
writer.Write('S');
writer.Write('P');
- writer.Write((ushort)15);
+ writer.Write((ushort)16);
writer.Write((ushort)luaFiles.Count);
writer.Write((ushort)scene.exporters.Length);
writer.Write((ushort)scene.atlases.Length);
@@ -156,7 +167,7 @@ namespace SplashEdit.RuntimeCode
writer.Write((ushort)scene.bvh.TriangleRefCount);
writer.Write((ushort)scene.sceneType);
- writer.Write((ushort)0); // pad0
+ writer.Write((ushort)triggerBoxCount); // was pad0
writer.Write((ushort)scene.collisionExporter.MeshCount);
writer.Write((ushort)scene.collisionExporter.TriangleCount);
@@ -278,29 +289,50 @@ namespace SplashEdit.RuntimeCode
}
// ──────────────────────────────────────────────────────
- // Collider metadata (32 bytes each)
+ // Collider metadata (32 bytes each) — Dynamic objects only
// ──────────────────────────────────────────────────────
for (int exporterIdx = 0; exporterIdx < scene.exporters.Length; exporterIdx++)
{
PSXObjectExporter exporter = scene.exporters[exporterIdx];
- if (exporter.CollisionType == PSXCollisionType.None || exporter.StaticCollider)
- continue;
+ if (exporter.CollisionType != PSXCollisionType.Dynamic) continue;
MeshFilter meshFilter = exporter.GetComponent();
- Mesh collisionMesh = exporter.CustomCollisionMesh != null
- ? exporter.CustomCollisionMesh
- : meshFilter?.sharedMesh;
+ Mesh renderMesh = meshFilter?.sharedMesh;
+ if (renderMesh == null) continue;
- if (collisionMesh == null)
- continue;
+ WriteWorldAABB(writer, exporter, renderMesh.bounds, gte);
- WriteWorldAABB(writer, exporter, collisionMesh.bounds, gte);
-
- // Collision metadata (8 bytes)
- writer.Write((byte)exporter.CollisionType);
- writer.Write((byte)(1 << (exporter.CollisionLayer - 1)));
+ writer.Write((byte)1); // CollisionType::Solid on C++ side
+ writer.Write((byte)0xFF); // layerMask (all layers)
writer.Write((ushort)exporterIdx);
- writer.Write((uint)0); // padding
+ writer.Write((uint)0);
+ }
+
+ // ──────────────────────────────────────────────────────
+ // Trigger box metadata (32 bytes each)
+ // ──────────────────────────────────────────────────────
+ if (scene.triggerBoxes != null)
+ {
+ foreach (var tb in scene.triggerBoxes)
+ {
+ Bounds wb = tb.GetWorldBounds();
+ Vector3 wMin = wb.min;
+ Vector3 wMax = wb.max;
+
+ writer.Write(PSXTrig.ConvertWorldToFixed12(wMin.x / gte));
+ writer.Write(PSXTrig.ConvertWorldToFixed12(-wMax.y / gte));
+ writer.Write(PSXTrig.ConvertWorldToFixed12(wMin.z / gte));
+ writer.Write(PSXTrig.ConvertWorldToFixed12(wMax.x / gte));
+ writer.Write(PSXTrig.ConvertWorldToFixed12(-wMin.y / gte));
+ writer.Write(PSXTrig.ConvertWorldToFixed12(wMax.z / gte));
+
+ if (tb.LuaFile != null)
+ writer.Write((short)luaFiles.IndexOf(tb.LuaFile));
+ else
+ writer.Write((short)-1);
+ writer.Write((ushort)0); // padding
+ writer.Write((uint)0); // padding
+ }
}
// ──────────────────────────────────────────────────────
diff --git a/Runtime/PSXTriggerBox.cs b/Runtime/PSXTriggerBox.cs
new file mode 100644
index 0000000..ea0e94e
--- /dev/null
+++ b/Runtime/PSXTriggerBox.cs
@@ -0,0 +1,37 @@
+using UnityEngine;
+
+namespace SplashEdit.RuntimeCode
+{
+ public class PSXTriggerBox : MonoBehaviour
+ {
+ [SerializeField] private Vector3 size = Vector3.one;
+ [SerializeField] private LuaFile luaFile;
+
+ public Vector3 Size => size;
+ public LuaFile LuaFile => luaFile;
+
+ public Bounds GetWorldBounds()
+ {
+ Vector3 halfSize = size * 0.5f;
+ Vector3 worldCenter = transform.position;
+ Vector3 worldMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
+ Vector3 worldMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
+
+ for (int i = 0; i < 8; i++)
+ {
+ Vector3 corner = new Vector3(
+ (i & 1) != 0 ? halfSize.x : -halfSize.x,
+ (i & 2) != 0 ? halfSize.y : -halfSize.y,
+ (i & 4) != 0 ? halfSize.z : -halfSize.z
+ );
+ Vector3 world = transform.TransformPoint(corner);
+ worldMin = Vector3.Min(worldMin, world);
+ worldMax = Vector3.Max(worldMax, world);
+ }
+
+ Bounds b = new Bounds();
+ b.SetMinMax(worldMin, worldMax);
+ return b;
+ }
+ }
+}
diff --git a/Runtime/PSXTriggerBox.cs.meta b/Runtime/PSXTriggerBox.cs.meta
new file mode 100644
index 0000000..c3b808d
--- /dev/null
+++ b/Runtime/PSXTriggerBox.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 72b9d8d2e8eafba46a30ba345beb9692
\ No newline at end of file
diff --git a/Runtime/PSXUIExporter.cs b/Runtime/PSXUIExporter.cs
index f2701cc..b11282e 100644
--- a/Runtime/PSXUIExporter.cs
+++ b/Runtime/PSXUIExporter.cs
@@ -161,10 +161,23 @@ namespace SplashEdit.RuntimeCode
List elements = new List();
+ Debug.Log($"[UIExporter] Canvas '{canvas.CanvasName}' on '{canvas.gameObject.name}' " +
+ $"canvasW={canvasW} canvasH={canvasH} childCount={canvas.transform.childCount}");
+
+ // Log what each collector finds
+ int prevCount = elements.Count;
CollectImages(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements);
+ Debug.Log($"[UIExporter] Images: {elements.Count - prevCount}");
+ prevCount = elements.Count;
CollectBoxes(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements);
+ Debug.Log($"[UIExporter] Boxes: {elements.Count - prevCount}");
+ prevCount = elements.Count;
CollectTexts(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements, uniqueFonts);
+ Debug.Log($"[UIExporter] Texts: {elements.Count - prevCount}");
+ prevCount = elements.Count;
CollectProgressBars(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements);
+ Debug.Log($"[UIExporter] ProgressBars: {elements.Count - prevCount}");
+ Debug.Log($"[UIExporter] TOTAL elements: {elements.Count}");
string name = canvas.CanvasName ?? "canvas";
if (name.Length > 24) name = name.Substring(0, 24);
diff --git a/Sample/Lua/example.lua b/Sample/Lua/example.lua
index a2510bc..a7aba16 100644
--- a/Sample/Lua/example.lua
+++ b/Sample/Lua/example.lua
@@ -57,7 +57,7 @@ end
-- ============================================================================
--- Called when this object's collider overlaps another.
-function onCollision(self, other)
+function onCollideWithPlayer(self)
Debug.Log("Collision with another object")
end
diff --git a/tools/LUA_VSCODE_SETUP.md b/tools/LUA_VSCODE_SETUP.md
index 71f3926..a7dce27 100644
--- a/tools/LUA_VSCODE_SETUP.md
+++ b/tools/LUA_VSCODE_SETUP.md
@@ -22,7 +22,7 @@ Add (or merge) the following into your workspace `.vscode/settings.json`:
// Event callbacks the engine calls — not "undefined" globals
"onCreate", "onUpdate", "onDestroy",
"onEnable", "onDisable",
- "onCollision", "onInteract",
+ "onCollideWithPlayer", "onInteract",
"onTriggerEnter", "onTriggerStay", "onTriggerExit",
"onButtonPress", "onButtonRelease"
]
diff --git a/tools/splash_api.lua b/tools/splash_api.lua
index b73e84a..fefc53e 100644
--- a/tools/splash_api.lua
+++ b/tools/splash_api.lua
@@ -351,7 +351,7 @@ function onDisable(self) end
--- Called when this object's collider overlaps another.
--- @param self EntityHandle
--- @param other EntityHandle
-function onCollision(self, other) end
+function onCollideWithPlayer(self) end
--- Called when the player interacts with this object (PSXInteractable).
--- @param self EntityHandle