using UnityEngine; using UnityEditor; using SplashEdit.RuntimeCode; using System.Linq; namespace SplashEdit.EditorCode { [CustomEditor(typeof(PSXObjectExporter))] [CanEditMultipleObjects] public class PSXObjectExporterEditor : UnityEditor.Editor { private SerializedProperty isActiveProp; private SerializedProperty bitDepthProp; private SerializedProperty luaFileProp; private SerializedProperty collisionTypeProp; private MeshFilter meshFilter; private MeshRenderer meshRenderer; private int triangleCount; private int vertexCount; private bool showExport = true; private bool showCollision = true; private void OnEnable() { isActiveProp = serializedObject.FindProperty("isActive"); bitDepthProp = serializedObject.FindProperty("bitDepth"); luaFileProp = serializedObject.FindProperty("luaFile"); collisionTypeProp = serializedObject.FindProperty("collisionType"); CacheMeshInfo(); } private void CacheMeshInfo() { var exporter = target as PSXObjectExporter; if (exporter == null) return; meshFilter = exporter.GetComponent(); meshRenderer = exporter.GetComponent(); if (meshFilter != null && meshFilter.sharedMesh != null) { triangleCount = meshFilter.sharedMesh.triangles.Length / 3; vertexCount = meshFilter.sharedMesh.vertexCount; } } public override void OnInspectorGUI() { serializedObject.Update(); DrawHeader(); EditorGUILayout.Space(4); if (!isActiveProp.boolValue) { EditorGUILayout.LabelField("Object will be skipped during export.", PSXEditorStyles.InfoBox); serializedObject.ApplyModifiedProperties(); return; } DrawMeshSummary(); PSXEditorStyles.DrawSeparator(6, 6); DrawExportSection(); PSXEditorStyles.DrawSeparator(6, 6); DrawCollisionSection(); PSXEditorStyles.DrawSeparator(6, 6); DrawActions(); serializedObject.ApplyModifiedProperties(); } private new void DrawHeader() { EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle); EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(isActiveProp, GUIContent.none, GUILayout.Width(18)); var exporter = target as PSXObjectExporter; EditorGUILayout.LabelField(exporter.gameObject.name, PSXEditorStyles.CardHeaderStyle); EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); } private void DrawMeshSummary() { if (meshFilter == null || meshFilter.sharedMesh == null) { EditorGUILayout.LabelField("No mesh on this object.", PSXEditorStyles.InfoBox); return; } EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField($"{triangleCount} tris", PSXEditorStyles.RichLabel, GUILayout.Width(60)); EditorGUILayout.LabelField($"{vertexCount} verts", PSXEditorStyles.RichLabel, GUILayout.Width(70)); int subMeshCount = meshFilter.sharedMesh.subMeshCount; if (subMeshCount > 1) EditorGUILayout.LabelField($"{subMeshCount} submeshes", PSXEditorStyles.RichLabel, GUILayout.Width(90)); int matCount = meshRenderer != null ? meshRenderer.sharedMaterials.Length : 0; int textured = meshRenderer != null ? meshRenderer.sharedMaterials.Count(m => m != null && m.mainTexture != null) : 0; if (textured > 0) EditorGUILayout.LabelField($"{textured}/{matCount} textured", PSXEditorStyles.RichLabel); else EditorGUILayout.LabelField("untextured", PSXEditorStyles.RichLabel); EditorGUILayout.EndHorizontal(); } private void DrawExportSection() { showExport = EditorGUILayout.Foldout(showExport, "Export", true, PSXEditorStyles.FoldoutHeader); if (!showExport) return; EditorGUI.indentLevel++; EditorGUILayout.PropertyField(bitDepthProp, new GUIContent("Bit Depth")); EditorGUILayout.PropertyField(luaFileProp, new GUIContent("Lua Script")); if (luaFileProp.objectReferenceValue != null) { EditorGUILayout.BeginHorizontal(); GUILayout.Space(EditorGUI.indentLevel * 15); if (GUILayout.Button("Edit", PSXEditorStyles.SecondaryButton, GUILayout.Width(50))) AssetDatabase.OpenAsset(luaFileProp.objectReferenceValue); if (GUILayout.Button("Clear", PSXEditorStyles.SecondaryButton, GUILayout.Width(50))) luaFileProp.objectReferenceValue = null; GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); } else { EditorGUILayout.BeginHorizontal(); GUILayout.Space(EditorGUI.indentLevel * 15); if (GUILayout.Button("Create Lua Script", PSXEditorStyles.SecondaryButton, GUILayout.Width(130))) CreateNewLuaScript(); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); } EditorGUI.indentLevel--; } private void DrawCollisionSection() { showCollision = EditorGUILayout.Foldout(showCollision, "Collision", true, PSXEditorStyles.FoldoutHeader); if (!showCollision) return; EditorGUI.indentLevel++; EditorGUILayout.PropertyField(collisionTypeProp, new GUIContent("Type")); var collType = (PSXCollisionType)collisionTypeProp.enumValueIndex; if (collType == PSXCollisionType.Static) { 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); } EditorGUI.indentLevel--; } private void DrawActions() { EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Select Scene Exporter", PSXEditorStyles.SecondaryButton)) { var se = FindFirstObjectByType(); if (se != null) Selection.activeGameObject = se.gameObject; else EditorUtility.DisplayDialog("Not Found", "No PSXSceneExporter in scene.", "OK"); } EditorGUILayout.EndHorizontal(); } private void CreateNewLuaScript() { var exporter = target as PSXObjectExporter; string defaultName = exporter.gameObject.name.ToLower().Replace(" ", "_"); string path = EditorUtility.SaveFilePanelInProject( "Create Lua Script", defaultName + ".lua", "lua", "Create a new Lua script for this object"); if (string.IsNullOrEmpty(path)) return; string template = $"function onCreate(self)\nend\n\nfunction onUpdate(self, dt)\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 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); } } }