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