Compare commits
19 Commits
4aa4e49424
...
lua
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01b636f3e2 | ||
|
|
61fbca17a7 | ||
| 275b4e891d | |||
| 132ab479c2 | |||
| eff03e0e1a | |||
| 5e862f8c0b | |||
| 62bf7d8b2d | |||
|
|
a251eeaed5 | ||
|
|
13ed569eaf | ||
|
|
45a552be5a | ||
|
|
24d0c1fa07 | ||
|
|
1c48b8b425 | ||
|
|
d29ef569b3 | ||
|
|
6bf74fa929 | ||
|
|
d5be174247 | ||
|
|
a8aa674a9c | ||
|
|
5fffcea6cf | ||
|
|
8914ba35cc | ||
|
|
bb8e0804f5 |
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45b95f68e129e6f478d509d59f39bc6e
|
||||
guid: d7e9b1c3e60e2ff48be3cd61902ba6f1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
BIN
Data/SPLASHLICENSE.DAT
Normal file
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5688ba03f531c3245a838793c0ae7f93
|
||||
guid: 244f6913a02805e4aa3cebdd1240cab7
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
872
Debug.unity
@@ -1,872 +0,0 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!29 &1
|
||||
OcclusionCullingSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_OcclusionBakeSettings:
|
||||
smallestOccluder: 5
|
||||
smallestHole: 0.25
|
||||
backfaceThreshold: 100
|
||||
m_SceneGUID: 00000000000000000000000000000000
|
||||
m_OcclusionCullingData: {fileID: 0}
|
||||
--- !u!104 &2
|
||||
RenderSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 10
|
||||
m_Fog: 0
|
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
m_FogMode: 3
|
||||
m_FogDensity: 0.01
|
||||
m_LinearFogStart: 0
|
||||
m_LinearFogEnd: 300
|
||||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||
m_AmbientIntensity: 1
|
||||
m_AmbientMode: 0
|
||||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_HaloStrength: 0.5
|
||||
m_FlareStrength: 1
|
||||
m_FlareFadeSpeed: 3
|
||||
m_HaloTexture: {fileID: 0}
|
||||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_DefaultReflectionMode: 0
|
||||
m_DefaultReflectionResolution: 128
|
||||
m_ReflectionBounces: 1
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 0}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 13
|
||||
m_BakeOnSceneLoad: 0
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_EnvironmentLightingMode: 0
|
||||
m_EnableBakedLightmaps: 1
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_LightmapEditorSettings:
|
||||
serializedVersion: 12
|
||||
m_Resolution: 2
|
||||
m_BakeResolution: 40
|
||||
m_AtlasSize: 1024
|
||||
m_AO: 0
|
||||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAmbientOcclusion: 0
|
||||
m_Padding: 2
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
m_TextureCompression: 1
|
||||
m_ReflectionCompression: 2
|
||||
m_MixedBakeMode: 2
|
||||
m_BakeBackend: 1
|
||||
m_PVRSampling: 1
|
||||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 512
|
||||
m_PVRBounces: 2
|
||||
m_PVREnvironmentSampleCount: 256
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_PVRFilteringMode: 1
|
||||
m_PVRDenoiserTypeDirect: 1
|
||||
m_PVRDenoiserTypeIndirect: 1
|
||||
m_PVRDenoiserTypeAO: 1
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVREnvironmentMIS: 1
|
||||
m_PVRCulling: 1
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 1
|
||||
m_PVRFilteringGaussRadiusAO: 1
|
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_ExportTrainingData: 0
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_LightProbeSampleCountMultiplier: 4
|
||||
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_LightingSettings: {fileID: 0}
|
||||
--- !u!196 &4
|
||||
NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 3
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
agentSlope: 45
|
||||
agentClimb: 0.4
|
||||
ledgeDropHeight: 0
|
||||
maxJumpAcrossDistance: 0
|
||||
minRegionArea: 2
|
||||
manualCellSize: 0
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
buildHeightMesh: 0
|
||||
maxJobWorkers: 0
|
||||
preserveTilesOutsideBounds: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &283344192
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 283344197}
|
||||
- component: {fileID: 283344196}
|
||||
- component: {fileID: 283344195}
|
||||
- component: {fileID: 283344194}
|
||||
- component: {fileID: 283344193}
|
||||
m_Layer: 0
|
||||
m_Name: f (1)
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &283344193
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 283344192}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bea0f31a495202580ac77bd9fd6e99f2, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
IsActive: 1
|
||||
bitDepth: 8
|
||||
luaFile: {fileID: 0}
|
||||
previewNormals: 0
|
||||
normalPreviewLength: 0.5
|
||||
--- !u!65 &283344194
|
||||
BoxCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 283344192}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_Size: {x: 1, y: 1, z: 1}
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!23 &283344195
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 283344192}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 0
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!33 &283344196
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 283344192}
|
||||
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!4 &283344197
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 283344192}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0.1479161, y: 0.37224695, z: -0.33835596, w: 0.85150945}
|
||||
m_LocalPosition: {x: -30.19757, y: 0, z: 8.341}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 47.226, z: -43.342}
|
||||
--- !u!1 &1293128684
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1293128689}
|
||||
- component: {fileID: 1293128688}
|
||||
- component: {fileID: 1293128687}
|
||||
- component: {fileID: 1293128686}
|
||||
- component: {fileID: 1293128685}
|
||||
m_Layer: 0
|
||||
m_Name: f
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1293128685
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1293128684}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bea0f31a495202580ac77bd9fd6e99f2, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
IsActive: 1
|
||||
bitDepth: 8
|
||||
luaFile: {fileID: 0}
|
||||
previewNormals: 0
|
||||
normalPreviewLength: 0.5
|
||||
--- !u!65 &1293128686
|
||||
BoxCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1293128684}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_Size: {x: 1, y: 1, z: 1}
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!23 &1293128687
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1293128684}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 0
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!33 &1293128688
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1293128684}
|
||||
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!4 &1293128689
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1293128684}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0.13355339, y: 0.35044792, z: -0.3301185, w: 0.8662399}
|
||||
m_LocalPosition: {x: -30.19757, y: 0, z: 10.84006}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 44.053, z: -41.723}
|
||||
--- !u!1 &1331845601
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1331845603}
|
||||
- component: {fileID: 1331845602}
|
||||
m_Layer: 0
|
||||
m_Name: Exp
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1331845602
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1331845601}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: ab5195ad94fd173cfb6d48ee06eaf245, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
GTEScaling: 100
|
||||
SceneLuaFile: {fileID: 0}
|
||||
BSPPreviewDepth: 2
|
||||
--- !u!4 &1331845603
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1331845601}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: -31.5414, y: 0, z: 10.89769}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1337453585
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1337453587}
|
||||
- component: {fileID: 1337453586}
|
||||
m_Layer: 0
|
||||
m_Name: Directional Light
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!108 &1337453586
|
||||
Light:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1337453585}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 11
|
||||
m_Type: 1
|
||||
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
|
||||
m_Intensity: 1
|
||||
m_Range: 10
|
||||
m_SpotAngle: 30
|
||||
m_InnerSpotAngle: 21.80208
|
||||
m_CookieSize: 10
|
||||
m_Shadows:
|
||||
m_Type: 2
|
||||
m_Resolution: -1
|
||||
m_CustomResolution: -1
|
||||
m_Strength: 1
|
||||
m_Bias: 0.05
|
||||
m_NormalBias: 0.4
|
||||
m_NearPlane: 0.2
|
||||
m_CullingMatrixOverride:
|
||||
e00: 1
|
||||
e01: 0
|
||||
e02: 0
|
||||
e03: 0
|
||||
e10: 0
|
||||
e11: 1
|
||||
e12: 0
|
||||
e13: 0
|
||||
e20: 0
|
||||
e21: 0
|
||||
e22: 1
|
||||
e23: 0
|
||||
e30: 0
|
||||
e31: 0
|
||||
e32: 0
|
||||
e33: 1
|
||||
m_UseCullingMatrixOverride: 0
|
||||
m_Cookie: {fileID: 0}
|
||||
m_DrawHalo: 0
|
||||
m_Flare: {fileID: 0}
|
||||
m_RenderMode: 0
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingLayerMask: 1
|
||||
m_Lightmapping: 4
|
||||
m_LightShadowCasterMode: 0
|
||||
m_AreaSize: {x: 1, y: 1}
|
||||
m_BounceIntensity: 1
|
||||
m_ColorTemperature: 6570
|
||||
m_UseColorTemperature: 0
|
||||
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_UseBoundingSphereOverride: 0
|
||||
m_UseViewFrustumForShadowCasterCull: 1
|
||||
m_ForceVisible: 0
|
||||
m_ShadowRadius: 0
|
||||
m_ShadowAngle: 0
|
||||
m_LightUnit: 1
|
||||
m_LuxAtDistance: 1
|
||||
m_EnableSpotReflector: 1
|
||||
--- !u!4 &1337453587
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1337453585}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
|
||||
m_LocalPosition: {x: 0, y: 3, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
|
||||
--- !u!1 &1388445967
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1388445970}
|
||||
- component: {fileID: 1388445969}
|
||||
- component: {fileID: 1388445968}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!81 &1388445968
|
||||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1388445967}
|
||||
m_Enabled: 1
|
||||
--- !u!20 &1388445969
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1388445967}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_ClearFlags: 1
|
||||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
||||
m_projectionMatrixMode: 1
|
||||
m_GateFitMode: 2
|
||||
m_FOVAxisMode: 0
|
||||
m_Iso: 200
|
||||
m_ShutterSpeed: 0.005
|
||||
m_Aperture: 16
|
||||
m_FocusDistance: 10
|
||||
m_FocalLength: 50
|
||||
m_BladeCount: 5
|
||||
m_Curvature: {x: 2, y: 11}
|
||||
m_BarrelClipping: 0.25
|
||||
m_Anamorphism: 0
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
near clip plane: 0.3
|
||||
far clip plane: 1000
|
||||
field of view: 60
|
||||
orthographic: 0
|
||||
orthographic size: 5
|
||||
m_Depth: -1
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingPath: -1
|
||||
m_TargetTexture: {fileID: 0}
|
||||
m_TargetDisplay: 0
|
||||
m_TargetEye: 3
|
||||
m_HDR: 1
|
||||
m_AllowMSAA: 1
|
||||
m_AllowDynamicResolution: 0
|
||||
m_ForceIntoRT: 0
|
||||
m_OcclusionCulling: 1
|
||||
m_StereoConvergence: 10
|
||||
m_StereoSeparation: 0.022
|
||||
--- !u!4 &1388445970
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1388445967}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1392840323
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1392840328}
|
||||
- component: {fileID: 1392840327}
|
||||
- component: {fileID: 1392840326}
|
||||
- component: {fileID: 1392840325}
|
||||
- component: {fileID: 1392840324}
|
||||
m_Layer: 0
|
||||
m_Name: f (2)
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1392840324
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1392840323}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bea0f31a495202580ac77bd9fd6e99f2, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
IsActive: 1
|
||||
bitDepth: 8
|
||||
luaFile: {fileID: 0}
|
||||
previewNormals: 0
|
||||
normalPreviewLength: 0.5
|
||||
--- !u!65 &1392840325
|
||||
BoxCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1392840323}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_Size: {x: 1, y: 1, z: 1}
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!23 &1392840326
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1392840323}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 0
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!33 &1392840327
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1392840323}
|
||||
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!4 &1392840328
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1392840323}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0.409346, y: -0.7736037, z: 0.22623056, w: 0.42754278}
|
||||
m_LocalPosition: {x: -30.19757, y: 2.041, z: 10.84006}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: -122.144, z: 55.77}
|
||||
--- !u!1 &1513869424
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1513869429}
|
||||
- component: {fileID: 1513869428}
|
||||
- component: {fileID: 1513869427}
|
||||
- component: {fileID: 1513869426}
|
||||
- component: {fileID: 1513869425}
|
||||
m_Layer: 0
|
||||
m_Name: f (3)
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1513869425
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1513869424}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bea0f31a495202580ac77bd9fd6e99f2, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
IsActive: 1
|
||||
bitDepth: 8
|
||||
luaFile: {fileID: 0}
|
||||
previewNormals: 0
|
||||
normalPreviewLength: 0.5
|
||||
--- !u!65 &1513869426
|
||||
BoxCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1513869424}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 3
|
||||
m_Size: {x: 1, y: 1, z: 1}
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!23 &1513869427
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1513869424}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_StaticShadowCaster: 0
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 0
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 3
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!33 &1513869428
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1513869424}
|
||||
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!4 &1513869429
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1513869424}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0.09615398, y: 0.2551484, z: 0.3392829, w: 0.9003004}
|
||||
m_LocalPosition: {x: -27.156, y: 0, z: 9.065}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: -20.259, y: 24.845, z: 36.791}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Roots:
|
||||
- {fileID: 1388445970}
|
||||
- {fileID: 1337453587}
|
||||
- {fileID: 1293128689}
|
||||
- {fileID: 283344197}
|
||||
- {fileID: 1392840328}
|
||||
- {fileID: 1513869429}
|
||||
- {fileID: 1331845603}
|
||||
@@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab8d113c2664ebf4d899b0826cd5f10c
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
197
Editor/Core/MkpsxisoDownloader.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace SplashEdit.EditorCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Downloads and manages mkpsxiso — the tool that builds PlayStation CD images
|
||||
/// from an XML catalog. Used for the ISO build target.
|
||||
/// https://github.com/Lameguy64/mkpsxiso
|
||||
/// </summary>
|
||||
public static class MkpsxisoDownloader
|
||||
{
|
||||
private const string MKPSXISO_VERSION = "2.20";
|
||||
private const string MKPSXISO_RELEASE_BASE =
|
||||
"https://github.com/Lameguy64/mkpsxiso/releases/download/v" + MKPSXISO_VERSION + "/";
|
||||
|
||||
private static readonly HttpClient _http = new HttpClient();
|
||||
|
||||
/// <summary>
|
||||
/// Install directory for mkpsxiso inside .tools/
|
||||
/// </summary>
|
||||
public static string MkpsxisoDir =>
|
||||
Path.Combine(SplashBuildPaths.ToolsDir, "mkpsxiso");
|
||||
|
||||
/// <summary>
|
||||
/// Path to the mkpsxiso binary.
|
||||
/// </summary>
|
||||
public static string MkpsxisoBinary
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Application.platform == RuntimePlatform.WindowsEditor)
|
||||
return Path.Combine(MkpsxisoDir, "mkpsxiso.exe");
|
||||
return Path.Combine(MkpsxisoDir, "bin", "mkpsxiso");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if mkpsxiso is installed and ready to use.
|
||||
/// </summary>
|
||||
public static bool IsInstalled() => File.Exists(MkpsxisoBinary);
|
||||
|
||||
/// <summary>
|
||||
/// Downloads and installs mkpsxiso from the official GitHub releases.
|
||||
/// </summary>
|
||||
public static async Task<bool> DownloadAndInstall(Action<string> log = null)
|
||||
{
|
||||
string archiveName;
|
||||
switch (Application.platform)
|
||||
{
|
||||
case RuntimePlatform.WindowsEditor:
|
||||
archiveName = $"mkpsxiso-{MKPSXISO_VERSION}-win64.zip";
|
||||
break;
|
||||
case RuntimePlatform.LinuxEditor:
|
||||
archiveName = $"mkpsxiso-{MKPSXISO_VERSION}-Linux.zip";
|
||||
break;
|
||||
case RuntimePlatform.OSXEditor:
|
||||
archiveName = $"mkpsxiso-{MKPSXISO_VERSION}-Darwin.zip";
|
||||
break;
|
||||
default:
|
||||
log?.Invoke("Unsupported platform for mkpsxiso.");
|
||||
return false;
|
||||
}
|
||||
|
||||
string downloadUrl = $"{MKPSXISO_RELEASE_BASE}{archiveName}";
|
||||
log?.Invoke($"Downloading mkpsxiso: {downloadUrl}");
|
||||
|
||||
try
|
||||
{
|
||||
string tempFile = Path.Combine(Path.GetTempPath(), archiveName);
|
||||
EditorUtility.DisplayProgressBar("Downloading mkpsxiso", "Downloading...", 0.1f);
|
||||
|
||||
using (var client = new System.Net.WebClient())
|
||||
{
|
||||
client.Headers.Add("User-Agent", "SplashEdit/1.0");
|
||||
|
||||
client.DownloadProgressChanged += (s, e) =>
|
||||
{
|
||||
float progress = 0.1f + 0.8f * (e.ProgressPercentage / 100f);
|
||||
string sizeMB = $"{e.BytesReceived / (1024 * 1024)}/{e.TotalBytesToReceive / (1024 * 1024)} MB";
|
||||
EditorUtility.DisplayProgressBar("Downloading mkpsxiso", $"Downloading... {sizeMB}", progress);
|
||||
};
|
||||
|
||||
await client.DownloadFileTaskAsync(new Uri(downloadUrl), tempFile);
|
||||
}
|
||||
|
||||
log?.Invoke("Extracting...");
|
||||
EditorUtility.DisplayProgressBar("Installing mkpsxiso", "Extracting...", 0.9f);
|
||||
|
||||
string installDir = MkpsxisoDir;
|
||||
if (Directory.Exists(installDir))
|
||||
Directory.Delete(installDir, true);
|
||||
Directory.CreateDirectory(installDir);
|
||||
|
||||
System.IO.Compression.ZipFile.ExtractToDirectory(tempFile, installDir);
|
||||
|
||||
// Fix nested directory (archives often have one extra level)
|
||||
SplashEdit.RuntimeCode.Utils.FixNestedDirectory(installDir);
|
||||
|
||||
try { File.Delete(tempFile); } catch { }
|
||||
|
||||
EditorUtility.ClearProgressBar();
|
||||
|
||||
if (IsInstalled())
|
||||
{
|
||||
// Make executable on Linux
|
||||
if (Application.platform != RuntimePlatform.WindowsEditor)
|
||||
{
|
||||
var chmod = Process.Start("chmod", $"+x \"{MkpsxisoBinary}\"");
|
||||
chmod?.WaitForExit();
|
||||
}
|
||||
log?.Invoke("mkpsxiso installed successfully!");
|
||||
return true;
|
||||
}
|
||||
|
||||
log?.Invoke($"mkpsxiso binary not found at: {MkpsxisoBinary}");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log?.Invoke($"mkpsxiso download failed: {ex.Message}");
|
||||
EditorUtility.ClearProgressBar();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs mkpsxiso with the given XML catalog to produce a BIN/CUE image.
|
||||
/// </summary>
|
||||
/// <param name="xmlPath">Path to the mkpsxiso XML catalog.</param>
|
||||
/// <param name="outputBin">Override output .bin path (optional, uses XML default if null).</param>
|
||||
/// <param name="outputCue">Override output .cue path (optional, uses XML default if null).</param>
|
||||
/// <param name="log">Logging callback.</param>
|
||||
/// <returns>True if mkpsxiso succeeded.</returns>
|
||||
public static bool BuildISO(string xmlPath, string outputBin = null,
|
||||
string outputCue = null, Action<string> log = null)
|
||||
{
|
||||
if (!IsInstalled())
|
||||
{
|
||||
log?.Invoke("mkpsxiso is not installed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build arguments
|
||||
string args = $"-y \"{xmlPath}\"";
|
||||
if (!string.IsNullOrEmpty(outputBin))
|
||||
args += $" -o \"{outputBin}\"";
|
||||
if (!string.IsNullOrEmpty(outputCue))
|
||||
args += $" -c \"{outputCue}\"";
|
||||
|
||||
log?.Invoke($"Running: mkpsxiso {args}");
|
||||
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = MkpsxisoBinary,
|
||||
Arguments = args,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var process = Process.Start(psi);
|
||||
string stdout = process.StandardOutput.ReadToEnd();
|
||||
string stderr = process.StandardError.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
if (!string.IsNullOrEmpty(stdout))
|
||||
log?.Invoke(stdout.Trim());
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stderr))
|
||||
log?.Invoke($"mkpsxiso error: {stderr.Trim()}");
|
||||
log?.Invoke($"mkpsxiso exited with code {process.ExitCode}");
|
||||
return false;
|
||||
}
|
||||
|
||||
log?.Invoke("ISO image built successfully.");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log?.Invoke($"mkpsxiso execution failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Editor/Core/MkpsxisoDownloader.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45aea686b641c474dba05b83956d8947
|
||||
@@ -94,34 +94,20 @@ namespace SplashEdit.EditorCode
|
||||
|
||||
// Step 3: Download the file
|
||||
string tempFile = Path.Combine(Path.GetTempPath(), $"pcsx-redux-{latestBuildId}.zip");
|
||||
|
||||
EditorUtility.DisplayProgressBar("Downloading PCSX-Redux", "Downloading...", 0.1f);
|
||||
|
||||
using (var response = await _http.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead))
|
||||
using (var client = new System.Net.WebClient())
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
long? totalBytes = response.Content.Headers.ContentLength;
|
||||
long downloadedBytes = 0;
|
||||
client.Headers.Add("User-Agent", "SplashEdit/1.0");
|
||||
|
||||
using (var fileStream = File.Create(tempFile))
|
||||
using (var downloadStream = await response.Content.ReadAsStreamAsync())
|
||||
client.DownloadProgressChanged += (s, e) =>
|
||||
{
|
||||
byte[] buffer = new byte[81920];
|
||||
int bytesRead;
|
||||
while ((bytesRead = await downloadStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
await fileStream.WriteAsync(buffer, 0, bytesRead);
|
||||
downloadedBytes += bytesRead;
|
||||
float progress = 0.1f + 0.8f * (e.ProgressPercentage / 100f);
|
||||
string sizeMB = $"{e.BytesReceived / (1024 * 1024)}/{e.TotalBytesToReceive / (1024 * 1024)} MB";
|
||||
EditorUtility.DisplayProgressBar("Downloading PCSX-Redux", $"Downloading... {sizeMB}", progress);
|
||||
};
|
||||
|
||||
if (totalBytes.HasValue)
|
||||
{
|
||||
float progress = (float)downloadedBytes / totalBytes.Value;
|
||||
string sizeMB = $"{downloadedBytes / (1024 * 1024)}/{totalBytes.Value / (1024 * 1024)} MB";
|
||||
EditorUtility.DisplayProgressBar("Downloading PCSX-Redux",
|
||||
$"Downloading... {sizeMB}", progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
await client.DownloadFileTaskAsync(new Uri(downloadUrl), tempFile);
|
||||
}
|
||||
|
||||
log?.Invoke($"Downloaded to {tempFile}");
|
||||
@@ -144,6 +130,7 @@ namespace SplashEdit.EditorCode
|
||||
};
|
||||
var proc = Process.Start(psi);
|
||||
proc?.WaitForExit();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -151,6 +138,20 @@ namespace SplashEdit.EditorCode
|
||||
log?.Invoke($"Extracted to {installDir}");
|
||||
}
|
||||
|
||||
// Make executable
|
||||
|
||||
if(Application.platform == RuntimePlatform.LinuxEditor) {
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "chmod",
|
||||
Arguments = $"+x \"{SplashBuildPaths.PCSXReduxBinary}\"",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
var proc = Process.Start(psi);
|
||||
proc?.WaitForExit();
|
||||
}
|
||||
|
||||
// Clean up temp file
|
||||
try { File.Delete(tempFile); } catch { }
|
||||
|
||||
@@ -164,7 +165,7 @@ namespace SplashEdit.EditorCode
|
||||
else
|
||||
{
|
||||
// The zip might have a nested directory — try to find the exe
|
||||
FixNestedDirectory(installDir);
|
||||
SplashEdit.RuntimeCode.Utils.FixNestedDirectory(installDir);
|
||||
if (SplashBuildPaths.IsPCSXReduxInstalled())
|
||||
{
|
||||
log?.Invoke("PCSX-Redux installed successfully!");
|
||||
@@ -187,29 +188,6 @@ namespace SplashEdit.EditorCode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the zip extracts into a nested directory, move files up.
|
||||
/// </summary>
|
||||
private static void FixNestedDirectory(string installDir)
|
||||
{
|
||||
var subdirs = Directory.GetDirectories(installDir);
|
||||
if (subdirs.Length == 1)
|
||||
{
|
||||
string nested = subdirs[0];
|
||||
foreach (string file in Directory.GetFiles(nested))
|
||||
{
|
||||
string dest = Path.Combine(installDir, Path.GetFileName(file));
|
||||
File.Move(file, dest);
|
||||
}
|
||||
foreach (string dir in Directory.GetDirectories(nested))
|
||||
{
|
||||
string dest = Path.Combine(installDir, Path.GetFileName(dir));
|
||||
Directory.Move(dir, dest);
|
||||
}
|
||||
try { Directory.Delete(nested); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse the latest build ID from the master manifest JSON.
|
||||
/// Expected format: {"builds":[{"id":1234,...},...],...}
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace SplashEdit.EditorCode
|
||||
private const int FUNC_SEEK = 0x107;
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -157,6 +158,7 @@ namespace SplashEdit.EditorCode
|
||||
bool lastByteWasEscape = false;
|
||||
var textBuffer = new StringBuilder();
|
||||
int totalBytesReceived = 0;
|
||||
int consecutiveErrors = 0;
|
||||
DateTime lastLogTime = DateTime.Now;
|
||||
|
||||
_log?.Invoke("PCdrv monitor: waiting for data from PS1...");
|
||||
@@ -179,6 +181,7 @@ namespace SplashEdit.EditorCode
|
||||
}
|
||||
|
||||
int b = _port.ReadByte();
|
||||
consecutiveErrors = 0;
|
||||
totalBytesReceived++;
|
||||
|
||||
// Log first bytes received to help diagnose protocol issues
|
||||
@@ -256,8 +259,16 @@ namespace SplashEdit.EditorCode
|
||||
catch (OperationCanceledException) { break; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!ct.IsCancellationRequested)
|
||||
if (ct.IsCancellationRequested) break;
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,17 +52,14 @@ namespace SplashEdit.EditorCode
|
||||
/// </summary>
|
||||
public static async Task<bool> DownloadAndInstall(Action<string> log = null)
|
||||
{
|
||||
string platformSuffix;
|
||||
string archiveName;
|
||||
switch (Application.platform)
|
||||
{
|
||||
case RuntimePlatform.WindowsEditor:
|
||||
platformSuffix = "x86_64-pc-windows-msvc";
|
||||
archiveName = $"psxavenc-{PSXAVENC_VERSION}-{platformSuffix}.zip";
|
||||
archiveName = $"psxavenc-windows.zip";
|
||||
break;
|
||||
case RuntimePlatform.LinuxEditor:
|
||||
platformSuffix = "x86_64-unknown-linux-gnu";
|
||||
archiveName = $"psxavenc-{PSXAVENC_VERSION}-{platformSuffix}.tar.gz";
|
||||
archiveName = $"psxavenc-linux.zip";
|
||||
break;
|
||||
default:
|
||||
log?.Invoke("Only Windows and Linux are supported.");
|
||||
@@ -77,29 +74,18 @@ namespace SplashEdit.EditorCode
|
||||
string tempFile = Path.Combine(Path.GetTempPath(), archiveName);
|
||||
EditorUtility.DisplayProgressBar("Downloading psxavenc", "Downloading...", 0.1f);
|
||||
|
||||
using (var response = await _http.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead))
|
||||
using (var client = new System.Net.WebClient())
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
long? totalBytes = response.Content.Headers.ContentLength;
|
||||
long downloaded = 0;
|
||||
client.Headers.Add("User-Agent", "SplashEdit/1.0");
|
||||
|
||||
using (var fs = File.Create(tempFile))
|
||||
using (var stream = await response.Content.ReadAsStreamAsync())
|
||||
client.DownloadProgressChanged += (s, e) =>
|
||||
{
|
||||
byte[] buffer = new byte[81920];
|
||||
int bytesRead;
|
||||
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
await fs.WriteAsync(buffer, 0, bytesRead);
|
||||
downloaded += bytesRead;
|
||||
if (totalBytes.HasValue)
|
||||
{
|
||||
float progress = (float)downloaded / totalBytes.Value;
|
||||
EditorUtility.DisplayProgressBar("Downloading psxavenc",
|
||||
$"{downloaded / 1024}/{totalBytes.Value / 1024} KB", progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
float progress = 0.1f + 0.8f * (e.ProgressPercentage / 100f);
|
||||
string sizeMB = $"{e.BytesReceived / (1024 * 1024)}/{e.TotalBytesToReceive / (1024 * 1024)} MB";
|
||||
EditorUtility.DisplayProgressBar("Downloading psxavenc", $"Downloading... {sizeMB}", progress);
|
||||
};
|
||||
|
||||
await client.DownloadFileTaskAsync(new Uri(downloadUrl), tempFile);
|
||||
}
|
||||
|
||||
log?.Invoke("Extracting...");
|
||||
@@ -129,7 +115,7 @@ namespace SplashEdit.EditorCode
|
||||
}
|
||||
|
||||
// Fix nested directory (sometimes archives have one extra level)
|
||||
FixNestedDirectory(installDir);
|
||||
SplashEdit.RuntimeCode.Utils.FixNestedDirectory(installDir);
|
||||
|
||||
try { File.Delete(tempFile); } catch { }
|
||||
|
||||
@@ -158,27 +144,6 @@ namespace SplashEdit.EditorCode
|
||||
}
|
||||
}
|
||||
|
||||
private static void FixNestedDirectory(string dir)
|
||||
{
|
||||
// If extraction created exactly one subdirectory, flatten it
|
||||
var subdirs = Directory.GetDirectories(dir);
|
||||
if (subdirs.Length == 1)
|
||||
{
|
||||
string nested = subdirs[0];
|
||||
foreach (string file in Directory.GetFiles(nested))
|
||||
{
|
||||
string dest = Path.Combine(dir, Path.GetFileName(file));
|
||||
if (!File.Exists(dest)) File.Move(file, dest);
|
||||
}
|
||||
foreach (string sub in Directory.GetDirectories(nested))
|
||||
{
|
||||
string dest = Path.Combine(dir, Path.GetFileName(sub));
|
||||
if (!Directory.Exists(dest)) Directory.Move(sub, dest);
|
||||
}
|
||||
try { Directory.Delete(nested, true); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Unity AudioClip to PS1 SPU ADPCM format using psxavenc.
|
||||
/// Returns the ADPCM byte array, or null on failure.
|
||||
|
||||
@@ -247,9 +247,25 @@ namespace SplashEdit.EditorCode
|
||||
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()
|
||||
{
|
||||
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();
|
||||
DrawConsoleOutput();
|
||||
}
|
||||
@@ -310,16 +326,18 @@ namespace SplashEdit.EditorCode
|
||||
int selMax = Mathf.Max(_selectionAnchor, _selectionEnd);
|
||||
bool hasSelection = _selectionAnchor >= 0 && _selectionEnd >= 0;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_lines.Count == 0)
|
||||
// Iterate the snapshot taken during Layout so the control count
|
||||
// is stable across Layout and Repaint events.
|
||||
var snapshot = _snapshot;
|
||||
|
||||
if (snapshot.Length == 0)
|
||||
{
|
||||
GUILayout.Label("Waiting for output...", EditorStyles.centeredGreyMiniLabel);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _lines.Count; i++)
|
||||
for (int i = 0; i < snapshot.Length; i++)
|
||||
{
|
||||
var line = _lines[i];
|
||||
var line = snapshot[i];
|
||||
|
||||
if (line.isError && !_showStderr) continue;
|
||||
if (!line.isError && !_showStdout) continue;
|
||||
@@ -375,7 +393,6 @@ namespace SplashEdit.EditorCode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
|
||||
188
Editor/Core/SceneMemoryReport.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SplashEdit.EditorCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Memory analysis report for a single exported scene.
|
||||
/// All values are in bytes unless noted otherwise.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SceneMemoryReport
|
||||
{
|
||||
public string sceneName;
|
||||
|
||||
// ─── Main RAM ───
|
||||
public long splashpackFileSize; // Total file on disc
|
||||
public long splashpackLiveSize; // Bytes kept in RAM at runtime (before bulk data freed)
|
||||
public int triangleCount;
|
||||
public int gameObjectCount;
|
||||
|
||||
// ─── VRAM (1024 x 512 x 2 = 1,048,576 bytes) ───
|
||||
public long framebufferSize; // 2 x W x H x 2
|
||||
public long textureAtlasSize; // Sum of atlas pixel data
|
||||
public long clutSize; // Sum of CLUT entries x 2
|
||||
public long fontVramSize; // Custom font textures
|
||||
public int atlasCount;
|
||||
public int clutCount;
|
||||
|
||||
// ─── SPU RAM (512KB, 0x1010 reserved) ───
|
||||
public long audioDataSize;
|
||||
public int audioClipCount;
|
||||
|
||||
// ─── CD Storage ───
|
||||
public long loaderPackSize;
|
||||
|
||||
// ─── Constants ───
|
||||
public const long TOTAL_RAM = 2 * 1024 * 1024;
|
||||
public const long KERNEL_RESERVED = 0x10000; // 64KB kernel area
|
||||
public const long USABLE_RAM = TOTAL_RAM - KERNEL_RESERVED;
|
||||
public const long TOTAL_VRAM = 1024 * 512 * 2; // 1MB
|
||||
public const long TOTAL_SPU = 512 * 1024;
|
||||
public const long SPU_RESERVED = 0x1010;
|
||||
public const long USABLE_SPU = TOTAL_SPU - SPU_RESERVED;
|
||||
|
||||
// Fixed runtime overhead from C++ (renderer.hh constants, now configurable)
|
||||
public static long BUMP_ALLOC_TOTAL => 2L * SplashSettings.BumpSize;
|
||||
public static long OT_TOTAL => 2L * SplashSettings.OtSize * 4;
|
||||
public const long VIS_REFS = 4096 * 4; // 16KB
|
||||
public const long STACK_ESTIMATE = 32 * 1024; // 32KB
|
||||
public const long LUA_OVERHEAD = 16 * 1024; // 16KB approximate
|
||||
public const long SYSTEM_FONT_VRAM = 4 * 1024; // ~4KB
|
||||
|
||||
public long FixedOverhead => BUMP_ALLOC_TOTAL + OT_TOTAL + VIS_REFS + STACK_ESTIMATE + LUA_OVERHEAD;
|
||||
|
||||
// Heap estimate and warnings
|
||||
public long EstimatedHeapFree => USABLE_RAM - TotalRamUsage;
|
||||
public bool IsHeapWarning => EstimatedHeapFree < 128 * 1024; // < 128KB free
|
||||
public bool IsHeapCritical => EstimatedHeapFree < 64 * 1024; // < 64KB free
|
||||
|
||||
/// <summary>RAM used by scene data (live portion of splashpack).</summary>
|
||||
public long SceneRamUsage => splashpackLiveSize > 0 ? splashpackLiveSize : splashpackFileSize;
|
||||
|
||||
/// <summary>Total estimated RAM: fixed overhead + scene data. Does NOT include code/BSS.</summary>
|
||||
public long TotalRamUsage => FixedOverhead + SceneRamUsage;
|
||||
|
||||
public long TotalVramUsed => framebufferSize + textureAtlasSize + clutSize + fontVramSize + SYSTEM_FONT_VRAM;
|
||||
public long TotalSpuUsed => audioDataSize;
|
||||
public long TotalDiscSize => splashpackFileSize + loaderPackSize;
|
||||
|
||||
public float RamPercent => Mathf.Clamp01((float)TotalRamUsage / USABLE_RAM) * 100f;
|
||||
public float VramPercent => Mathf.Clamp01((float)TotalVramUsed / TOTAL_VRAM) * 100f;
|
||||
public float SpuPercent => USABLE_SPU > 0 ? Mathf.Clamp01((float)TotalSpuUsed / USABLE_SPU) * 100f : 0f;
|
||||
|
||||
public long RamFree => USABLE_RAM - TotalRamUsage;
|
||||
public long VramFree => TOTAL_VRAM - TotalVramUsed;
|
||||
public long SpuFree => USABLE_SPU - TotalSpuUsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a SceneMemoryReport by reading the exported splashpack binary header
|
||||
/// and the scene's VRAM/audio data.
|
||||
/// </summary>
|
||||
public static class SceneMemoryAnalyzer
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyze an exported scene. Call after ExportToPath().
|
||||
/// </summary>
|
||||
/// <param name="sceneName">Display name for the scene.</param>
|
||||
/// <param name="splashpackPath">Path to the exported .splashpack file.</param>
|
||||
/// <param name="loaderPackPath">Path to the loading screen file (may be null).</param>
|
||||
/// <param name="atlases">Texture atlases from the export pipeline.</param>
|
||||
/// <param name="audioExportSizes">Array of ADPCM byte sizes per audio clip.</param>
|
||||
/// <param name="fonts">Custom font descriptors.</param>
|
||||
public static SceneMemoryReport Analyze(
|
||||
string sceneName,
|
||||
string splashpackPath,
|
||||
string loaderPackPath,
|
||||
SplashEdit.RuntimeCode.TextureAtlas[] atlases,
|
||||
long[] audioExportSizes,
|
||||
SplashEdit.RuntimeCode.PSXFontData[] fonts,
|
||||
int triangleCount = 0)
|
||||
{
|
||||
var r = new SceneMemoryReport { sceneName = sceneName };
|
||||
|
||||
// ── File sizes ──
|
||||
if (File.Exists(splashpackPath))
|
||||
r.splashpackFileSize = new FileInfo(splashpackPath).Length;
|
||||
if (!string.IsNullOrEmpty(loaderPackPath) && File.Exists(loaderPackPath))
|
||||
r.loaderPackSize = new FileInfo(loaderPackPath).Length;
|
||||
|
||||
r.triangleCount = triangleCount;
|
||||
|
||||
// ── Parse splashpack header for counts and pixelDataOffset ──
|
||||
if (File.Exists(splashpackPath))
|
||||
{
|
||||
try { ReadHeader(splashpackPath, r); }
|
||||
catch (Exception e) { Debug.LogWarning($"Memory report: failed to read header: {e.Message}"); }
|
||||
}
|
||||
|
||||
// ── Framebuffers ──
|
||||
int fbW = SplashSettings.ResolutionWidth;
|
||||
int fbH = SplashSettings.ResolutionHeight;
|
||||
int fbCount = SplashSettings.DualBuffering ? 2 : 1;
|
||||
r.framebufferSize = fbW * fbH * 2L * fbCount;
|
||||
|
||||
// ── VRAM: Texture atlases + CLUTs ──
|
||||
if (atlases != null)
|
||||
{
|
||||
r.atlasCount = atlases.Length;
|
||||
foreach (var atlas in atlases)
|
||||
{
|
||||
r.textureAtlasSize += atlas.Width * SplashEdit.RuntimeCode.TextureAtlas.Height * 2L;
|
||||
foreach (var tex in atlas.ContainedTextures)
|
||||
{
|
||||
if (tex.ColorPalette != null)
|
||||
{
|
||||
r.clutCount++;
|
||||
r.clutSize += tex.ColorPalette.Count * 2L;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── VRAM: Custom fonts ──
|
||||
if (fonts != null)
|
||||
{
|
||||
foreach (var font in fonts)
|
||||
{
|
||||
if (font.TextureHeight > 0)
|
||||
r.fontVramSize += 64L * font.TextureHeight * 2; // 4bpp = 64 hwords wide
|
||||
}
|
||||
}
|
||||
|
||||
// ── SPU: Audio ──
|
||||
if (audioExportSizes != null)
|
||||
{
|
||||
r.audioClipCount = audioExportSizes.Length;
|
||||
foreach (long sz in audioExportSizes)
|
||||
r.audioDataSize += sz;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
private static void ReadHeader(string path, SceneMemoryReport r)
|
||||
{
|
||||
using (var reader = new BinaryReader(File.OpenRead(path)))
|
||||
{
|
||||
if (reader.BaseStream.Length < 104) return;
|
||||
|
||||
// Magic + version (4 bytes)
|
||||
reader.ReadBytes(4);
|
||||
|
||||
// luaFileCount(2) + gameObjectCount(2) + textureAtlasCount(2) + clutCount(2)
|
||||
reader.ReadUInt16(); // luaFileCount
|
||||
r.gameObjectCount = reader.ReadUInt16();
|
||||
reader.ReadUInt16(); // textureAtlasCount
|
||||
reader.ReadUInt16(); // clutCount
|
||||
|
||||
// Skip to pixelDataOffset at byte 100
|
||||
reader.BaseStream.Seek(100, SeekOrigin.Begin);
|
||||
uint pixelDataOffset = reader.ReadUInt32();
|
||||
r.splashpackLiveSize = pixelDataOffset > 0 ? pixelDataOffset : r.splashpackFileSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Editor/Core/SceneMemoryReport.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f68ba273eb88c3b4796e43f40b226c71
|
||||
@@ -41,7 +41,7 @@ namespace SplashEdit.EditorCode
|
||||
case RuntimePlatform.WindowsEditor:
|
||||
return Path.Combine(PCSXReduxDir, "pcsx-redux.exe");
|
||||
case RuntimePlatform.LinuxEditor:
|
||||
return Path.Combine(ToolsDir, "PCSX-Redux-HEAD-x86_64.AppImage");
|
||||
return Path.Combine(PCSXReduxDir, "PCSX-Redux-HEAD-x86_64.AppImage");
|
||||
default:
|
||||
return Path.Combine(PCSXReduxDir, "pcsx-redux");
|
||||
}
|
||||
@@ -109,6 +109,22 @@ namespace SplashEdit.EditorCode
|
||||
return Path.Combine(BuildOutputDir, $"scene_{sceneIndex}.splashpack");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default license file path (SPLASHLICENSE.DAT) shipped in the package Data folder.
|
||||
/// Resolved relative to the Unity project so it works on any machine.
|
||||
/// </summary>
|
||||
public static string DefaultLicenseFilePath =>
|
||||
Path.GetFullPath(Path.Combine("Packages", "net.psxsplash.splashedit", "Data", "SPLASHLICENSE.DAT"));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the loader pack (loading screen) output path for a scene by index.
|
||||
/// Uses a deterministic naming scheme: scene_0.loading, scene_1.loading, etc.
|
||||
/// </summary>
|
||||
public static string GetSceneLoaderPackPath(int sceneIndex, string sceneName)
|
||||
{
|
||||
return Path.Combine(BuildOutputDir, $"scene_{sceneIndex}.loading");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ISO output path for release builds.
|
||||
/// </summary>
|
||||
@@ -121,6 +137,24 @@ namespace SplashEdit.EditorCode
|
||||
public static string CUEOutputPath =>
|
||||
Path.Combine(BuildOutputDir, "psxsplash.cue");
|
||||
|
||||
/// <summary>
|
||||
/// XML catalog path used by mkpsxiso to build the ISO image.
|
||||
/// </summary>
|
||||
public static string ISOCatalogPath =>
|
||||
Path.Combine(BuildOutputDir, "psxsplash.xml");
|
||||
|
||||
/// <summary>
|
||||
/// SYSTEM.CNF file path generated for the ISO image.
|
||||
/// The PS1 BIOS reads this to find and launch the executable.
|
||||
/// </summary>
|
||||
public static string SystemCnfPath =>
|
||||
Path.Combine(BuildOutputDir, "SYSTEM.CNF");
|
||||
|
||||
/// <summary>
|
||||
/// Checks if mkpsxiso is installed in the tools directory.
|
||||
/// </summary>
|
||||
public static bool IsMkpsxisoInstalled() => MkpsxisoDownloader.IsInstalled();
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the build output and tools directories exist.
|
||||
/// Also appends entries to the project .gitignore if not present.
|
||||
@@ -132,6 +166,45 @@ namespace SplashEdit.EditorCode
|
||||
EnsureGitIgnore();
|
||||
}
|
||||
|
||||
// ───── Lua bytecode compilation paths ─────
|
||||
|
||||
/// <summary>
|
||||
/// Directory for Lua source files extracted during export.
|
||||
/// </summary>
|
||||
public static string LuaSrcDir =>
|
||||
Path.Combine(BuildOutputDir, "lua_src");
|
||||
|
||||
/// <summary>
|
||||
/// Directory for compiled Lua bytecode files.
|
||||
/// </summary>
|
||||
public static string LuaCompiledDir =>
|
||||
Path.Combine(BuildOutputDir, "lua_compiled");
|
||||
|
||||
/// <summary>
|
||||
/// Manifest file listing input/output pairs for the PS1 Lua compiler.
|
||||
/// </summary>
|
||||
public static string LuaManifestPath =>
|
||||
Path.Combine(LuaSrcDir, "manifest.txt");
|
||||
|
||||
/// <summary>
|
||||
/// Sentinel file written by luac_psx when compilation is complete.
|
||||
/// Contains "OK" on success or "ERROR" on failure.
|
||||
/// </summary>
|
||||
public static string LuaDoneSentinel =>
|
||||
Path.Combine(LuaSrcDir, "__done__");
|
||||
|
||||
/// <summary>
|
||||
/// Path to the luac_psx PS1 compiler executable (built from tools/luac_psx/).
|
||||
/// </summary>
|
||||
public static string LuacPsxExePath =>
|
||||
Path.Combine(NativeSourceDir, "tools", "luac_psx", "luac_psx.ps-exe");
|
||||
|
||||
/// <summary>
|
||||
/// Path to the luac_psx tools directory (for building the compiler).
|
||||
/// </summary>
|
||||
public static string LuacPsxDir =>
|
||||
Path.Combine(NativeSourceDir, "tools", "luac_psx");
|
||||
|
||||
/// <summary>
|
||||
/// Checks if PCSX-Redux is installed in the tools directory.
|
||||
/// </summary>
|
||||
|
||||
@@ -92,29 +92,74 @@ namespace SplashEdit.EditorCode
|
||||
set => EditorPrefs.SetInt(Prefix + "SerialBaudRate", value);
|
||||
}
|
||||
|
||||
// --- VRAM Layout ---
|
||||
// --- VRAM Layout (hardcoded 320x240, dual-buffered, vertical) ---
|
||||
public static int ResolutionWidth
|
||||
{
|
||||
get => EditorPrefs.GetInt(Prefix + "ResWidth", 320);
|
||||
set => EditorPrefs.SetInt(Prefix + "ResWidth", value);
|
||||
get => 320;
|
||||
set { } // no-op, hardcoded
|
||||
}
|
||||
|
||||
public static int ResolutionHeight
|
||||
{
|
||||
get => EditorPrefs.GetInt(Prefix + "ResHeight", 240);
|
||||
set => EditorPrefs.SetInt(Prefix + "ResHeight", value);
|
||||
get => 240;
|
||||
set { } // no-op, hardcoded
|
||||
}
|
||||
|
||||
public static bool DualBuffering
|
||||
{
|
||||
get => EditorPrefs.GetBool(Prefix + "DualBuffering", true);
|
||||
set => EditorPrefs.SetBool(Prefix + "DualBuffering", value);
|
||||
get => true;
|
||||
set { } // no-op, hardcoded
|
||||
}
|
||||
|
||||
public static bool VerticalLayout
|
||||
{
|
||||
get => EditorPrefs.GetBool(Prefix + "VerticalLayout", true);
|
||||
set => EditorPrefs.SetBool(Prefix + "VerticalLayout", value);
|
||||
get => true;
|
||||
set { } // no-op, hardcoded
|
||||
}
|
||||
|
||||
// --- Clean Build ---
|
||||
public static bool CleanBuild
|
||||
{
|
||||
get => EditorPrefs.GetBool(Prefix + "CleanBuild", true);
|
||||
set => EditorPrefs.SetBool(Prefix + "CleanBuild", value);
|
||||
}
|
||||
|
||||
// --- Memory Overlay ---
|
||||
/// <summary>
|
||||
/// When enabled, compiles the runtime with a heap/RAM usage progress bar
|
||||
/// and text overlay at the top-right corner of the screen.
|
||||
/// Passes MEMOVERLAY=1 to the native Makefile.
|
||||
/// </summary>
|
||||
public static bool MemoryOverlay
|
||||
{
|
||||
get => EditorPrefs.GetBool(Prefix + "MemoryOverlay", false);
|
||||
set => EditorPrefs.SetBool(Prefix + "MemoryOverlay", value);
|
||||
}
|
||||
|
||||
|
||||
// --- FPS Overlay ---
|
||||
/// <summary>
|
||||
/// When enabled, compiles the runtime with an FPS counter
|
||||
/// and text overlay at the top-left corner of the screen.
|
||||
/// Passes FPSOVERLAY=1 to the native Makefile.
|
||||
/// </summary>
|
||||
public static bool FpsOverlay
|
||||
{
|
||||
get => EditorPrefs.GetBool(Prefix + "FpsOverlay", false);
|
||||
set => EditorPrefs.SetBool(Prefix + "FpsOverlay", value);
|
||||
}
|
||||
|
||||
// --- Renderer sizes ---
|
||||
public static int OtSize
|
||||
{
|
||||
get => EditorPrefs.GetInt(Prefix + "OtSize", 2048 * 4);
|
||||
set => EditorPrefs.SetInt(Prefix + "OtSize", value);
|
||||
}
|
||||
|
||||
public static int BumpSize
|
||||
{
|
||||
get => EditorPrefs.GetInt(Prefix + "BumpSize", 8096 * 16);
|
||||
set => EditorPrefs.SetInt(Prefix + "BumpSize", value);
|
||||
}
|
||||
|
||||
// --- Export settings ---
|
||||
@@ -124,17 +169,25 @@ namespace SplashEdit.EditorCode
|
||||
set => EditorPrefs.SetFloat(Prefix + "GTEScaling", value);
|
||||
}
|
||||
|
||||
public static bool AutoValidateOnExport
|
||||
// --- ISO Build ---
|
||||
/// <summary>
|
||||
/// Optional path to a Sony license file (.dat) for the ISO image.
|
||||
/// If empty, the ISO will be built without license data (homebrew-only).
|
||||
/// The file must be in raw 2336-byte sector format (from PsyQ SDK LCNSFILE).
|
||||
/// </summary>
|
||||
public static string LicenseFilePath
|
||||
{
|
||||
get => EditorPrefs.GetBool(Prefix + "AutoValidate", true);
|
||||
set => EditorPrefs.SetBool(Prefix + "AutoValidate", value);
|
||||
get => EditorPrefs.GetString(Prefix + "LicenseFilePath", SplashBuildPaths.DefaultLicenseFilePath);
|
||||
set => EditorPrefs.SetString(Prefix + "LicenseFilePath", value);
|
||||
}
|
||||
|
||||
// --- Play Mode Intercept ---
|
||||
public static bool InterceptPlayMode
|
||||
/// <summary>
|
||||
/// Volume label for the ISO image (up to 31 characters, uppercase).
|
||||
/// </summary>
|
||||
public static string ISOVolumeLabel
|
||||
{
|
||||
get => EditorPrefs.GetBool(Prefix + "InterceptPlayMode", false);
|
||||
set => EditorPrefs.SetBool(Prefix + "InterceptPlayMode", value);
|
||||
get => EditorPrefs.GetString(Prefix + "ISOVolumeLabel", "PSXSPLASH");
|
||||
set => EditorPrefs.SetString(Prefix + "ISOVolumeLabel", value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -147,7 +200,9 @@ namespace SplashEdit.EditorCode
|
||||
"Target", "Mode", "NativeProjectPath", "MIPSToolchainPath",
|
||||
"PCSXReduxPath", "PCSXReduxPCdrvBase", "SerialPort", "SerialBaudRate",
|
||||
"ResWidth", "ResHeight", "DualBuffering", "VerticalLayout",
|
||||
"GTEScaling", "AutoValidate", "InterceptPlayMode"
|
||||
"GTEScaling", "AutoValidate",
|
||||
"LicenseFilePath", "ISOVolumeLabel",
|
||||
"OtSize", "BumpSize"
|
||||
};
|
||||
|
||||
foreach (string key in keys)
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace SplashEdit.EditorCode
|
||||
private SerializedProperty _isRepeatable;
|
||||
private SerializedProperty _cooldownFrames;
|
||||
private SerializedProperty _showPrompt;
|
||||
private SerializedProperty _promptCanvasName;
|
||||
private SerializedProperty _requireLineOfSight;
|
||||
private SerializedProperty _interactionOffset;
|
||||
|
||||
private static readonly string[] ButtonNames =
|
||||
{
|
||||
@@ -34,23 +34,31 @@ namespace SplashEdit.EditorCode
|
||||
_isRepeatable = serializedObject.FindProperty("isRepeatable");
|
||||
_cooldownFrames = serializedObject.FindProperty("cooldownFrames");
|
||||
_showPrompt = serializedObject.FindProperty("showPrompt");
|
||||
_promptCanvasName = serializedObject.FindProperty("promptCanvasName");
|
||||
_requireLineOfSight = serializedObject.FindProperty("requireLineOfSight");
|
||||
_interactionOffset = serializedObject.FindProperty("interactionOffset");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
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);
|
||||
|
||||
// Button selector with visual
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PrefixLabel("Interact Button");
|
||||
_interactButton.intValue = EditorGUILayout.Popup(_interactButton.intValue, ButtonNames);
|
||||
@@ -63,74 +71,52 @@ namespace SplashEdit.EditorCode
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_cooldownFrames, new GUIContent("Cooldown (frames)"));
|
||||
|
||||
// Show cooldown in seconds
|
||||
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--;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(_showPrompt);
|
||||
});
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
_advancedFoldout = DrawFoldoutSection("Advanced", _advancedFoldout, () =>
|
||||
EditorGUILayout.PropertyField(_showPrompt, new GUIContent("Show Prompt Canvas"));
|
||||
|
||||
if (_showPrompt.boolValue)
|
||||
{
|
||||
EditorGUILayout.PropertyField(_requireLineOfSight);
|
||||
EditorGUILayout.PropertyField(_interactionOffset);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_promptCanvasName, new GUIContent("Canvas Name"));
|
||||
if (string.IsNullOrEmpty(_promptCanvasName.stringValue))
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
"Enter the name of a PSXCanvas that will be shown when the player is in range and hidden when they leave.",
|
||||
MessageType.Info);
|
||||
}
|
||||
if (_promptCanvasName.stringValue != null && _promptCanvasName.stringValue.Length > 15)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Canvas name is limited to 15 characters.", MessageType.Warning);
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
});
|
||||
|
||||
DrawLuaEventsInfo(new[] { "onInteract" });
|
||||
EditorGUILayout.Space(2);
|
||||
|
||||
_advancedFoldout = PSXEditorStyles.DrawFoldoutCard("Advanced", _advancedFoldout, () =>
|
||||
{
|
||||
EditorGUILayout.PropertyField(_requireLineOfSight,
|
||||
new GUIContent("Require Facing",
|
||||
"Player must be facing the object to interact. Uses a forward-direction check."));
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
271
Editor/Inspectors/PSXComponentEditors2.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
// I raged that my scrollwheel was broken while writing this and that's why it's 2 files.
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Editor/Inspectors/PSXComponentEditors2.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fd7a7bcc7d0ff841b158f2744d48010
|
||||
617
Editor/Inspectors/PSXUIEditors.cs
Normal file
@@ -0,0 +1,617 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using SplashEdit.RuntimeCode;
|
||||
using System.Linq;
|
||||
|
||||
namespace SplashEdit.EditorCode
|
||||
{
|
||||
// --- Scene Preview Gizmos ---
|
||||
// A single canvas-level gizmo draws all children in hierarchy order
|
||||
// so depth stacking is correct (last child in hierarchy renders on top).
|
||||
|
||||
public static class PSXUIGizmos
|
||||
{
|
||||
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected)]
|
||||
static void DrawCanvasGizmo(PSXCanvas canvas, GizmoType gizmoType)
|
||||
{
|
||||
RectTransform canvasRt = canvas.GetComponent<RectTransform>();
|
||||
if (canvasRt == null) return;
|
||||
|
||||
bool canvasSelected = (gizmoType & GizmoType.Selected) != 0;
|
||||
|
||||
// Canvas border
|
||||
Vector3[] canvasCorners = new Vector3[4];
|
||||
canvasRt.GetWorldCorners(canvasCorners);
|
||||
Color border = canvasSelected ? Color.yellow : new Color(1, 1, 0, 0.3f);
|
||||
Handles.DrawSolidRectangleWithOutline(canvasCorners, Color.clear, border);
|
||||
|
||||
// Draw all children in hierarchy order (first child = back, last child = front)
|
||||
var children = canvas.GetComponentsInChildren<Transform>(true).Reverse();
|
||||
foreach (var child in children)
|
||||
{
|
||||
if (child == canvas.transform) continue;
|
||||
bool childSelected = Selection.Contains(child.gameObject);
|
||||
|
||||
var box = child.GetComponent<PSXUIBox>();
|
||||
if (box != null) { DrawBox(box, childSelected); continue; }
|
||||
|
||||
var image = child.GetComponent<PSXUIImage>();
|
||||
if (image != null) { DrawImage(image, childSelected); continue; }
|
||||
|
||||
var text = child.GetComponent<PSXUIText>();
|
||||
if (text != null) { DrawText(text, childSelected); continue; }
|
||||
|
||||
var bar = child.GetComponent<PSXUIProgressBar>();
|
||||
if (bar != null) { DrawProgressBar(bar, childSelected); continue; }
|
||||
}
|
||||
|
||||
// Canvas label when selected
|
||||
if (canvasSelected)
|
||||
{
|
||||
Vector2 res = PSXCanvas.PSXResolution;
|
||||
Vector3 topMid = (canvasCorners[1] + canvasCorners[2]) * 0.5f;
|
||||
string label = $"PSX Canvas: {canvas.CanvasName} ({res.x}x{res.y})";
|
||||
GUIStyle style = new GUIStyle(EditorStyles.boldLabel);
|
||||
style.normal.textColor = Color.yellow;
|
||||
Handles.Label(topMid, label, style);
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawBox(PSXUIBox box, bool selected)
|
||||
{
|
||||
RectTransform rt = box.GetComponent<RectTransform>();
|
||||
if (rt == null) return;
|
||||
Vector3[] corners = new Vector3[4];
|
||||
rt.GetWorldCorners(corners);
|
||||
Color fill = box.BoxColor;
|
||||
fill.a = selected ? 1f : 0.9f;
|
||||
Color borderColor = selected ? Color.white : new Color(1, 1, 1, 0.5f);
|
||||
Handles.DrawSolidRectangleWithOutline(corners, fill, borderColor);
|
||||
}
|
||||
|
||||
static void DrawImage(PSXUIImage image, bool selected)
|
||||
{
|
||||
RectTransform rt = image.GetComponent<RectTransform>();
|
||||
if (rt == null) return;
|
||||
Vector3[] corners = new Vector3[4];
|
||||
rt.GetWorldCorners(corners);
|
||||
|
||||
if (image.SourceTexture != null)
|
||||
{
|
||||
Color tint = image.TintColor;
|
||||
tint.a = selected ? 1f : 0.9f;
|
||||
Handles.DrawSolidRectangleWithOutline(corners, tint * 0.3f, tint);
|
||||
|
||||
Handles.BeginGUI();
|
||||
Vector2 min = HandleUtility.WorldToGUIPoint(corners[0]);
|
||||
Vector2 max = HandleUtility.WorldToGUIPoint(corners[2]);
|
||||
Rect screenRect = new Rect(
|
||||
Mathf.Min(min.x, max.x), Mathf.Min(min.y, max.y),
|
||||
Mathf.Abs(max.x - min.x), Mathf.Abs(max.y - min.y));
|
||||
if (screenRect.width > 2 && screenRect.height > 2)
|
||||
{
|
||||
GUI.color = new Color(tint.r, tint.g, tint.b, selected ? 1f : 0.9f);
|
||||
GUI.DrawTexture(screenRect, image.SourceTexture, ScaleMode.StretchToFill);
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
Handles.EndGUI();
|
||||
}
|
||||
else
|
||||
{
|
||||
Color fill = new Color(0.4f, 0.4f, 0.8f, selected ? 0.8f : 0.6f);
|
||||
Handles.DrawSolidRectangleWithOutline(corners, fill, Color.cyan);
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawText(PSXUIText text, bool selected)
|
||||
{
|
||||
RectTransform rt = text.GetComponent<RectTransform>();
|
||||
if (rt == null) return;
|
||||
Vector3[] corners = new Vector3[4];
|
||||
rt.GetWorldCorners(corners);
|
||||
|
||||
Color borderColor = text.TextColor;
|
||||
borderColor.a = selected ? 1f : 0.7f;
|
||||
Color fill = new Color(0, 0, 0, selected ? 0.6f : 0.4f);
|
||||
Handles.DrawSolidRectangleWithOutline(corners, fill, borderColor);
|
||||
|
||||
string label = string.IsNullOrEmpty(text.DefaultText) ? "[empty]" : text.DefaultText;
|
||||
|
||||
PSXFontAsset font = text.GetEffectiveFont();
|
||||
int glyphW = font != null ? font.GlyphWidth : 8;
|
||||
int glyphH = font != null ? font.GlyphHeight : 16;
|
||||
|
||||
Handles.BeginGUI();
|
||||
Vector2 topLeft = HandleUtility.WorldToGUIPoint(corners[1]);
|
||||
Vector2 botRight = HandleUtility.WorldToGUIPoint(corners[3]);
|
||||
|
||||
float rectScreenW = Mathf.Abs(botRight.x - topLeft.x);
|
||||
float rectW = rt.rect.width;
|
||||
float psxPixelScale = (rectW > 0.01f) ? rectScreenW / rectW : 1f;
|
||||
|
||||
float guiX = Mathf.Min(topLeft.x, botRight.x);
|
||||
float guiY = Mathf.Min(topLeft.y, botRight.y);
|
||||
float guiW = Mathf.Abs(botRight.x - topLeft.x);
|
||||
float guiH = Mathf.Abs(botRight.y - topLeft.y);
|
||||
|
||||
Color tintColor = text.TextColor;
|
||||
tintColor.a = selected ? 1f : 0.8f;
|
||||
|
||||
if (font != null && font.FontTexture != null && font.SourceFont != null)
|
||||
{
|
||||
Texture2D fontTex = font.FontTexture;
|
||||
int glyphsPerRow = font.GlyphsPerRow;
|
||||
float cellScreenH = glyphH * psxPixelScale;
|
||||
|
||||
float cursorX = guiX;
|
||||
GUI.color = tintColor;
|
||||
foreach (char ch in label)
|
||||
{
|
||||
if (ch < 32 || ch > 126) continue;
|
||||
int charIdx = ch - 32;
|
||||
int col = charIdx % glyphsPerRow;
|
||||
int row = charIdx / glyphsPerRow;
|
||||
|
||||
float advance = glyphW;
|
||||
if (font.AdvanceWidths != null && charIdx < font.AdvanceWidths.Length)
|
||||
advance = font.AdvanceWidths[charIdx];
|
||||
|
||||
if (ch != ' ')
|
||||
{
|
||||
float uvX = (float)(col * glyphW) / fontTex.width;
|
||||
float uvY = 1f - (float)((row + 1) * glyphH) / fontTex.height;
|
||||
float uvW = (float)glyphW / fontTex.width;
|
||||
float uvH = (float)glyphH / fontTex.height;
|
||||
|
||||
float spriteScreenW = advance * psxPixelScale;
|
||||
Rect screenRect = new Rect(cursorX, guiY, spriteScreenW, cellScreenH);
|
||||
float uvWScaled = uvW * (advance / glyphW);
|
||||
Rect uvRect = new Rect(uvX, uvY, uvWScaled, uvH);
|
||||
|
||||
if (screenRect.xMax > guiX && screenRect.x < guiX + guiW)
|
||||
GUI.DrawTextureWithTexCoords(screenRect, fontTex, uvRect);
|
||||
}
|
||||
|
||||
cursorX += advance * psxPixelScale;
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
else
|
||||
{
|
||||
int fSize = Mathf.Clamp(Mathf.RoundToInt(glyphH * psxPixelScale * 0.75f), 6, 72);
|
||||
GUIStyle style = new GUIStyle(EditorStyles.label);
|
||||
style.normal.textColor = tintColor;
|
||||
style.alignment = TextAnchor.UpperLeft;
|
||||
style.fontSize = fSize;
|
||||
style.wordWrap = false;
|
||||
style.clipping = TextClipping.Clip;
|
||||
|
||||
Rect guiRect = new Rect(guiX, guiY, guiW, guiH);
|
||||
GUI.color = tintColor;
|
||||
GUI.Label(guiRect, label, style);
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
Handles.EndGUI();
|
||||
}
|
||||
|
||||
static void DrawProgressBar(PSXUIProgressBar bar, bool selected)
|
||||
{
|
||||
RectTransform rt = bar.GetComponent<RectTransform>();
|
||||
if (rt == null) return;
|
||||
Vector3[] corners = new Vector3[4];
|
||||
rt.GetWorldCorners(corners);
|
||||
|
||||
Color bgColor = bar.BackgroundColor;
|
||||
bgColor.a = selected ? 1f : 0.9f;
|
||||
Handles.DrawSolidRectangleWithOutline(corners, bgColor, selected ? Color.white : new Color(1, 1, 1, 0.5f));
|
||||
|
||||
float t = bar.InitialValue / 100f;
|
||||
if (t > 0.001f)
|
||||
{
|
||||
Vector3[] fillCorners = new Vector3[4];
|
||||
fillCorners[0] = corners[0];
|
||||
fillCorners[1] = corners[1];
|
||||
fillCorners[2] = Vector3.Lerp(corners[1], corners[2], t);
|
||||
fillCorners[3] = Vector3.Lerp(corners[0], corners[3], t);
|
||||
Color fillColor = bar.FillColor;
|
||||
fillColor.a = selected ? 1f : 0.9f;
|
||||
Handles.DrawSolidRectangleWithOutline(fillCorners, fillColor, Color.clear);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Custom inspector for PSXCanvas component.
|
||||
/// Shows canvas name, visibility, sort order, font, and a summary of child elements.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(PSXCanvas))]
|
||||
public class PSXCanvasEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
Vector2 res = PSXCanvas.PSXResolution;
|
||||
|
||||
// Header card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.LabelField($"PSX Canvas ({res.x}x{res.y})", PSXEditorStyles.CardHeaderStyle);
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// Properties card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("canvasName"), new GUIContent("Canvas Name",
|
||||
"Name used from Lua: UI.FindCanvas(\"name\"). Max 24 chars."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"), new GUIContent("Start Visible",
|
||||
"Whether the canvas is visible when the scene loads."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("sortOrder"), new GUIContent("Sort Order",
|
||||
"Render priority (0 = back, 255 = front)."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("defaultFont"), new GUIContent("Default Font",
|
||||
"Default custom font for text elements. If empty, uses built-in system font (8x16)."));
|
||||
|
||||
PSXEditorStyles.DrawSeparator(6, 6);
|
||||
|
||||
if (GUILayout.Button($"Reset Canvas to {res.x}x{res.y}", PSXEditorStyles.SecondaryButton))
|
||||
{
|
||||
PSXCanvas.InvalidateResolutionCache();
|
||||
((PSXCanvas)target).ConfigureCanvas();
|
||||
}
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// Element summary card
|
||||
PSXCanvas canvas = (PSXCanvas)target;
|
||||
int imageCount = canvas.GetComponentsInChildren<PSXUIImage>(true).Length;
|
||||
int boxCount = canvas.GetComponentsInChildren<PSXUIBox>(true).Length;
|
||||
int textCount = canvas.GetComponentsInChildren<PSXUIText>(true).Length;
|
||||
int progressCount = canvas.GetComponentsInChildren<PSXUIProgressBar>(true).Length;
|
||||
int total = imageCount + boxCount + textCount + progressCount;
|
||||
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.LabelField(
|
||||
$"Elements: {total} total\n" +
|
||||
$" Images: {imageCount} | Boxes: {boxCount}\n" +
|
||||
$" Texts: {textCount} | Progress Bars: {progressCount}",
|
||||
PSXEditorStyles.InfoBox);
|
||||
|
||||
if (total > 128)
|
||||
EditorGUILayout.LabelField("PS1 UI system supports max 128 elements total across all canvases.", PSXEditorStyles.InfoBox);
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom inspector for PSXUIImage component.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(PSXUIImage))]
|
||||
public class PSXUIImageEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
// Header card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.LabelField("PSX UI Image", PSXEditorStyles.CardHeaderStyle);
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// Properties card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"), new GUIContent("Element Name",
|
||||
"Name used from Lua: UI.FindElement(canvas, \"name\"). Max 24 chars."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("sourceTexture"), new GUIContent("Source Texture",
|
||||
"Texture to quantize and pack into VRAM."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("bitDepth"), new GUIContent("Bit Depth",
|
||||
"VRAM storage depth. 4-bit = 16 colors, 8-bit = 256 colors, 16-bit = direct color."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("tintColor"), new GUIContent("Tint Color",
|
||||
"Color multiply applied to the image (white = no tint)."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
// Texture size warning
|
||||
PSXUIImage img = (PSXUIImage)target;
|
||||
if (img.SourceTexture != null)
|
||||
{
|
||||
if (img.SourceTexture.width > 256 || img.SourceTexture.height > 256)
|
||||
{
|
||||
EditorGUILayout.Space(4);
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.LabelField("Texture exceeds 256x256. It will be resized during export.", PSXEditorStyles.InfoBox);
|
||||
PSXEditorStyles.EndCard();
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom inspector for PSXUIBox component.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(PSXUIBox))]
|
||||
public class PSXUIBoxEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
// Header card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.LabelField("PSX UI Box", PSXEditorStyles.CardHeaderStyle);
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// Properties card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("boxColor"), new GUIContent("Box Color",
|
||||
"Solid fill color rendered as a FastFill primitive."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom inspector for PSXUIText component.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(PSXUIText))]
|
||||
public class PSXUITextEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
// Header card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.LabelField("PSX UI Text", PSXEditorStyles.CardHeaderStyle);
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// Properties card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("defaultText"), new GUIContent("Default Text",
|
||||
"Initial text content. Max 63 chars. Change at runtime via UI.SetText()."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("textColor"), new GUIContent("Text Color",
|
||||
"Text render color."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("fontOverride"), new GUIContent("Font Override",
|
||||
"Custom font for this text element. If empty, uses the canvas default font or built-in system font (8x16)."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// Warnings and info
|
||||
PSXUIText txt = (PSXUIText)target;
|
||||
if (!string.IsNullOrEmpty(txt.DefaultText) && txt.DefaultText.Length > 63)
|
||||
{
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.LabelField("Text exceeds 63 characters and will be truncated.", PSXEditorStyles.InfoBox);
|
||||
PSXEditorStyles.EndCard();
|
||||
EditorGUILayout.Space(4);
|
||||
}
|
||||
|
||||
PSXEditorStyles.BeginCard();
|
||||
PSXFontAsset font = txt.GetEffectiveFont();
|
||||
if (font != null)
|
||||
{
|
||||
EditorGUILayout.LabelField(
|
||||
$"Font: {font.name} ({font.GlyphWidth}x{font.GlyphHeight} glyphs)",
|
||||
PSXEditorStyles.InfoBox);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField("Using built-in system font (8x16 glyphs).", PSXEditorStyles.InfoBox);
|
||||
}
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom inspector for PSXUIProgressBar component.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(PSXUIProgressBar))]
|
||||
public class PSXUIProgressBarEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
// Header card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.LabelField("PSX UI Progress Bar", PSXEditorStyles.CardHeaderStyle);
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// Properties card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("backgroundColor"), new GUIContent("Background Color",
|
||||
"Color shown behind the fill bar."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("fillColor"), new GUIContent("Fill Color",
|
||||
"Color of the progress fill."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("initialValue"), new GUIContent("Initial Value",
|
||||
"Starting progress (0-100). Change via UI.SetProgress()."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
|
||||
|
||||
PSXEditorStyles.DrawSeparator(6, 4);
|
||||
|
||||
// Preview bar
|
||||
PSXUIProgressBar bar = (PSXUIProgressBar)target;
|
||||
PSXEditorStyles.DrawProgressBar(bar.InitialValue / 100f, "Preview", bar.FillColor, 16);
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom inspector for PSXFontAsset ScriptableObject.
|
||||
/// Shows font metrics, auto-conversion from TTF/OTF, and a preview of the glyph layout.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(PSXFontAsset))]
|
||||
public class PSXFontAssetEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
PSXFontAsset font = (PSXFontAsset)target;
|
||||
|
||||
// Header card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.LabelField("PSX Font Asset", PSXEditorStyles.CardHeaderStyle);
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// Source font card
|
||||
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)",
|
||||
"Assign a Unity Font (TrueType/OpenType). Click 'Generate Bitmap' to rasterize it.\n" +
|
||||
"Glyph cell dimensions are auto-derived from the font metrics."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("fontSize"), new GUIContent("Font Size",
|
||||
"Pixel height for rasterization. Determines glyph cell height.\n" +
|
||||
"Glyph cell width is auto-derived from the widest character.\n" +
|
||||
"Changing this and re-generating will update both the bitmap AND the glyph dimensions."));
|
||||
|
||||
if (font.SourceFont != null)
|
||||
{
|
||||
EditorGUILayout.Space(2);
|
||||
if (GUILayout.Button("Generate Bitmap from Font", PSXEditorStyles.PrimaryButton, GUILayout.Height(28)))
|
||||
{
|
||||
Undo.RecordObject(font, "Generate PSX Font Bitmap");
|
||||
font.GenerateBitmapFromFont();
|
||||
}
|
||||
|
||||
if (font.FontTexture == null)
|
||||
EditorGUILayout.LabelField(
|
||||
"Click 'Generate Bitmap' to create the font texture.\n" +
|
||||
"If generation fails, check that the font's import settings have " +
|
||||
"'Character' set to 'ASCII Default Set'.", PSXEditorStyles.InfoBox);
|
||||
}
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// Manual bitmap card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.LabelField("Manual Bitmap Source", PSXEditorStyles.CardHeaderStyle);
|
||||
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)
|
||||
{
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
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);
|
||||
EditorGUI.EndDisabledGroup();
|
||||
EditorGUILayout.LabelField("Glyph dimensions are auto-derived when generating from a font.\n" +
|
||||
"Change the Font Size slider and re-generate to adjust.", PSXEditorStyles.InfoBox);
|
||||
}
|
||||
else
|
||||
{
|
||||
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)."));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("glyphHeight"), new GUIContent("Glyph Height",
|
||||
"Height of each glyph cell in pixels."));
|
||||
}
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// Layout info card
|
||||
PSXEditorStyles.BeginCard();
|
||||
int glyphsPerRow = font.GlyphsPerRow;
|
||||
int rowCount = font.RowCount;
|
||||
int totalH = font.TextureHeight;
|
||||
int vramBytes = totalH * 128;
|
||||
|
||||
EditorGUILayout.LabelField(
|
||||
$"Layout: {glyphsPerRow} glyphs/row, {rowCount} rows\n" +
|
||||
$"Texture: 256 x {totalH} pixels (4bpp)\n" +
|
||||
$"VRAM: {vramBytes} bytes ({vramBytes / 1024f:F1} KB)\n" +
|
||||
$"Glyph cell: {font.GlyphWidth} x {font.GlyphHeight}",
|
||||
PSXEditorStyles.InfoBox);
|
||||
|
||||
if (font.AdvanceWidths != null && font.AdvanceWidths.Length >= 95)
|
||||
{
|
||||
int minAdv = 255, maxAdv = 0;
|
||||
for (int i = 1; i < 95; i++)
|
||||
{
|
||||
if (font.AdvanceWidths[i] < minAdv) minAdv = font.AdvanceWidths[i];
|
||||
if (font.AdvanceWidths[i] > maxAdv) maxAdv = font.AdvanceWidths[i];
|
||||
}
|
||||
EditorGUILayout.LabelField(
|
||||
$"Advance widths: {minAdv}-{maxAdv}px (proportional spacing stored)",
|
||||
PSXEditorStyles.InfoBox);
|
||||
}
|
||||
else if (font.FontTexture != null)
|
||||
{
|
||||
EditorGUILayout.LabelField(
|
||||
"No advance widths stored. Click 'Generate Bitmap' to compute them.",
|
||||
PSXEditorStyles.InfoBox);
|
||||
}
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
// Validation
|
||||
if (font.FontTexture != null)
|
||||
{
|
||||
if (font.FontTexture.width != 256)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
// Preview
|
||||
EditorGUILayout.Space(4);
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.LabelField("Preview", PSXEditorStyles.CardHeaderStyle);
|
||||
PSXEditorStyles.DrawSeparator(2, 4);
|
||||
Rect previewRect = EditorGUILayout.GetControlRect(false, 64);
|
||||
GUI.DrawTexture(previewRect, font.FontTexture, ScaleMode.ScaleToFit);
|
||||
PSXEditorStyles.EndCard();
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Editor/Inspectors/PSXUIEditors.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 385b3916e29dc0e48b2866851d1fc1a9
|
||||
789
Editor/PSXCutsceneEditor.cs
Normal file
@@ -0,0 +1,789 @@
|
||||
#if UNITY_EDITOR
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
[CustomEditor(typeof(PSXCutsceneClip))]
|
||||
public class PSXCutsceneEditor : Editor
|
||||
{
|
||||
// ── Preview state ──
|
||||
private bool _showAudioEvents = true;
|
||||
private bool _previewing;
|
||||
private bool _playing;
|
||||
private float _previewFrame;
|
||||
private double _playStartEditorTime;
|
||||
private float _playStartFrame;
|
||||
private HashSet<int> _firedAudioEventIndices = new HashSet<int>();
|
||||
|
||||
// Saved scene-view state so we can restore after preview
|
||||
private bool _hasSavedSceneView;
|
||||
private Vector3 _savedPivot;
|
||||
private Quaternion _savedRotation;
|
||||
private float _savedSize;
|
||||
|
||||
// Saved object transforms
|
||||
private Dictionary<string, Vector3> _savedObjectPositions = new Dictionary<string, Vector3>();
|
||||
private Dictionary<string, Quaternion> _savedObjectRotations = new Dictionary<string, Quaternion>();
|
||||
private Dictionary<string, bool> _savedObjectActive = new Dictionary<string, bool>();
|
||||
|
||||
// Audio preview
|
||||
private Dictionary<string, AudioClip> _audioClipCache = new Dictionary<string, AudioClip>();
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
if (_previewing) StopPreview();
|
||||
}
|
||||
|
||||
private void OnEditorUpdate()
|
||||
{
|
||||
if (!_playing) return;
|
||||
|
||||
PSXCutsceneClip clip = (PSXCutsceneClip)target;
|
||||
double elapsed = EditorApplication.timeSinceStartup - _playStartEditorTime;
|
||||
_previewFrame = _playStartFrame + (float)(elapsed * 30.0);
|
||||
|
||||
if (_previewFrame >= clip.DurationFrames)
|
||||
{
|
||||
_previewFrame = clip.DurationFrames;
|
||||
_playing = false;
|
||||
}
|
||||
|
||||
ApplyPreview(clip);
|
||||
Repaint();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
PSXCutsceneClip clip = (PSXCutsceneClip)target;
|
||||
Undo.RecordObject(clip, "Edit Cutscene");
|
||||
|
||||
// ── Header ──
|
||||
EditorGUILayout.Space(4);
|
||||
EditorGUILayout.LabelField("Cutscene Settings", EditorStyles.boldLabel);
|
||||
|
||||
clip.CutsceneName = EditorGUILayout.TextField("Cutscene Name", clip.CutsceneName);
|
||||
if (!string.IsNullOrEmpty(clip.CutsceneName) && clip.CutsceneName.Length > 24)
|
||||
EditorGUILayout.HelpBox("Name exceeds 24 characters and will be truncated on export.", MessageType.Warning);
|
||||
|
||||
clip.DurationFrames = EditorGUILayout.IntField("Duration (frames)", clip.DurationFrames);
|
||||
if (clip.DurationFrames < 1) clip.DurationFrames = 1;
|
||||
|
||||
float seconds = clip.DurationFrames / 30f;
|
||||
EditorGUILayout.LabelField($" = {seconds:F2} seconds at 30fps", EditorStyles.miniLabel);
|
||||
|
||||
// ── Preview Controls ──
|
||||
EditorGUILayout.Space(6);
|
||||
DrawPreviewControls(clip);
|
||||
|
||||
// Collect scene references for validation
|
||||
var exporterNames = new HashSet<string>();
|
||||
var audioNames = new HashSet<string>();
|
||||
var canvasNames = new HashSet<string>();
|
||||
var elementNames = new Dictionary<string, HashSet<string>>(); // canvas → element names
|
||||
var exporters = Object.FindObjectsByType<PSXObjectExporter>(FindObjectsSortMode.None);
|
||||
foreach (var e in exporters)
|
||||
exporterNames.Add(e.gameObject.name);
|
||||
var audioSources = Object.FindObjectsByType<PSXAudioClip>(FindObjectsSortMode.None);
|
||||
foreach (var a in audioSources)
|
||||
if (!string.IsNullOrEmpty(a.ClipName))
|
||||
audioNames.Add(a.ClipName);
|
||||
var canvases = Object.FindObjectsByType<PSXCanvas>(FindObjectsSortMode.None);
|
||||
foreach (var c in canvases)
|
||||
{
|
||||
string cName = c.CanvasName ?? "";
|
||||
if (!string.IsNullOrEmpty(cName))
|
||||
{
|
||||
canvasNames.Add(cName);
|
||||
if (!elementNames.ContainsKey(cName))
|
||||
elementNames[cName] = new HashSet<string>();
|
||||
// Gather all UI element names under this canvas
|
||||
foreach (var box in c.GetComponentsInChildren<PSXUIBox>())
|
||||
if (!string.IsNullOrEmpty(box.ElementName)) elementNames[cName].Add(box.ElementName);
|
||||
foreach (var txt in c.GetComponentsInChildren<PSXUIText>())
|
||||
if (!string.IsNullOrEmpty(txt.ElementName)) elementNames[cName].Add(txt.ElementName);
|
||||
foreach (var bar in c.GetComponentsInChildren<PSXUIProgressBar>())
|
||||
if (!string.IsNullOrEmpty(bar.ElementName)) elementNames[cName].Add(bar.ElementName);
|
||||
foreach (var img in c.GetComponentsInChildren<PSXUIImage>())
|
||||
if (!string.IsNullOrEmpty(img.ElementName)) elementNames[cName].Add(img.ElementName);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tracks ──
|
||||
EditorGUILayout.Space(8);
|
||||
EditorGUILayout.LabelField("Tracks", EditorStyles.boldLabel);
|
||||
|
||||
if (clip.Tracks == null) clip.Tracks = new List<PSXCutsceneTrack>();
|
||||
|
||||
int removeTrackIdx = -1;
|
||||
for (int ti = 0; ti < clip.Tracks.Count; ti++)
|
||||
{
|
||||
var track = clip.Tracks[ti];
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
track.TrackType = (PSXTrackType)EditorGUILayout.EnumPopup("Type", track.TrackType);
|
||||
if (GUILayout.Button("Remove", GUILayout.Width(65)))
|
||||
removeTrackIdx = ti;
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
bool isCameraTrack = track.TrackType == PSXTrackType.CameraPosition || track.TrackType == PSXTrackType.CameraRotation;
|
||||
bool isUITrack = track.IsUITrack;
|
||||
bool isUIElementTrack = track.IsUIElementTrack;
|
||||
|
||||
if (isCameraTrack)
|
||||
{
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
EditorGUILayout.TextField("Target", "(camera)");
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
else if (isUITrack)
|
||||
{
|
||||
track.UICanvasName = EditorGUILayout.TextField("Canvas Name", track.UICanvasName);
|
||||
if (!string.IsNullOrEmpty(track.UICanvasName) && !canvasNames.Contains(track.UICanvasName))
|
||||
EditorGUILayout.HelpBox($"No PSXCanvas with name '{track.UICanvasName}' in scene.", MessageType.Error);
|
||||
|
||||
if (isUIElementTrack)
|
||||
{
|
||||
track.UIElementName = EditorGUILayout.TextField("Element Name", track.UIElementName);
|
||||
if (!string.IsNullOrEmpty(track.UICanvasName) && !string.IsNullOrEmpty(track.UIElementName))
|
||||
{
|
||||
if (elementNames.TryGetValue(track.UICanvasName, out var elNames) && !elNames.Contains(track.UIElementName))
|
||||
EditorGUILayout.HelpBox($"No UI element '{track.UIElementName}' found under canvas '{track.UICanvasName}'.", MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
track.ObjectName = EditorGUILayout.TextField("Object Name", track.ObjectName);
|
||||
|
||||
// Validation
|
||||
if (!string.IsNullOrEmpty(track.ObjectName) && !exporterNames.Contains(track.ObjectName))
|
||||
EditorGUILayout.HelpBox($"No PSXObjectExporter found for '{track.ObjectName}' in scene.", MessageType.Error);
|
||||
}
|
||||
|
||||
// ── Keyframes ──
|
||||
if (track.Keyframes == null) track.Keyframes = new List<PSXKeyframe>();
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.LabelField($"Keyframes ({track.Keyframes.Count})", EditorStyles.miniLabel);
|
||||
|
||||
int removeKfIdx = -1;
|
||||
for (int ki = 0; ki < track.Keyframes.Count; ki++)
|
||||
{
|
||||
var kf = track.Keyframes[ki];
|
||||
|
||||
// Row 1: frame number + interp mode + buttons
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("Frame", GUILayout.Width(42));
|
||||
kf.Frame = EditorGUILayout.IntField(kf.Frame, GUILayout.Width(60));
|
||||
kf.Interp = (PSXInterpMode)EditorGUILayout.EnumPopup(kf.Interp, GUILayout.Width(80));
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
// Capture from scene
|
||||
if (isCameraTrack)
|
||||
{
|
||||
if (GUILayout.Button("Capture Cam", GUILayout.Width(90)))
|
||||
{
|
||||
var sv = SceneView.lastActiveSceneView;
|
||||
if (sv != null)
|
||||
kf.Value = track.TrackType == PSXTrackType.CameraPosition
|
||||
? sv.camera.transform.position : sv.camera.transform.eulerAngles;
|
||||
else Debug.LogWarning("No active Scene View.");
|
||||
}
|
||||
}
|
||||
else if (!isUITrack && (track.TrackType == PSXTrackType.ObjectPosition || track.TrackType == PSXTrackType.ObjectRotation))
|
||||
{
|
||||
// Capture from the named object in scene
|
||||
if (!string.IsNullOrEmpty(track.ObjectName) && GUILayout.Button("From Object", GUILayout.Width(85)))
|
||||
{
|
||||
var go = GameObject.Find(track.ObjectName);
|
||||
if (go != null)
|
||||
kf.Value = track.TrackType == PSXTrackType.ObjectPosition
|
||||
? go.transform.position : go.transform.eulerAngles;
|
||||
else Debug.LogWarning($"Object '{track.ObjectName}' not found in scene.");
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("\u2212", GUILayout.Width(22)))
|
||||
removeKfIdx = ki;
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
// Row 2: value on its own line
|
||||
EditorGUI.indentLevel++;
|
||||
switch (track.TrackType)
|
||||
{
|
||||
case PSXTrackType.ObjectActive:
|
||||
case PSXTrackType.UICanvasVisible:
|
||||
case PSXTrackType.UIElementVisible:
|
||||
{
|
||||
string label = track.TrackType == PSXTrackType.ObjectActive ? "Active" : "Visible";
|
||||
bool active = EditorGUILayout.Toggle(label, kf.Value.x > 0.5f);
|
||||
kf.Value = new Vector3(active ? 1f : 0f, 0, 0);
|
||||
break;
|
||||
}
|
||||
case PSXTrackType.ObjectRotation:
|
||||
case PSXTrackType.CameraRotation:
|
||||
{
|
||||
kf.Value = EditorGUILayout.Vector3Field("Rotation\u00b0", kf.Value);
|
||||
break;
|
||||
}
|
||||
case PSXTrackType.UIProgress:
|
||||
{
|
||||
float progress = EditorGUILayout.Slider("Progress %", kf.Value.x, 0f, 100f);
|
||||
kf.Value = new Vector3(progress, 0, 0);
|
||||
break;
|
||||
}
|
||||
case PSXTrackType.UIPosition:
|
||||
{
|
||||
Vector2 pos = EditorGUILayout.Vector2Field("Position (px)", new Vector2(kf.Value.x, kf.Value.y));
|
||||
kf.Value = new Vector3(pos.x, pos.y, 0);
|
||||
break;
|
||||
}
|
||||
case PSXTrackType.UIColor:
|
||||
{
|
||||
// Show as RGB 0-255 integers
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("R", GUILayout.Width(14));
|
||||
float r = EditorGUILayout.IntField(Mathf.Clamp(Mathf.RoundToInt(kf.Value.x), 0, 255), GUILayout.Width(40));
|
||||
EditorGUILayout.LabelField("G", GUILayout.Width(14));
|
||||
float g = EditorGUILayout.IntField(Mathf.Clamp(Mathf.RoundToInt(kf.Value.y), 0, 255), GUILayout.Width(40));
|
||||
EditorGUILayout.LabelField("B", GUILayout.Width(14));
|
||||
float b = EditorGUILayout.IntField(Mathf.Clamp(Mathf.RoundToInt(kf.Value.z), 0, 255), GUILayout.Width(40));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
kf.Value = new Vector3(r, g, b);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
kf.Value = EditorGUILayout.Vector3Field("Value", kf.Value);
|
||||
break;
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
if (ki < track.Keyframes.Count - 1)
|
||||
{
|
||||
EditorGUILayout.Space(1);
|
||||
var rect = EditorGUILayout.GetControlRect(false, 1);
|
||||
EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 0.3f));
|
||||
}
|
||||
}
|
||||
|
||||
if (removeKfIdx >= 0) track.Keyframes.RemoveAt(removeKfIdx);
|
||||
|
||||
// Add keyframe buttons
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("+ Keyframe", GUILayout.Width(90)))
|
||||
{
|
||||
int frame = track.Keyframes.Count > 0 ? track.Keyframes[track.Keyframes.Count - 1].Frame + 15 : 0;
|
||||
track.Keyframes.Add(new PSXKeyframe { Frame = frame, Value = Vector3.zero });
|
||||
}
|
||||
if (isCameraTrack)
|
||||
{
|
||||
if (GUILayout.Button("+ from Scene Cam", GUILayout.Width(130)))
|
||||
{
|
||||
var sv = SceneView.lastActiveSceneView;
|
||||
Vector3 val = Vector3.zero;
|
||||
if (sv != null)
|
||||
val = track.TrackType == PSXTrackType.CameraPosition
|
||||
? sv.camera.transform.position : sv.camera.transform.eulerAngles;
|
||||
int frame = track.Keyframes.Count > 0 ? track.Keyframes[track.Keyframes.Count - 1].Frame + 15 : 0;
|
||||
track.Keyframes.Add(new PSXKeyframe { Frame = frame, Value = val });
|
||||
}
|
||||
}
|
||||
else if (!isUITrack && (track.TrackType == PSXTrackType.ObjectPosition || track.TrackType == PSXTrackType.ObjectRotation))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(track.ObjectName))
|
||||
{
|
||||
if (GUILayout.Button("+ from Object", GUILayout.Width(110)))
|
||||
{
|
||||
var go = GameObject.Find(track.ObjectName);
|
||||
Vector3 val = Vector3.zero;
|
||||
if (go != null)
|
||||
val = track.TrackType == PSXTrackType.ObjectPosition
|
||||
? go.transform.position : go.transform.eulerAngles;
|
||||
int frame = track.Keyframes.Count > 0 ? track.Keyframes[track.Keyframes.Count - 1].Frame + 15 : 0;
|
||||
track.Keyframes.Add(new PSXKeyframe { Frame = frame, Value = val });
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.Space(2);
|
||||
}
|
||||
|
||||
if (removeTrackIdx >= 0) clip.Tracks.RemoveAt(removeTrackIdx);
|
||||
|
||||
if (clip.Tracks.Count < 8)
|
||||
{
|
||||
if (GUILayout.Button("+ Add Track"))
|
||||
clip.Tracks.Add(new PSXCutsceneTrack());
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("Maximum 8 tracks per cutscene.", MessageType.Info);
|
||||
}
|
||||
|
||||
// ── Audio Events ──
|
||||
EditorGUILayout.Space(8);
|
||||
_showAudioEvents = EditorGUILayout.Foldout(_showAudioEvents, "Audio Events", true);
|
||||
if (_showAudioEvents)
|
||||
{
|
||||
if (clip.AudioEvents == null) clip.AudioEvents = new List<PSXAudioEvent>();
|
||||
|
||||
int removeEventIdx = -1;
|
||||
for (int ei = 0; ei < clip.AudioEvents.Count; ei++)
|
||||
{
|
||||
var evt = clip.AudioEvents[ei];
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("Frame", GUILayout.Width(42));
|
||||
evt.Frame = EditorGUILayout.IntField(evt.Frame, GUILayout.Width(60));
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("\u2212", GUILayout.Width(22)))
|
||||
removeEventIdx = ei;
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
evt.ClipName = EditorGUILayout.TextField("Clip Name", evt.ClipName);
|
||||
if (!string.IsNullOrEmpty(evt.ClipName) && !audioNames.Contains(evt.ClipName))
|
||||
EditorGUILayout.HelpBox($"No PSXAudioClip with ClipName '{evt.ClipName}' in scene.", MessageType.Error);
|
||||
|
||||
evt.Volume = EditorGUILayout.IntSlider("Volume", evt.Volume, 0, 128);
|
||||
evt.Pan = EditorGUILayout.IntSlider("Pan", evt.Pan, 0, 127);
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
if (removeEventIdx >= 0) clip.AudioEvents.RemoveAt(removeEventIdx);
|
||||
|
||||
if (clip.AudioEvents.Count < 64)
|
||||
{
|
||||
if (GUILayout.Button("+ Add Audio Event"))
|
||||
clip.AudioEvents.Add(new PSXAudioEvent());
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("Maximum 64 audio events per cutscene.", MessageType.Info);
|
||||
}
|
||||
}
|
||||
|
||||
if (GUI.changed)
|
||||
EditorUtility.SetDirty(clip);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Preview Controls
|
||||
// =====================================================================
|
||||
|
||||
private void DrawPreviewControls(PSXCutsceneClip clip)
|
||||
{
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
|
||||
|
||||
// Transport bar
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
bool wasPlaying = _playing;
|
||||
if (_playing)
|
||||
{
|
||||
if (GUILayout.Button("\u275A\u275A Pause", GUILayout.Width(70)))
|
||||
_playing = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("\u25B6 Play", GUILayout.Width(70)))
|
||||
{
|
||||
if (!_previewing) StartPreview(clip);
|
||||
_playing = true;
|
||||
_playStartEditorTime = EditorApplication.timeSinceStartup;
|
||||
_playStartFrame = _previewFrame;
|
||||
_firedAudioEventIndices.Clear();
|
||||
// Mark already-passed events so they won't fire again
|
||||
if (clip.AudioEvents != null)
|
||||
for (int i = 0; i < clip.AudioEvents.Count; i++)
|
||||
if (clip.AudioEvents[i].Frame < (int)_previewFrame)
|
||||
_firedAudioEventIndices.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("\u25A0 Stop", GUILayout.Width(60)))
|
||||
{
|
||||
_playing = false;
|
||||
_previewFrame = 0;
|
||||
if (_previewing) StopPreview();
|
||||
}
|
||||
|
||||
if (_previewing)
|
||||
{
|
||||
GUI.color = new Color(1f, 0.4f, 0.4f);
|
||||
if (GUILayout.Button("End Preview", GUILayout.Width(90)))
|
||||
{
|
||||
_playing = false;
|
||||
StopPreview();
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
// Timeline scrubber
|
||||
EditorGUI.BeginChangeCheck();
|
||||
float newFrame = EditorGUILayout.Slider("Frame", _previewFrame, 0, clip.DurationFrames);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (!_previewing) StartPreview(clip);
|
||||
_previewFrame = newFrame;
|
||||
_playing = false;
|
||||
_firedAudioEventIndices.Clear();
|
||||
ApplyPreview(clip);
|
||||
}
|
||||
|
||||
float previewSec = _previewFrame / 30f;
|
||||
EditorGUILayout.LabelField(
|
||||
$" {(int)_previewFrame} / {clip.DurationFrames} ({previewSec:F2}s / {seconds(clip):F2}s)",
|
||||
EditorStyles.miniLabel);
|
||||
|
||||
if (_previewing)
|
||||
EditorGUILayout.HelpBox(
|
||||
"PREVIEWING: Scene View camera & objects are being driven. " +
|
||||
"Click \u201cEnd Preview\u201d or \u201cStop\u201d to restore original positions.",
|
||||
MessageType.Warning);
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private static float seconds(PSXCutsceneClip clip) => clip.DurationFrames / 30f;
|
||||
|
||||
// =====================================================================
|
||||
// Preview Lifecycle
|
||||
// =====================================================================
|
||||
|
||||
private void StartPreview(PSXCutsceneClip clip)
|
||||
{
|
||||
if (_previewing) return;
|
||||
_previewing = true;
|
||||
_firedAudioEventIndices.Clear();
|
||||
|
||||
// Save scene view camera
|
||||
var sv = SceneView.lastActiveSceneView;
|
||||
if (sv != null)
|
||||
{
|
||||
_hasSavedSceneView = true;
|
||||
_savedPivot = sv.pivot;
|
||||
_savedRotation = sv.rotation;
|
||||
_savedSize = sv.size;
|
||||
}
|
||||
|
||||
// Save object transforms
|
||||
_savedObjectPositions.Clear();
|
||||
_savedObjectRotations.Clear();
|
||||
_savedObjectActive.Clear();
|
||||
if (clip.Tracks != null)
|
||||
{
|
||||
foreach (var track in clip.Tracks)
|
||||
{
|
||||
if (string.IsNullOrEmpty(track.ObjectName)) continue;
|
||||
bool isCam = track.TrackType == PSXTrackType.CameraPosition || track.TrackType == PSXTrackType.CameraRotation;
|
||||
if (isCam) continue;
|
||||
|
||||
var go = GameObject.Find(track.ObjectName);
|
||||
if (go == null) continue;
|
||||
|
||||
if (!_savedObjectPositions.ContainsKey(track.ObjectName))
|
||||
{
|
||||
_savedObjectPositions[track.ObjectName] = go.transform.position;
|
||||
_savedObjectRotations[track.ObjectName] = go.transform.rotation;
|
||||
_savedObjectActive[track.ObjectName] = go.activeSelf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build audio clip lookup
|
||||
_audioClipCache.Clear();
|
||||
var audioSources = Object.FindObjectsByType<PSXAudioClip>(FindObjectsSortMode.None);
|
||||
foreach (var a in audioSources)
|
||||
if (!string.IsNullOrEmpty(a.ClipName) && a.Clip != null)
|
||||
_audioClipCache[a.ClipName] = a.Clip;
|
||||
}
|
||||
|
||||
private void StopPreview()
|
||||
{
|
||||
if (!_previewing) return;
|
||||
_previewing = false;
|
||||
_playing = false;
|
||||
|
||||
// Restore scene view camera
|
||||
if (_hasSavedSceneView)
|
||||
{
|
||||
var sv = SceneView.lastActiveSceneView;
|
||||
if (sv != null)
|
||||
{
|
||||
sv.pivot = _savedPivot;
|
||||
sv.rotation = _savedRotation;
|
||||
sv.size = _savedSize;
|
||||
sv.Repaint();
|
||||
}
|
||||
_hasSavedSceneView = false;
|
||||
}
|
||||
|
||||
// Restore object transforms
|
||||
foreach (var kvp in _savedObjectPositions)
|
||||
{
|
||||
var go = GameObject.Find(kvp.Key);
|
||||
if (go == null) continue;
|
||||
go.transform.position = kvp.Value;
|
||||
if (_savedObjectRotations.ContainsKey(kvp.Key))
|
||||
go.transform.rotation = _savedObjectRotations[kvp.Key];
|
||||
if (_savedObjectActive.ContainsKey(kvp.Key))
|
||||
go.SetActive(_savedObjectActive[kvp.Key]);
|
||||
}
|
||||
|
||||
_savedObjectPositions.Clear();
|
||||
_savedObjectRotations.Clear();
|
||||
_savedObjectActive.Clear();
|
||||
|
||||
SceneView.RepaintAll();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Apply Preview at Current Frame
|
||||
// =====================================================================
|
||||
|
||||
private void ApplyPreview(PSXCutsceneClip clip)
|
||||
{
|
||||
if (!_previewing) return;
|
||||
float frame = _previewFrame;
|
||||
|
||||
var sv = SceneView.lastActiveSceneView;
|
||||
Vector3? camPos = null;
|
||||
Quaternion? camRot = null;
|
||||
|
||||
if (clip.Tracks != null)
|
||||
{
|
||||
foreach (var track in clip.Tracks)
|
||||
{
|
||||
// Compute initial value for pre-first-keyframe blending
|
||||
Vector3 initialVal = Vector3.zero;
|
||||
switch (track.TrackType)
|
||||
{
|
||||
case PSXTrackType.CameraPosition:
|
||||
if (sv != null)
|
||||
// Recover position from saved pivot/rotation/size
|
||||
initialVal = _savedPivot - _savedRotation * Vector3.forward * _savedSize;
|
||||
break;
|
||||
case PSXTrackType.CameraRotation:
|
||||
initialVal = _savedRotation.eulerAngles;
|
||||
break;
|
||||
case PSXTrackType.ObjectPosition:
|
||||
if (_savedObjectPositions.ContainsKey(track.ObjectName ?? ""))
|
||||
initialVal = _savedObjectPositions[track.ObjectName];
|
||||
break;
|
||||
case PSXTrackType.ObjectRotation:
|
||||
if (_savedObjectRotations.ContainsKey(track.ObjectName ?? ""))
|
||||
initialVal = _savedObjectRotations[track.ObjectName].eulerAngles;
|
||||
break;
|
||||
case PSXTrackType.ObjectActive:
|
||||
if (_savedObjectActive.ContainsKey(track.ObjectName ?? ""))
|
||||
initialVal = new Vector3(_savedObjectActive[track.ObjectName] ? 1f : 0f, 0, 0);
|
||||
break;
|
||||
// UI tracks: initial values stay zero (no scene preview state to capture)
|
||||
case PSXTrackType.UICanvasVisible:
|
||||
case PSXTrackType.UIElementVisible:
|
||||
initialVal = new Vector3(1f, 0, 0); // assume visible by default
|
||||
break;
|
||||
case PSXTrackType.UIProgress:
|
||||
case PSXTrackType.UIPosition:
|
||||
case PSXTrackType.UIColor:
|
||||
break; // zero is fine
|
||||
}
|
||||
|
||||
Vector3 val = EvaluateTrack(track, frame, initialVal);
|
||||
|
||||
switch (track.TrackType)
|
||||
{
|
||||
case PSXTrackType.CameraPosition:
|
||||
camPos = val;
|
||||
break;
|
||||
case PSXTrackType.CameraRotation:
|
||||
camRot = Quaternion.Euler(val);
|
||||
break;
|
||||
case PSXTrackType.ObjectPosition:
|
||||
{
|
||||
var go = GameObject.Find(track.ObjectName);
|
||||
if (go != null) go.transform.position = val;
|
||||
break;
|
||||
}
|
||||
case PSXTrackType.ObjectRotation:
|
||||
{
|
||||
var go = GameObject.Find(track.ObjectName);
|
||||
if (go != null) go.transform.rotation = Quaternion.Euler(val);
|
||||
break;
|
||||
}
|
||||
case PSXTrackType.ObjectActive:
|
||||
{
|
||||
var go = GameObject.Find(track.ObjectName);
|
||||
if (go != null) go.SetActive(val.x > 0.5f);
|
||||
break;
|
||||
}
|
||||
// UI tracks: no scene preview, values are applied on PS1 only
|
||||
case PSXTrackType.UICanvasVisible:
|
||||
case PSXTrackType.UIElementVisible:
|
||||
case PSXTrackType.UIProgress:
|
||||
case PSXTrackType.UIPosition:
|
||||
case PSXTrackType.UIColor:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drive scene view camera
|
||||
if (sv != null && (camPos.HasValue || camRot.HasValue))
|
||||
{
|
||||
Vector3 pos = camPos ?? sv.camera.transform.position;
|
||||
Quaternion rot = camRot ?? sv.camera.transform.rotation;
|
||||
|
||||
// SceneView needs pivot and rotation set — pivot = position + forward * size
|
||||
sv.rotation = rot;
|
||||
sv.pivot = pos + rot * Vector3.forward * sv.cameraDistance;
|
||||
sv.Repaint();
|
||||
}
|
||||
|
||||
// Fire audio events (only during playback, not scrubbing)
|
||||
if (_playing && clip.AudioEvents != null)
|
||||
{
|
||||
for (int i = 0; i < clip.AudioEvents.Count; i++)
|
||||
{
|
||||
if (_firedAudioEventIndices.Contains(i)) continue;
|
||||
var evt = clip.AudioEvents[i];
|
||||
if (frame >= evt.Frame)
|
||||
{
|
||||
_firedAudioEventIndices.Add(i);
|
||||
PlayAudioPreview(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Track Evaluation (linear interpolation, matching C++ runtime)
|
||||
// =====================================================================
|
||||
|
||||
private static Vector3 EvaluateTrack(PSXCutsceneTrack track, float frame, Vector3 initialValue)
|
||||
{
|
||||
if (track.Keyframes == null || track.Keyframes.Count == 0)
|
||||
return Vector3.zero;
|
||||
|
||||
// Step interpolation tracks: ObjectActive, UICanvasVisible, UIElementVisible
|
||||
if (track.TrackType == PSXTrackType.ObjectActive ||
|
||||
track.TrackType == PSXTrackType.UICanvasVisible ||
|
||||
track.TrackType == PSXTrackType.UIElementVisible)
|
||||
{
|
||||
if (track.Keyframes.Count > 0 && track.Keyframes[0].Frame > 0 && frame < track.Keyframes[0].Frame)
|
||||
return initialValue;
|
||||
return EvaluateStep(track.Keyframes, frame);
|
||||
}
|
||||
|
||||
// Find surrounding keyframes
|
||||
PSXKeyframe before = null, after = null;
|
||||
for (int i = 0; i < track.Keyframes.Count; i++)
|
||||
{
|
||||
if (track.Keyframes[i].Frame <= frame)
|
||||
before = track.Keyframes[i];
|
||||
if (track.Keyframes[i].Frame >= frame && after == null)
|
||||
after = track.Keyframes[i];
|
||||
}
|
||||
|
||||
if (before == null && after == null) return Vector3.zero;
|
||||
|
||||
// Pre-first-keyframe: blend from initial value to first keyframe
|
||||
if (before == null && after != null && after.Frame > 0 && frame < after.Frame)
|
||||
{
|
||||
float rawT = frame / after.Frame;
|
||||
float t = ApplyInterpCurve(rawT, after.Interp);
|
||||
return Vector3.Lerp(initialValue, after.Value, t);
|
||||
}
|
||||
|
||||
if (before == null) return after.Value;
|
||||
if (after == null) return before.Value;
|
||||
if (before == after) return before.Value;
|
||||
|
||||
float span = after.Frame - before.Frame;
|
||||
float rawT2 = (frame - before.Frame) / span;
|
||||
float t2 = ApplyInterpCurve(rawT2, after.Interp);
|
||||
|
||||
// Linear interpolation for all tracks including rotation.
|
||||
// No shortest-path wrapping: a keyframe from 0 to 360 rotates the full circle.
|
||||
return Vector3.Lerp(before.Value, after.Value, t2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply easing curve to a linear t value (0..1). Matches the C++ applyCurve().
|
||||
/// </summary>
|
||||
private static float ApplyInterpCurve(float t, PSXInterpMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
default:
|
||||
case PSXInterpMode.Linear:
|
||||
return t;
|
||||
case PSXInterpMode.Step:
|
||||
return 0f;
|
||||
case PSXInterpMode.EaseIn:
|
||||
return t * t;
|
||||
case PSXInterpMode.EaseOut:
|
||||
return t * (2f - t);
|
||||
case PSXInterpMode.EaseInOut:
|
||||
return t * t * (3f - 2f * t);
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector3 EvaluateStep(List<PSXKeyframe> keyframes, float frame)
|
||||
{
|
||||
Vector3 result = Vector3.zero;
|
||||
for (int i = 0; i < keyframes.Count; i++)
|
||||
{
|
||||
if (keyframes[i].Frame <= frame)
|
||||
result = keyframes[i].Value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Audio Preview
|
||||
// =====================================================================
|
||||
|
||||
private void PlayAudioPreview(PSXAudioEvent evt)
|
||||
{
|
||||
if (string.IsNullOrEmpty(evt.ClipName)) return;
|
||||
if (!_audioClipCache.TryGetValue(evt.ClipName, out AudioClip clip)) return;
|
||||
|
||||
// Use Unity's editor audio playback utility via reflection
|
||||
// (PlayClipAtPoint doesn't work in edit mode)
|
||||
var unityEditorAssembly = typeof(AudioImporter).Assembly;
|
||||
var audioUtilClass = unityEditorAssembly.GetType("UnityEditor.AudioUtil");
|
||||
if (audioUtilClass == null) return;
|
||||
|
||||
// Stop any previous preview
|
||||
var stopMethod = audioUtilClass.GetMethod("StopAllPreviewClips",
|
||||
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
|
||||
stopMethod?.Invoke(null, null);
|
||||
|
||||
// Play the clip
|
||||
var playMethod = audioUtilClass.GetMethod("PlayPreviewClip",
|
||||
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public,
|
||||
null, new System.Type[] { typeof(AudioClip), typeof(int), typeof(bool) }, null);
|
||||
playMethod?.Invoke(null, new object[] { clip, 0, false });
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
2
Editor/PSXCutsceneEditor.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad1b0e43d59aa0446b4e1d6497e8ee94
|
||||
@@ -15,7 +15,7 @@ namespace SplashEdit.EditorCode
|
||||
|
||||
// ───── 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()
|
||||
{
|
||||
SplashControlPanel.ShowWindow();
|
||||
@@ -26,7 +26,7 @@ namespace SplashEdit.EditorCode
|
||||
[MenuItem("GameObject/PlayStation 1/Scene Exporter", false, 10)]
|
||||
public static void CreateSceneExporter(MenuCommand menuCommand)
|
||||
{
|
||||
var existing = Object.FindObjectOfType<PSXSceneExporter>();
|
||||
var existing = Object.FindFirstObjectByType<PSXSceneExporter>();
|
||||
if (existing != null)
|
||||
{
|
||||
EditorUtility.DisplayDialog(
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace SplashEdit.EditorCode
|
||||
private int _selectedRegion = -1;
|
||||
private bool _showAdvanced = false;
|
||||
|
||||
[MenuItem("PSX/Nav Region Builder")]
|
||||
[MenuItem("PlayStation 1/Nav Region Builder")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
GetWindow<PSXNavRegionEditor>("Nav Region Builder");
|
||||
|
||||
@@ -2,147 +2,46 @@ using UnityEngine;
|
||||
using UnityEditor;
|
||||
using SplashEdit.RuntimeCode;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SplashEdit.EditorCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom inspector for PSXObjectExporter with enhanced UX.
|
||||
/// Shows mesh info, texture preview, collision visualization, and validation.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(PSXObjectExporter))]
|
||||
[CanEditMultipleObjects]
|
||||
public class PSXObjectExporterEditor : UnityEditor.Editor
|
||||
{
|
||||
// Serialized properties
|
||||
private SerializedProperty isActiveProp;
|
||||
private SerializedProperty bitDepthProp;
|
||||
private SerializedProperty luaFileProp;
|
||||
private SerializedProperty objectFlagsProp;
|
||||
private SerializedProperty collisionTypeProp;
|
||||
private SerializedProperty exportCollisionMeshProp;
|
||||
private SerializedProperty customCollisionMeshProp;
|
||||
private SerializedProperty collisionLayerProp;
|
||||
private SerializedProperty previewNormalsProp;
|
||||
private SerializedProperty normalPreviewLengthProp;
|
||||
private SerializedProperty showCollisionBoundsProp;
|
||||
private SerializedProperty textureProp;
|
||||
|
||||
// UI State
|
||||
private bool showMeshInfo = true;
|
||||
private bool showTextureInfo = true;
|
||||
private bool showExportSettings = true;
|
||||
private bool showCollisionSettings = true;
|
||||
private bool showGizmoSettings = false;
|
||||
private bool showValidation = true;
|
||||
|
||||
// Cached data
|
||||
private MeshFilter meshFilter;
|
||||
private MeshRenderer meshRenderer;
|
||||
private int triangleCount;
|
||||
private int vertexCount;
|
||||
private Bounds meshBounds;
|
||||
private List<string> validationErrors = new List<string>();
|
||||
private List<string> validationWarnings = new List<string>();
|
||||
|
||||
// Styles
|
||||
private GUIStyle headerStyle;
|
||||
private GUIStyle errorStyle;
|
||||
private GUIStyle warningStyle;
|
||||
|
||||
// Validation
|
||||
private bool _validationDirty = true;
|
||||
private bool showExport = true;
|
||||
private bool showCollision = true;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
// Get serialized properties
|
||||
isActiveProp = serializedObject.FindProperty("isActive");
|
||||
bitDepthProp = serializedObject.FindProperty("bitDepth");
|
||||
luaFileProp = serializedObject.FindProperty("luaFile");
|
||||
objectFlagsProp = serializedObject.FindProperty("objectFlags");
|
||||
collisionTypeProp = serializedObject.FindProperty("collisionType");
|
||||
exportCollisionMeshProp = serializedObject.FindProperty("exportCollisionMesh");
|
||||
customCollisionMeshProp = serializedObject.FindProperty("customCollisionMesh");
|
||||
collisionLayerProp = serializedObject.FindProperty("collisionLayer");
|
||||
previewNormalsProp = serializedObject.FindProperty("previewNormals");
|
||||
normalPreviewLengthProp = serializedObject.FindProperty("normalPreviewLength");
|
||||
showCollisionBoundsProp = serializedObject.FindProperty("showCollisionBounds");
|
||||
textureProp = serializedObject.FindProperty("texture");
|
||||
|
||||
// Cache mesh info
|
||||
CacheMeshInfo();
|
||||
|
||||
// Defer validation to first inspector draw
|
||||
_validationDirty = true;
|
||||
}
|
||||
|
||||
private void CacheMeshInfo()
|
||||
{
|
||||
var exporter = target as PSXObjectExporter;
|
||||
if (exporter == null) return;
|
||||
|
||||
meshFilter = exporter.GetComponent<MeshFilter>();
|
||||
meshRenderer = exporter.GetComponent<MeshRenderer>();
|
||||
|
||||
if (meshFilter != null && meshFilter.sharedMesh != null)
|
||||
{
|
||||
var mesh = meshFilter.sharedMesh;
|
||||
triangleCount = mesh.triangles.Length / 3;
|
||||
vertexCount = mesh.vertexCount;
|
||||
meshBounds = mesh.bounds;
|
||||
}
|
||||
}
|
||||
|
||||
private void RunValidation()
|
||||
{
|
||||
validationErrors.Clear();
|
||||
validationWarnings.Clear();
|
||||
|
||||
var exporter = target as PSXObjectExporter;
|
||||
if (exporter == null) return;
|
||||
|
||||
// Check mesh
|
||||
if (meshFilter == null || meshFilter.sharedMesh == null)
|
||||
{
|
||||
validationErrors.Add("No mesh assigned to MeshFilter");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (triangleCount > 100)
|
||||
{
|
||||
validationWarnings.Add($"High triangle count ({triangleCount}). PS1 recommended: <100 per object");
|
||||
}
|
||||
|
||||
// Check vertex bounds
|
||||
var mesh = meshFilter.sharedMesh;
|
||||
var verts = mesh.vertices;
|
||||
bool hasOutOfBounds = false;
|
||||
|
||||
foreach (var v in verts)
|
||||
{
|
||||
var world = exporter.transform.TransformPoint(v);
|
||||
float scaled = Mathf.Max(Mathf.Abs(world.x), Mathf.Abs(world.y), Mathf.Abs(world.z)) * 4096f;
|
||||
if (scaled > 32767f)
|
||||
{
|
||||
hasOutOfBounds = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasOutOfBounds)
|
||||
{
|
||||
validationErrors.Add("Vertices exceed PS1 coordinate limits (±8 units from origin)");
|
||||
}
|
||||
}
|
||||
|
||||
// Check renderer
|
||||
if (meshRenderer == null)
|
||||
{
|
||||
validationWarnings.Add("No MeshRenderer - object will not be visible");
|
||||
}
|
||||
else if (meshRenderer.sharedMaterial == null)
|
||||
{
|
||||
validationWarnings.Add("No material assigned - will use default colors");
|
||||
triangleCount = meshFilter.sharedMesh.triangles.Length / 3;
|
||||
vertexCount = meshFilter.sharedMesh.vertexCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,290 +49,140 @@ namespace SplashEdit.EditorCode
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
// Run deferred validation
|
||||
if (_validationDirty)
|
||||
{
|
||||
RunValidation();
|
||||
_validationDirty = false;
|
||||
}
|
||||
|
||||
InitStyles();
|
||||
|
||||
// Active toggle at top
|
||||
EditorGUILayout.PropertyField(isActiveProp, new GUIContent("Export This Object"));
|
||||
DrawHeader();
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
if (!isActiveProp.boolValue)
|
||||
{
|
||||
EditorGUILayout.HelpBox("This object will be skipped during export.", MessageType.Info);
|
||||
EditorGUILayout.LabelField("Object will be skipped during export.", PSXEditorStyles.InfoBox);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
DrawMeshSummary();
|
||||
PSXEditorStyles.DrawSeparator(6, 6);
|
||||
DrawExportSection();
|
||||
PSXEditorStyles.DrawSeparator(6, 6);
|
||||
DrawCollisionSection();
|
||||
PSXEditorStyles.DrawSeparator(6, 6);
|
||||
DrawActions();
|
||||
|
||||
// Mesh Info Section
|
||||
DrawMeshInfoSection();
|
||||
|
||||
// Texture Section
|
||||
DrawTextureSection();
|
||||
|
||||
// Export Settings Section
|
||||
DrawExportSettingsSection();
|
||||
|
||||
// Collision Settings Section
|
||||
DrawCollisionSettingsSection();
|
||||
|
||||
// Gizmo Settings Section
|
||||
DrawGizmoSettingsSection();
|
||||
|
||||
// Validation Section
|
||||
DrawValidationSection();
|
||||
|
||||
// Action Buttons
|
||||
DrawActionButtons();
|
||||
|
||||
if (serializedObject.ApplyModifiedProperties())
|
||||
{
|
||||
_validationDirty = true;
|
||||
}
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void InitStyles()
|
||||
private new void DrawHeader()
|
||||
{
|
||||
if (headerStyle == null)
|
||||
{
|
||||
headerStyle = new GUIStyle(EditorStyles.foldoutHeader);
|
||||
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();
|
||||
}
|
||||
|
||||
if (errorStyle == null)
|
||||
private void DrawMeshSummary()
|
||||
{
|
||||
errorStyle = new GUIStyle(EditorStyles.label);
|
||||
errorStyle.normal.textColor = Color.red;
|
||||
if (meshFilter == null || meshFilter.sharedMesh == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("No mesh on this object.", PSXEditorStyles.InfoBox);
|
||||
return;
|
||||
}
|
||||
|
||||
if (warningStyle == null)
|
||||
{
|
||||
warningStyle = new GUIStyle(EditorStyles.label);
|
||||
warningStyle.normal.textColor = new Color(1f, 0.7f, 0f);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField($"{triangleCount} tris", PSXEditorStyles.RichLabel, GUILayout.Width(60));
|
||||
EditorGUILayout.LabelField($"{vertexCount} verts", PSXEditorStyles.RichLabel, GUILayout.Width(70));
|
||||
|
||||
private void DrawMeshInfoSection()
|
||||
{
|
||||
showMeshInfo = EditorGUILayout.BeginFoldoutHeaderGroup(showMeshInfo, "Mesh Information");
|
||||
if (showMeshInfo)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
int subMeshCount = meshFilter.sharedMesh.subMeshCount;
|
||||
if (subMeshCount > 1)
|
||||
EditorGUILayout.LabelField($"{subMeshCount} submeshes", PSXEditorStyles.RichLabel, GUILayout.Width(90));
|
||||
|
||||
if (meshFilter != null && meshFilter.sharedMesh != null)
|
||||
{
|
||||
EditorGUILayout.LabelField("Mesh", meshFilter.sharedMesh.name);
|
||||
EditorGUILayout.LabelField("Triangles", triangleCount.ToString());
|
||||
EditorGUILayout.LabelField("Vertices", vertexCount.ToString());
|
||||
EditorGUILayout.LabelField("Bounds Size", meshBounds.size.ToString("F2"));
|
||||
|
||||
// Triangle budget bar
|
||||
float budgetPercent = triangleCount / 100f;
|
||||
Rect rect = EditorGUILayout.GetControlRect(false, 20);
|
||||
EditorGUI.ProgressBar(rect, Mathf.Clamp01(budgetPercent), $"Triangle Budget: {triangleCount}/100");
|
||||
}
|
||||
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.HelpBox("No mesh assigned", MessageType.Warning);
|
||||
EditorGUILayout.LabelField("untextured", PSXEditorStyles.RichLabel);
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
}
|
||||
private void DrawExportSection()
|
||||
{
|
||||
showExport = EditorGUILayout.Foldout(showExport, "Export", true, PSXEditorStyles.FoldoutHeader);
|
||||
if (!showExport) return;
|
||||
|
||||
private void DrawTextureSection()
|
||||
{
|
||||
showTextureInfo = EditorGUILayout.BeginFoldoutHeaderGroup(showTextureInfo, "Texture Settings");
|
||||
if (showTextureInfo)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.PropertyField(textureProp, new GUIContent("Override Texture"));
|
||||
EditorGUILayout.PropertyField(bitDepthProp, new GUIContent("Bit Depth"));
|
||||
|
||||
// Show texture preview if assigned
|
||||
var tex = textureProp.objectReferenceValue as Texture2D;
|
||||
if (tex != null)
|
||||
{
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
Rect previewRect = GUILayoutUtility.GetRect(64, 64, GUILayout.Width(64));
|
||||
EditorGUI.DrawPreviewTexture(previewRect, tex);
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField($"Size: {tex.width}x{tex.height}");
|
||||
|
||||
// VRAM estimate
|
||||
int bpp = bitDepthProp.enumValueIndex == 0 ? 4 : (bitDepthProp.enumValueIndex == 1 ? 8 : 16);
|
||||
int vramBytes = (tex.width * tex.height * bpp) / 8;
|
||||
EditorGUILayout.LabelField($"Est. VRAM: {vramBytes} bytes ({bpp}bpp)");
|
||||
}
|
||||
else if (meshRenderer != null && meshRenderer.sharedMaterial != null)
|
||||
{
|
||||
var matTex = meshRenderer.sharedMaterial.mainTexture;
|
||||
if (matTex != null)
|
||||
{
|
||||
EditorGUILayout.HelpBox($"Using material texture: {matTex.name}", MessageType.Info);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
}
|
||||
|
||||
private void DrawExportSettingsSection()
|
||||
{
|
||||
showExportSettings = EditorGUILayout.BeginFoldoutHeaderGroup(showExportSettings, "Export Settings");
|
||||
if (showExportSettings)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.PropertyField(objectFlagsProp, new GUIContent("Object Flags"));
|
||||
EditorGUILayout.PropertyField(luaFileProp, new GUIContent("Lua Script"));
|
||||
|
||||
// Quick Lua file buttons
|
||||
if (luaFileProp.objectReferenceValue != null)
|
||||
{
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("Edit Lua", GUILayout.Width(80)))
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.Space(EditorGUI.indentLevel * 15);
|
||||
if (GUILayout.Button("Edit", PSXEditorStyles.SecondaryButton, GUILayout.Width(50)))
|
||||
AssetDatabase.OpenAsset(luaFileProp.objectReferenceValue);
|
||||
}
|
||||
if (GUILayout.Button("Clear", GUILayout.Width(60)))
|
||||
{
|
||||
if (GUILayout.Button("Clear", PSXEditorStyles.SecondaryButton, GUILayout.Width(50)))
|
||||
luaFileProp.objectReferenceValue = null;
|
||||
}
|
||||
}
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("Create New Lua Script"))
|
||||
{
|
||||
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--;
|
||||
}
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
}
|
||||
|
||||
private void DrawCollisionSettingsSection()
|
||||
{
|
||||
showCollisionSettings = EditorGUILayout.BeginFoldoutHeaderGroup(showCollisionSettings, "Collision Settings");
|
||||
if (showCollisionSettings)
|
||||
private void DrawCollisionSection()
|
||||
{
|
||||
showCollision = EditorGUILayout.Foldout(showCollision, "Collision", true, PSXEditorStyles.FoldoutHeader);
|
||||
if (!showCollision) return;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.PropertyField(collisionTypeProp, new GUIContent("Collision Type"));
|
||||
EditorGUILayout.PropertyField(collisionTypeProp, new GUIContent("Type"));
|
||||
|
||||
var collType = (PSXCollisionType)collisionTypeProp.enumValueIndex;
|
||||
if (collType != PSXCollisionType.None)
|
||||
if (collType == PSXCollisionType.Static)
|
||||
{
|
||||
EditorGUILayout.PropertyField(exportCollisionMeshProp, new GUIContent("Export Collision Mesh"));
|
||||
EditorGUILayout.PropertyField(customCollisionMeshProp, new GUIContent("Custom Collision Mesh"));
|
||||
EditorGUILayout.PropertyField(collisionLayerProp, new GUIContent("Collision Layer"));
|
||||
|
||||
// Collision info
|
||||
EditorGUILayout.Space(5);
|
||||
string collisionInfo = collType switch
|
||||
EditorGUILayout.LabelField(
|
||||
"<color=#88cc88>Only bakes holes in the navregions</color>",
|
||||
PSXEditorStyles.RichLabel);
|
||||
}
|
||||
else if (collType == PSXCollisionType.Dynamic)
|
||||
{
|
||||
PSXCollisionType.Solid => "Solid: Blocks movement, fires onCollision",
|
||||
PSXCollisionType.Trigger => "Trigger: Fires onTriggerEnter/Exit, doesn't block",
|
||||
PSXCollisionType.Platform => "Platform: Solid from above only",
|
||||
_ => ""
|
||||
};
|
||||
EditorGUILayout.HelpBox(collisionInfo, MessageType.Info);
|
||||
EditorGUILayout.LabelField(
|
||||
"<color=#88aaff>Runtime AABB collider. Pushes player back + fires Lua events.</color>",
|
||||
PSXEditorStyles.RichLabel);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
}
|
||||
|
||||
private void DrawGizmoSettingsSection()
|
||||
private void DrawActions()
|
||||
{
|
||||
showGizmoSettings = EditorGUILayout.BeginFoldoutHeaderGroup(showGizmoSettings, "Gizmo Settings");
|
||||
if (showGizmoSettings)
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Select Scene Exporter", PSXEditorStyles.SecondaryButton))
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.PropertyField(previewNormalsProp, new GUIContent("Preview Normals"));
|
||||
if (previewNormalsProp.boolValue)
|
||||
{
|
||||
EditorGUILayout.PropertyField(normalPreviewLengthProp, new GUIContent("Normal Length"));
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(showCollisionBoundsProp, new GUIContent("Show Collision Bounds"));
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
}
|
||||
|
||||
private void DrawValidationSection()
|
||||
{
|
||||
if (validationErrors.Count == 0 && validationWarnings.Count == 0)
|
||||
return;
|
||||
|
||||
showValidation = EditorGUILayout.BeginFoldoutHeaderGroup(showValidation, "Validation");
|
||||
if (showValidation)
|
||||
{
|
||||
foreach (var error in validationErrors)
|
||||
{
|
||||
EditorGUILayout.HelpBox(error, MessageType.Error);
|
||||
}
|
||||
|
||||
foreach (var warning in validationWarnings)
|
||||
{
|
||||
EditorGUILayout.HelpBox(warning, MessageType.Warning);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Refresh Validation"))
|
||||
{
|
||||
CacheMeshInfo();
|
||||
RunValidation();
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
}
|
||||
|
||||
private void DrawActionButtons()
|
||||
{
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("Select Scene Exporter"))
|
||||
{
|
||||
var exporter = FindObjectOfType<PSXSceneExporter>();
|
||||
if (exporter != null)
|
||||
{
|
||||
Selection.activeGameObject = exporter.gameObject;
|
||||
}
|
||||
var se = FindFirstObjectByType<PSXSceneExporter>();
|
||||
if (se != null)
|
||||
Selection.activeGameObject = se.gameObject;
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("Not Found", "No PSXSceneExporter in scene.", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Open Scene Validator"))
|
||||
{
|
||||
PSXSceneValidatorWindow.ShowWindow();
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void CreateNewLuaScript()
|
||||
@@ -441,46 +190,13 @@ namespace SplashEdit.EditorCode
|
||||
var exporter = target as PSXObjectExporter;
|
||||
string defaultName = exporter.gameObject.name.ToLower().Replace(" ", "_");
|
||||
string path = EditorUtility.SaveFilePanelInProject(
|
||||
"Create Lua Script",
|
||||
defaultName + ".lua",
|
||||
"lua",
|
||||
"Create Lua Script", defaultName + ".lua", "lua",
|
||||
"Create a new Lua script for this object");
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
string template = $@"-- Lua script for {exporter.gameObject.name}
|
||||
--
|
||||
-- Available globals: Entity, Vec3, Input, Timer, Camera, Audio,
|
||||
-- Debug, Math, Scene, Persist
|
||||
--
|
||||
-- Available events:
|
||||
-- onCreate(self) — called once when the object is registered
|
||||
-- onUpdate(self, dt) — called every frame (dt = delta frames, usually 1)
|
||||
-- onEnable(self) — called when the object becomes active
|
||||
-- onDisable(self) — called when the object becomes inactive
|
||||
-- onCollision(self, other) — called on collision with another object
|
||||
-- onTriggerEnter(self, other)
|
||||
-- onTriggerStay(self, other)
|
||||
-- onTriggerExit(self, other)
|
||||
-- onInteract(self) — called when the player interacts
|
||||
-- onButtonPress(self, btn) — called on button press (btn = Input.CROSS etc.)
|
||||
-- onButtonRelease(self, btn)
|
||||
-- onDestroy(self) — called before the object is destroyed
|
||||
--
|
||||
-- Properties: self.position (Vec3), self.rotationY (pi-units), self.active (bool)
|
||||
if (string.IsNullOrEmpty(path)) return;
|
||||
|
||||
function onCreate(self)
|
||||
-- Called once when this object is registered in the scene
|
||||
end
|
||||
|
||||
function onUpdate(self, dt)
|
||||
-- Called every frame. dt = number of elapsed frames (usually 1).
|
||||
end
|
||||
|
||||
function onInteract(self)
|
||||
-- Called when the player interacts with this object
|
||||
end
|
||||
";
|
||||
string template =
|
||||
$"function onCreate(self)\nend\n\nfunction onUpdate(self, dt)\nend\n";
|
||||
System.IO.File.WriteAllText(path, template);
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
@@ -491,24 +207,41 @@ end
|
||||
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<MeshFilter>();
|
||||
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);
|
||||
}
|
||||
|
||||
[MenuItem("CONTEXT/PSXObjectExporter/Copy Settings to Selected")]
|
||||
private static void CopySettingsToSelected(MenuCommand command)
|
||||
{
|
||||
var source = command.context as PSXObjectExporter;
|
||||
if (source == null) return;
|
||||
|
||||
foreach (var go in Selection.gameObjects)
|
||||
{
|
||||
var target = go.GetComponent<PSXObjectExporter>();
|
||||
if (target != null && target != source)
|
||||
{
|
||||
Undo.RecordObject(target, "Copy PSX Settings");
|
||||
// Copy via serialized object
|
||||
EditorUtility.CopySerialized(source, target);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,143 +1,192 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using SplashEdit.RuntimeCode;
|
||||
using System.Linq;
|
||||
|
||||
namespace SplashEdit.EditorCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom inspector for PSXSceneExporter.
|
||||
/// When the component is selected and fog is enabled, activates a Unity scene-view
|
||||
/// fog preview that approximates the PS1 linear fog distances.
|
||||
///
|
||||
/// Fog distance mapping:
|
||||
/// fogFarSZ = 8000 / FogDensity (GTE SZ units)
|
||||
/// fogNearSZ = fogFarSZ / 3
|
||||
/// SZ is 20.12 fixed-point: SZ = (unityCoord / GTEScaling) * 4096
|
||||
/// => unityDist = SZ * GTEScaling / 4096
|
||||
/// => Unity fog near = (8000 / (FogDensity * 3)) * GTEScaling / 4096
|
||||
/// => Unity fog far = (8000 / FogDensity) * GTEScaling / 4096
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(PSXSceneExporter))]
|
||||
public class PSXSceneExporterEditor : UnityEditor.Editor
|
||||
{
|
||||
// Saved RenderSettings state so we can restore it on deselect.
|
||||
private bool _savedFog;
|
||||
private Color _savedFogColor;
|
||||
private FogMode _savedFogMode;
|
||||
private float _savedFogStart;
|
||||
private float _savedFogEnd;
|
||||
private SerializedProperty gteScalingProp;
|
||||
private SerializedProperty sceneLuaProp;
|
||||
private SerializedProperty fogEnabledProp;
|
||||
private SerializedProperty fogColorProp;
|
||||
private SerializedProperty fogDensityProp;
|
||||
private SerializedProperty sceneTypeProp;
|
||||
private SerializedProperty cutscenesProp;
|
||||
private SerializedProperty loadingScreenProp;
|
||||
private SerializedProperty previewBVHProp;
|
||||
private SerializedProperty previewRoomsPortalsProp;
|
||||
private SerializedProperty bvhDepthProp;
|
||||
|
||||
private bool _previewActive = false;
|
||||
private bool showFog = true;
|
||||
private bool showCutscenes = true;
|
||||
private bool showDebug = false;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
SaveAndApplyFogPreview();
|
||||
// Re-apply whenever the scene is repainted (handles inspector value changes).
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
gteScalingProp = serializedObject.FindProperty("GTEScaling");
|
||||
sceneLuaProp = serializedObject.FindProperty("SceneLuaFile");
|
||||
fogEnabledProp = serializedObject.FindProperty("FogEnabled");
|
||||
fogColorProp = serializedObject.FindProperty("FogColor");
|
||||
fogDensityProp = serializedObject.FindProperty("FogDensity");
|
||||
sceneTypeProp = serializedObject.FindProperty("SceneType");
|
||||
cutscenesProp = serializedObject.FindProperty("Cutscenes");
|
||||
loadingScreenProp = serializedObject.FindProperty("LoadingScreenPrefab");
|
||||
previewBVHProp = serializedObject.FindProperty("PreviewBVH");
|
||||
previewRoomsPortalsProp = serializedObject.FindProperty("PreviewRoomsPortals");
|
||||
bvhDepthProp = serializedObject.FindProperty("BVHPreviewDepth");
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
RestoreFog();
|
||||
}
|
||||
|
||||
private void OnEditorUpdate()
|
||||
{
|
||||
// Keep the preview in sync when the user tweaks values in the inspector.
|
||||
if (_previewActive)
|
||||
ApplyFogPreview();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// Fog disabled on the component - turn off the preview.
|
||||
RenderSettings.fog = false;
|
||||
return;
|
||||
}
|
||||
|
||||
float gteScale = exporter.GTEScaling;
|
||||
int density = Mathf.Clamp(exporter.FogDensity, 1, 10);
|
||||
|
||||
// fogFarSZ in GTE SZ units (20.12 fp); convert to Unity world-space.
|
||||
// SZ = (unityDist / GTEScaling) * 4096, so unityDist = SZ * GTEScaling / 4096
|
||||
float fogFarSZ = 8000f / density;
|
||||
float fogNearSZ = fogFarSZ / 3f;
|
||||
|
||||
float fogFarUnity = fogFarSZ * gteScale / 4096f;
|
||||
float fogNearUnity = fogNearSZ * gteScale / 4096f;
|
||||
|
||||
RenderSettings.fog = true;
|
||||
RenderSettings.fogColor = exporter.FogColor;
|
||||
RenderSettings.fogMode = FogMode.Linear;
|
||||
RenderSettings.fogStartDistance = fogNearUnity;
|
||||
RenderSettings.fogEndDistance = fogFarUnity;
|
||||
}
|
||||
|
||||
private void RestoreFog()
|
||||
{
|
||||
if (!_previewActive) return;
|
||||
_previewActive = false;
|
||||
|
||||
RenderSettings.fog = _savedFog;
|
||||
RenderSettings.fogColor = _savedFogColor;
|
||||
RenderSettings.fogMode = _savedFogMode;
|
||||
RenderSettings.fogStartDistance = _savedFogStart;
|
||||
RenderSettings.fogEndDistance = _savedFogEnd;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
DrawDefaultInspector();
|
||||
|
||||
// Show computed fog distances when fog is enabled, so the user
|
||||
// can see exactly what range the preview represents.
|
||||
var exporter = (PSXSceneExporter)target;
|
||||
if (exporter.FogEnabled)
|
||||
{
|
||||
|
||||
DrawExporterHeader();
|
||||
EditorGUILayout.Space(4);
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
GUILayout.Label("Fog Preview (active in Scene view)", EditorStyles.boldLabel);
|
||||
|
||||
DrawSceneSettings();
|
||||
PSXEditorStyles.DrawSeparator(6, 6);
|
||||
DrawFogSection(exporter);
|
||||
PSXEditorStyles.DrawSeparator(6, 6);
|
||||
DrawCutscenesSection();
|
||||
PSXEditorStyles.DrawSeparator(6, 6);
|
||||
DrawLoadingSection();
|
||||
PSXEditorStyles.DrawSeparator(6, 6);
|
||||
DrawDebugSection();
|
||||
PSXEditorStyles.DrawSeparator(6, 6);
|
||||
DrawSceneStats();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawExporterHeader()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle);
|
||||
EditorGUILayout.LabelField("Scene Exporter", PSXEditorStyles.CardHeaderStyle);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawSceneSettings()
|
||||
{
|
||||
EditorGUILayout.PropertyField(sceneTypeProp, new GUIContent("Scene Type"));
|
||||
|
||||
bool isInterior = (PSXSceneType)sceneTypeProp.enumValueIndex == PSXSceneType.Interior;
|
||||
EditorGUILayout.LabelField(
|
||||
isInterior
|
||||
? "<color=#88aaff>Room/portal occlusion culling.</color>"
|
||||
: "<color=#88cc88>BVH frustum culling.</color>",
|
||||
PSXEditorStyles.RichLabel);
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
EditorGUILayout.PropertyField(gteScalingProp, new GUIContent("GTE Scaling"));
|
||||
EditorGUILayout.PropertyField(sceneLuaProp, new GUIContent("Scene Lua"));
|
||||
|
||||
if (sceneLuaProp.objectReferenceValue != null)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.Space(EditorGUI.indentLevel * 15);
|
||||
if (GUILayout.Button("Edit", EditorStyles.miniButtonLeft, GUILayout.Width(50)))
|
||||
AssetDatabase.OpenAsset(sceneLuaProp.objectReferenceValue);
|
||||
if (GUILayout.Button("Clear", EditorStyles.miniButtonRight, GUILayout.Width(50)))
|
||||
sceneLuaProp.objectReferenceValue = null;
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFogSection(PSXSceneExporter exporter)
|
||||
{
|
||||
showFog = EditorGUILayout.Foldout(showFog, "Fog & Background", true, PSXEditorStyles.FoldoutHeader);
|
||||
if (!showFog) return;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.PropertyField(fogColorProp, new GUIContent("Background Color",
|
||||
"Background clear color. Also used as the fog blend target when fog is enabled."));
|
||||
|
||||
EditorGUILayout.PropertyField(fogEnabledProp, new GUIContent("Distance Fog"));
|
||||
|
||||
if (fogEnabledProp.boolValue)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(fogDensityProp, new GUIContent("Density"));
|
||||
|
||||
float gteScale = exporter.GTEScaling;
|
||||
int density = Mathf.Clamp(exporter.FogDensity, 1, 10);
|
||||
float fogFarUnity = (8000f / density) * gteScale / 4096f;
|
||||
float fogNearUnity = fogFarUnity / 3f;
|
||||
|
||||
EditorGUILayout.LabelField("Near distance", $"{fogNearUnity:F1} Unity units");
|
||||
EditorGUILayout.LabelField("Far distance", $"{fogFarUnity:F1} Unity units");
|
||||
EditorGUILayout.LabelField("(PS1 SZ range)", $"{8000f / (density * 3f):F0} - {8000f / density:F0} GTE units");
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
// Keep preview applied as values may have changed.
|
||||
ApplyFogPreview();
|
||||
EditorGUILayout.Space(2);
|
||||
EditorGUILayout.LabelField(
|
||||
$"<color=#aaaaaa>GTE range: {fogNearUnity:F1} - {fogFarUnity:F1} units | " +
|
||||
$"{8000f / (density * 3f):F0} - {8000f / density:F0} SZ</color>",
|
||||
PSXEditorStyles.RichLabel);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
else
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
private void DrawCutscenesSection()
|
||||
{
|
||||
// Make sure preview is off when fog is disabled.
|
||||
RenderSettings.fog = false;
|
||||
showCutscenes = EditorGUILayout.Foldout(showCutscenes, "Cutscenes", true, PSXEditorStyles.FoldoutHeader);
|
||||
if (!showCutscenes) return;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(cutscenesProp, new GUIContent("Clips"), true);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
private void DrawLoadingSection()
|
||||
{
|
||||
EditorGUILayout.PropertyField(loadingScreenProp, new GUIContent("Loading Screen Prefab"));
|
||||
if (loadingScreenProp.objectReferenceValue != null)
|
||||
{
|
||||
var go = loadingScreenProp.objectReferenceValue as GameObject;
|
||||
if (go != null && go.GetComponentInChildren<PSXCanvas>() == null)
|
||||
{
|
||||
EditorGUILayout.LabelField(
|
||||
"<color=#ffaa44>Prefab has no PSXCanvas component.</color>",
|
||||
PSXEditorStyles.RichLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDebugSection()
|
||||
{
|
||||
showDebug = EditorGUILayout.Foldout(showDebug, "Debug", true, PSXEditorStyles.FoldoutHeader);
|
||||
if (!showDebug) return;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(previewBVHProp, new GUIContent("Preview BVH"));
|
||||
if (previewBVHProp.boolValue)
|
||||
EditorGUILayout.PropertyField(bvhDepthProp, new GUIContent("BVH Depth"));
|
||||
EditorGUILayout.PropertyField(previewRoomsPortalsProp, new GUIContent("Preview Rooms/Portals"));
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
private void DrawSceneStats()
|
||||
{
|
||||
var exporters = FindObjectsByType<PSXObjectExporter>(FindObjectsSortMode.None);
|
||||
int total = exporters.Length;
|
||||
int active = exporters.Count(e => e.IsActive);
|
||||
int staticCol = exporters.Count(e => e.CollisionType == PSXCollisionType.Static);
|
||||
int dynamicCol = exporters.Count(e => e.CollisionType == PSXCollisionType.Dynamic);
|
||||
int triggerBoxes = FindObjectsByType<PSXTriggerBox>(FindObjectsSortMode.None).Length;
|
||||
|
||||
EditorGUILayout.BeginVertical(PSXEditorStyles.CardStyle);
|
||||
EditorGUILayout.LabelField(
|
||||
$"<b>{active}</b>/{total} objects | <b>{staticCol}</b> static <b>{dynamicCol}</b> dynamic <b>{triggerBoxes}</b> triggers",
|
||||
PSXEditorStyles.RichLabel);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,496 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using SplashEdit.RuntimeCode;
|
||||
|
||||
namespace SplashEdit.EditorCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Scene Validator Window - Validates the current scene for PS1 compatibility.
|
||||
/// Checks for common issues that would cause problems on real hardware.
|
||||
/// </summary>
|
||||
public class PSXSceneValidatorWindow : EditorWindow
|
||||
{
|
||||
private Vector2 scrollPosition;
|
||||
private List<ValidationResult> validationResults = new List<ValidationResult>();
|
||||
private bool hasValidated = false;
|
||||
private int errorCount = 0;
|
||||
private int warningCount = 0;
|
||||
private int infoCount = 0;
|
||||
|
||||
// Filter toggles
|
||||
private bool showErrors = true;
|
||||
private bool showWarnings = true;
|
||||
private bool showInfo = true;
|
||||
|
||||
// PS1 Limits
|
||||
private const int MAX_RECOMMENDED_TRIS_PER_OBJECT = 100;
|
||||
private const int MAX_RECOMMENDED_TOTAL_TRIS = 400;
|
||||
private const int MAX_VERTEX_COORD = 32767; // signed 16-bit
|
||||
private const int MIN_VERTEX_COORD = -32768;
|
||||
private const int VRAM_WIDTH = 1024;
|
||||
private const int VRAM_HEIGHT = 512;
|
||||
|
||||
private static readonly Vector2 MinSize = new Vector2(500, 400);
|
||||
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow<PSXSceneValidatorWindow>("Scene Validator");
|
||||
window.minSize = MinSize;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
validationResults.Clear();
|
||||
hasValidated = false;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
DrawHeader();
|
||||
DrawFilters();
|
||||
DrawResults();
|
||||
DrawFooter();
|
||||
}
|
||||
|
||||
private void DrawHeader()
|
||||
{
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
GUILayout.Label("PS1 Scene Validator", EditorStyles.boldLabel);
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (GUILayout.Button("Validate Scene", GUILayout.Width(120)))
|
||||
{
|
||||
ValidateScene();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
// Summary bar
|
||||
if (hasValidated)
|
||||
{
|
||||
using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox))
|
||||
{
|
||||
var errorStyle = new GUIStyle(EditorStyles.label);
|
||||
errorStyle.normal.textColor = errorCount > 0 ? Color.red : Color.green;
|
||||
GUILayout.Label($"✗ {errorCount} Errors", errorStyle);
|
||||
|
||||
var warnStyle = new GUIStyle(EditorStyles.label);
|
||||
warnStyle.normal.textColor = warningCount > 0 ? new Color(1f, 0.7f, 0f) : Color.green;
|
||||
GUILayout.Label($"⚠ {warningCount} Warnings", warnStyle);
|
||||
|
||||
var infoStyle = new GUIStyle(EditorStyles.label);
|
||||
infoStyle.normal.textColor = Color.cyan;
|
||||
GUILayout.Label($"ℹ {infoCount} Info", infoStyle);
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
}
|
||||
|
||||
private void DrawFilters()
|
||||
{
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
GUILayout.Label("Show:", GUILayout.Width(40));
|
||||
showErrors = GUILayout.Toggle(showErrors, "Errors", EditorStyles.miniButtonLeft);
|
||||
showWarnings = GUILayout.Toggle(showWarnings, "Warnings", EditorStyles.miniButtonMid);
|
||||
showInfo = GUILayout.Toggle(showInfo, "Info", EditorStyles.miniButtonRight);
|
||||
GUILayout.FlexibleSpace();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
}
|
||||
|
||||
private void DrawResults()
|
||||
{
|
||||
using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPosition))
|
||||
{
|
||||
scrollPosition = scrollView.scrollPosition;
|
||||
|
||||
if (!hasValidated)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Click 'Validate Scene' to check for PS1 compatibility issues.", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
if (validationResults.Count == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("No issues found! Your scene looks ready for PS1 export.", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var result in validationResults)
|
||||
{
|
||||
if (result.Type == ValidationType.Error && !showErrors) continue;
|
||||
if (result.Type == ValidationType.Warning && !showWarnings) continue;
|
||||
if (result.Type == ValidationType.Info && !showInfo) continue;
|
||||
|
||||
DrawValidationResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawValidationResult(ValidationResult result)
|
||||
{
|
||||
MessageType msgType = result.Type switch
|
||||
{
|
||||
ValidationType.Error => MessageType.Error,
|
||||
ValidationType.Warning => MessageType.Warning,
|
||||
_ => MessageType.Info
|
||||
};
|
||||
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
|
||||
{
|
||||
EditorGUILayout.HelpBox(result.Message, msgType);
|
||||
|
||||
if (result.RelatedObject != null)
|
||||
{
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
GUILayout.Label("Object:", GUILayout.Width(50));
|
||||
|
||||
if (GUILayout.Button(result.RelatedObject.name, EditorStyles.linkLabel))
|
||||
{
|
||||
Selection.activeObject = result.RelatedObject;
|
||||
EditorGUIUtility.PingObject(result.RelatedObject);
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (!string.IsNullOrEmpty(result.FixAction))
|
||||
{
|
||||
if (GUILayout.Button("Fix", GUILayout.Width(50)))
|
||||
{
|
||||
ApplyFix(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(2);
|
||||
}
|
||||
|
||||
private void DrawFooter()
|
||||
{
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
if (GUILayout.Button("Select All With Errors"))
|
||||
{
|
||||
var errorObjects = validationResults
|
||||
.Where(r => r.Type == ValidationType.Error && r.RelatedObject != null)
|
||||
.Select(r => r.RelatedObject)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
Selection.objects = errorObjects;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateScene()
|
||||
{
|
||||
validationResults.Clear();
|
||||
errorCount = 0;
|
||||
warningCount = 0;
|
||||
infoCount = 0;
|
||||
|
||||
// Check for scene exporter
|
||||
ValidateSceneExporter();
|
||||
|
||||
// Check all PSX objects
|
||||
ValidatePSXObjects();
|
||||
|
||||
// Check textures and VRAM
|
||||
ValidateTextures();
|
||||
|
||||
|
||||
// Check Lua files
|
||||
ValidateLuaFiles();
|
||||
|
||||
// Overall scene stats
|
||||
ValidateSceneStats();
|
||||
|
||||
hasValidated = true;
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private void ValidateSceneExporter()
|
||||
{
|
||||
var exporters = Object.FindObjectsOfType<PSXSceneExporter>();
|
||||
|
||||
if (exporters.Length == 0)
|
||||
{
|
||||
AddResult(ValidationType.Error,
|
||||
"No PSXSceneExporter found in scene. Add one via GameObject > PlayStation 1 > Scene Exporter",
|
||||
null, "AddExporter");
|
||||
}
|
||||
else if (exporters.Length > 1)
|
||||
{
|
||||
AddResult(ValidationType.Warning,
|
||||
$"Multiple PSXSceneExporters found ({exporters.Length}). Only one is needed per scene.",
|
||||
exporters[0].gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidatePSXObjects()
|
||||
{
|
||||
var exporters = Object.FindObjectsOfType<PSXObjectExporter>();
|
||||
|
||||
if (exporters.Length == 0)
|
||||
{
|
||||
AddResult(ValidationType.Info,
|
||||
"No objects marked for PSX export. Add PSXObjectExporter components to GameObjects you want to export.",
|
||||
null);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var exporter in exporters)
|
||||
{
|
||||
ValidateSingleObject(exporter);
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateSingleObject(PSXObjectExporter exporter)
|
||||
{
|
||||
var go = exporter.gameObject;
|
||||
|
||||
// Check for mesh
|
||||
var meshFilter = go.GetComponent<MeshFilter>();
|
||||
if (meshFilter == null || meshFilter.sharedMesh == null)
|
||||
{
|
||||
AddResult(ValidationType.Warning,
|
||||
$"'{go.name}' has no mesh. It will be exported as an empty object.",
|
||||
go);
|
||||
return;
|
||||
}
|
||||
|
||||
var mesh = meshFilter.sharedMesh;
|
||||
int triCount = mesh.triangles.Length / 3;
|
||||
|
||||
// Check triangle count
|
||||
if (triCount > MAX_RECOMMENDED_TRIS_PER_OBJECT)
|
||||
{
|
||||
AddResult(ValidationType.Warning,
|
||||
$"'{go.name}' has {triCount} triangles (recommended max: {MAX_RECOMMENDED_TRIS_PER_OBJECT}). Consider simplifying.",
|
||||
go);
|
||||
}
|
||||
|
||||
// Check vertex coordinates for GTE limits
|
||||
var vertices = mesh.vertices;
|
||||
var transform = go.transform;
|
||||
bool hasOutOfBounds = false;
|
||||
|
||||
foreach (var vert in vertices)
|
||||
{
|
||||
var worldPos = transform.TransformPoint(vert);
|
||||
// Check if fixed-point conversion would overflow (assuming scale factor)
|
||||
float scaledX = worldPos.x * 4096f; // FixedPoint<12> scale
|
||||
float scaledY = worldPos.y * 4096f;
|
||||
float scaledZ = worldPos.z * 4096f;
|
||||
|
||||
if (scaledX > MAX_VERTEX_COORD || scaledX < MIN_VERTEX_COORD ||
|
||||
scaledY > MAX_VERTEX_COORD || scaledY < MIN_VERTEX_COORD ||
|
||||
scaledZ > MAX_VERTEX_COORD || scaledZ < MIN_VERTEX_COORD)
|
||||
{
|
||||
hasOutOfBounds = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasOutOfBounds)
|
||||
{
|
||||
AddResult(ValidationType.Error,
|
||||
$"'{go.name}' has vertices that exceed PS1 coordinate limits. Move closer to origin or scale down.",
|
||||
go);
|
||||
}
|
||||
|
||||
// Check for renderer and material
|
||||
var renderer = go.GetComponent<MeshRenderer>();
|
||||
if (renderer == null)
|
||||
{
|
||||
AddResult(ValidationType.Info,
|
||||
$"'{go.name}' has no MeshRenderer. Will be exported without visual rendering.",
|
||||
go);
|
||||
}
|
||||
else if (renderer.sharedMaterial == null)
|
||||
{
|
||||
AddResult(ValidationType.Warning,
|
||||
$"'{go.name}' has no material assigned. Will use default colors.",
|
||||
go);
|
||||
}
|
||||
|
||||
// Check texture settings on exporter
|
||||
if (exporter.texture != null)
|
||||
{
|
||||
ValidateTexture(exporter.texture, go);
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateTextures()
|
||||
{
|
||||
var exporters = Object.FindObjectsOfType<PSXObjectExporter>();
|
||||
var textures = exporters
|
||||
.Where(e => e.texture != null)
|
||||
.Select(e => e.texture)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (textures.Count == 0)
|
||||
{
|
||||
AddResult(ValidationType.Info,
|
||||
"No textures assigned to any PSX objects. Scene will be vertex-colored only.",
|
||||
null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rough VRAM estimation
|
||||
int estimatedVramUsage = 0;
|
||||
foreach (var tex in textures)
|
||||
{
|
||||
// Rough estimate: width * height * bits/8
|
||||
// This is simplified - actual packing is more complex
|
||||
int bitsPerPixel = 16; // Assume 16bpp worst case
|
||||
estimatedVramUsage += (tex.width * tex.height * bitsPerPixel) / 8;
|
||||
}
|
||||
|
||||
int vramTotal = VRAM_WIDTH * VRAM_HEIGHT * 2; // 16bpp
|
||||
int vramAvailable = vramTotal / 2; // Assume half for framebuffers
|
||||
|
||||
if (estimatedVramUsage > vramAvailable)
|
||||
{
|
||||
AddResult(ValidationType.Warning,
|
||||
$"Estimated texture VRAM usage ({estimatedVramUsage / 1024}KB) may exceed available space (~{vramAvailable / 1024}KB). " +
|
||||
"Consider using lower bit depths or smaller textures.",
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateTexture(Texture2D texture, GameObject relatedObject)
|
||||
{
|
||||
// Check power of 2
|
||||
if (!Mathf.IsPowerOfTwo(texture.width) || !Mathf.IsPowerOfTwo(texture.height))
|
||||
{
|
||||
AddResult(ValidationType.Warning,
|
||||
$"Texture '{texture.name}' dimensions ({texture.width}x{texture.height}) are not power of 2. May cause issues.",
|
||||
relatedObject);
|
||||
}
|
||||
|
||||
// Check max size
|
||||
if (texture.width > 256 || texture.height > 256)
|
||||
{
|
||||
AddResult(ValidationType.Warning,
|
||||
$"Texture '{texture.name}' is large ({texture.width}x{texture.height}). Consider using 256x256 or smaller.",
|
||||
relatedObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void ValidateLuaFiles()
|
||||
{
|
||||
var exporters = Object.FindObjectsOfType<PSXObjectExporter>();
|
||||
|
||||
foreach (var exporter in exporters)
|
||||
{
|
||||
if (exporter.LuaFile != null)
|
||||
{
|
||||
// Check if Lua file exists and is valid
|
||||
string path = AssetDatabase.GetAssetPath(exporter.LuaFile);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
AddResult(ValidationType.Error,
|
||||
$"'{exporter.name}' references an invalid Lua file.",
|
||||
exporter.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateSceneStats()
|
||||
{
|
||||
var exporters = Object.FindObjectsOfType<PSXObjectExporter>();
|
||||
int totalTris = 0;
|
||||
|
||||
foreach (var exporter in exporters)
|
||||
{
|
||||
var mf = exporter.GetComponent<MeshFilter>();
|
||||
if (mf != null && mf.sharedMesh != null)
|
||||
{
|
||||
totalTris += mf.sharedMesh.triangles.Length / 3;
|
||||
}
|
||||
}
|
||||
|
||||
AddResult(ValidationType.Info,
|
||||
$"Scene statistics: {exporters.Length} objects, {totalTris} total triangles.",
|
||||
null);
|
||||
|
||||
if (totalTris > MAX_RECOMMENDED_TOTAL_TRIS)
|
||||
{
|
||||
AddResult(ValidationType.Warning,
|
||||
$"Total triangle count ({totalTris}) exceeds recommended maximum ({MAX_RECOMMENDED_TOTAL_TRIS}). " +
|
||||
"Performance may be poor on real hardware.",
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddResult(ValidationType type, string message, GameObject relatedObject, string fixAction = null)
|
||||
{
|
||||
validationResults.Add(new ValidationResult
|
||||
{
|
||||
Type = type,
|
||||
Message = message,
|
||||
RelatedObject = relatedObject,
|
||||
FixAction = fixAction
|
||||
});
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ValidationType.Error: errorCount++; break;
|
||||
case ValidationType.Warning: warningCount++; break;
|
||||
case ValidationType.Info: infoCount++; break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyFix(ValidationResult result)
|
||||
{
|
||||
switch (result.FixAction)
|
||||
{
|
||||
case "AddExporter":
|
||||
var go = new GameObject("PSXSceneExporter");
|
||||
go.AddComponent<PSXSceneExporter>();
|
||||
Undo.RegisterCreatedObjectUndo(go, "Create PSX Scene Exporter");
|
||||
Selection.activeGameObject = go;
|
||||
ValidateScene(); // Re-validate
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private enum ValidationType
|
||||
{
|
||||
Error,
|
||||
Warning,
|
||||
Info
|
||||
}
|
||||
|
||||
private class ValidationResult
|
||||
{
|
||||
public ValidationType Type;
|
||||
public string Message;
|
||||
public GameObject RelatedObject;
|
||||
public string FixAction;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a26bf89301a2554ca287b9e28e44906
|
||||
@@ -1,149 +1,307 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
|
||||
namespace SplashEdit.EditorCode
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Manages downloading and updating the psxsplash native project from GitHub releases.
|
||||
/// Uses the GitHub REST API (HTTP) to list releases and git to clone/checkout
|
||||
/// (required for recursive submodule support).
|
||||
/// </summary>
|
||||
public static class PSXSplashInstaller
|
||||
{
|
||||
// ───── Public config ─────
|
||||
public static readonly string RepoOwner = "psxsplash";
|
||||
public static readonly string RepoName = "psxsplash";
|
||||
public static readonly string RepoUrl = "https://github.com/psxsplash/psxsplash.git";
|
||||
public static readonly string InstallPath = "Assets/psxsplash";
|
||||
public static readonly string FullInstallPath;
|
||||
|
||||
private static readonly string GitHubApiReleasesUrl =
|
||||
$"https://api.github.com/repos/{RepoOwner}/{RepoName}/releases";
|
||||
|
||||
// ───── Cached release list ─────
|
||||
private static List<ReleaseInfo> _cachedReleases = new List<ReleaseInfo>();
|
||||
private static bool _isFetchingReleases;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a GitHub release.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ReleaseInfo
|
||||
{
|
||||
public string TagName; // e.g. "v1.2.0"
|
||||
public string Name; // human-readable name
|
||||
public string Body; // release notes (markdown)
|
||||
public string PublishedAt; // ISO 8601 date
|
||||
public bool IsPrerelease;
|
||||
public bool IsDraft;
|
||||
}
|
||||
|
||||
static PSXSplashInstaller()
|
||||
{
|
||||
FullInstallPath = Path.Combine(Application.dataPath, "psxsplash");
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Queries
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>Is the native project cloned on disk?</summary>
|
||||
public static bool IsInstalled()
|
||||
{
|
||||
return Directory.Exists(FullInstallPath) &&
|
||||
Directory.EnumerateFileSystemEntries(FullInstallPath).Any();
|
||||
}
|
||||
|
||||
public static async Task<bool> Install()
|
||||
/// <summary>Are we currently fetching releases from GitHub?</summary>
|
||||
public static bool IsFetchingReleases => _isFetchingReleases;
|
||||
|
||||
/// <summary>Cached list of releases (call FetchReleasesAsync to populate).</summary>
|
||||
public static IReadOnlyList<ReleaseInfo> CachedReleases => _cachedReleases;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tag currently checked out, or null if unknown / not a git repo.
|
||||
/// </summary>
|
||||
public static string GetCurrentTag()
|
||||
{
|
||||
if (IsInstalled()) return true;
|
||||
if (!IsInstalled()) return null;
|
||||
try
|
||||
{
|
||||
string result = RunGitCommandSync("describe --tags --exact-match HEAD", FullInstallPath);
|
||||
return string.IsNullOrWhiteSpace(result) ? null : result.Trim();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Fetch Releases (HTTP — no git required)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the list of releases from the GitHub REST API.
|
||||
/// Does NOT require git — uses UnityWebRequest.
|
||||
/// </summary>
|
||||
public static async Task<List<ReleaseInfo>> FetchReleasesAsync()
|
||||
{
|
||||
_isFetchingReleases = true;
|
||||
try
|
||||
{
|
||||
string json = await HttpGetAsync(GitHubApiReleasesUrl);
|
||||
if (string.IsNullOrEmpty(json))
|
||||
{
|
||||
UnityEngine.Debug.LogWarning("[PSXSplashInstaller] Failed to fetch releases from GitHub.");
|
||||
return _cachedReleases;
|
||||
}
|
||||
|
||||
var releases = ParseReleasesJson(json);
|
||||
// Filter out drafts, sort by newest first
|
||||
releases = releases
|
||||
.Where(r => !r.IsDraft)
|
||||
.OrderByDescending(r => r.PublishedAt)
|
||||
.ToList();
|
||||
|
||||
_cachedReleases = releases;
|
||||
return releases;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"[PSXSplashInstaller] Error fetching releases: {ex.Message}");
|
||||
return _cachedReleases;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isFetchingReleases = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Install / Clone at a specific release tag
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>
|
||||
/// Clones the repository at the specified release tag with --recursive.
|
||||
/// Uses a shallow clone (--depth 1) for speed.
|
||||
/// Requires git to be installed (submodules cannot be fetched via HTTP archives).
|
||||
/// </summary>
|
||||
/// <param name="tag">The release tag to clone, e.g. "v1.2.0". If null, clones the default branch.</param>
|
||||
/// <param name="onProgress">Optional progress callback.</param>
|
||||
public static async Task<bool> InstallRelease(string tag, Action<string> onProgress = null)
|
||||
{
|
||||
if (IsInstalled())
|
||||
{
|
||||
onProgress?.Invoke("Already installed. Use SwitchToRelease to change version.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IsGitAvailable())
|
||||
{
|
||||
UnityEngine.Debug.LogError(
|
||||
"[PSXSplashInstaller] git is required for recursive submodule clone but was not found on PATH.\n" +
|
||||
"Please install git: https://git-scm.com/downloads");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Create the parent directory if it doesn't exist
|
||||
Directory.CreateDirectory(Application.dataPath);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(FullInstallPath));
|
||||
|
||||
// Clone the repository
|
||||
var result = await RunGitCommandAsync($"clone --recursive {RepoUrl} \"{FullInstallPath}\"", Application.dataPath);
|
||||
return !result.Contains("error");
|
||||
}
|
||||
catch (Exception e)
|
||||
string branchArg = string.IsNullOrEmpty(tag) ? "" : $"--branch {tag}";
|
||||
string cmd = $"clone --recursive --depth 1 {branchArg} {RepoUrl} \"{FullInstallPath}\"";
|
||||
|
||||
onProgress?.Invoke($"Cloning {RepoUrl} at {tag ?? "HEAD"}...");
|
||||
string result = await RunGitCommandAsync(cmd, Application.dataPath, onProgress);
|
||||
|
||||
if (!IsInstalled())
|
||||
{
|
||||
UnityEngine.Debug.LogError($"Failed to install PSXSplash: {e.Message}");
|
||||
UnityEngine.Debug.LogError("[PSXSplashInstaller] Clone completed but directory is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
onProgress?.Invoke("Clone complete.");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"[PSXSplashInstaller] Clone failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Dictionary<string, string>> GetBranchesWithLatestCommitsAsync()
|
||||
/// <summary>
|
||||
/// Switches an existing clone to a different release tag.
|
||||
/// Fetches tags, checks out the tag, and updates submodules recursively.
|
||||
/// </summary>
|
||||
public static async Task<bool> SwitchToReleaseAsync(string tag, Action<string> onProgress = null)
|
||||
{
|
||||
if (!IsInstalled()) return new Dictionary<string, string>();
|
||||
if (!IsInstalled())
|
||||
{
|
||||
UnityEngine.Debug.LogError("[PSXSplashInstaller] Not installed — clone first.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsGitAvailable())
|
||||
{
|
||||
UnityEngine.Debug.LogError("[PSXSplashInstaller] git not found on PATH.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Fetch all branches and tags
|
||||
await RunGitCommandAsync("fetch --all", FullInstallPath);
|
||||
onProgress?.Invoke("Fetching tags...");
|
||||
await RunGitCommandAsync("fetch --tags --depth=1", FullInstallPath, onProgress);
|
||||
await RunGitCommandAsync($"fetch origin tag {tag} --no-tags", FullInstallPath, onProgress);
|
||||
|
||||
// Get all remote branches
|
||||
var branchesOutput = await RunGitCommandAsync("branch -r", FullInstallPath);
|
||||
var branches = branchesOutput.Split('\n')
|
||||
.Where(b => !string.IsNullOrEmpty(b.Trim()))
|
||||
.Select(b => b.Trim().Replace("origin/", ""))
|
||||
.Where(b => !b.Contains("HEAD"))
|
||||
.ToList();
|
||||
onProgress?.Invoke($"Checking out {tag}...");
|
||||
await RunGitCommandAsync($"checkout {tag}", FullInstallPath, onProgress);
|
||||
|
||||
var branchesWithCommits = new Dictionary<string, string>();
|
||||
onProgress?.Invoke("Updating submodules...");
|
||||
await RunGitCommandAsync("submodule update --init --recursive", FullInstallPath, onProgress);
|
||||
|
||||
// Get the latest commit for each branch
|
||||
foreach (var branch in branches)
|
||||
{
|
||||
var commitOutput = await RunGitCommandAsync($"log origin/{branch} -1 --pretty=format:%h", FullInstallPath);
|
||||
if (!string.IsNullOrEmpty(commitOutput))
|
||||
{
|
||||
branchesWithCommits[branch] = commitOutput.Trim();
|
||||
onProgress?.Invoke($"Switched to {tag}.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return branchesWithCommits;
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"Failed to get branches: {e.Message}");
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<List<string>> GetReleasesAsync()
|
||||
{
|
||||
if (!IsInstalled()) return new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
await RunGitCommandAsync("fetch --tags", FullInstallPath);
|
||||
var output = await RunGitCommandAsync("tag -l", FullInstallPath);
|
||||
|
||||
return output.Split('\n')
|
||||
.Where(t => !string.IsNullOrEmpty(t.Trim()))
|
||||
.Select(t => t.Trim())
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"Failed to get releases: {e.Message}");
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> CheckoutVersionAsync(string version)
|
||||
{
|
||||
if (!IsInstalled()) return false;
|
||||
|
||||
try
|
||||
{
|
||||
// If it's a branch name, checkout the branch
|
||||
// If it's a commit hash, checkout the commit
|
||||
var result = await RunGitCommandAsync($"checkout {version}", FullInstallPath);
|
||||
var result2 = await RunGitCommandAsync("submodule update --init --recursive", FullInstallPath);
|
||||
|
||||
return !result.Contains("error") && !result2.Contains("error");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"Failed to checkout version: {e.Message}");
|
||||
UnityEngine.Debug.LogError($"[PSXSplashInstaller] Switch failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Legacy compatibility: Install without specifying a tag (clones default branch).
|
||||
/// </summary>
|
||||
public static Task<bool> Install()
|
||||
{
|
||||
return InstallRelease(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches latest remote data (tags, branches).
|
||||
/// Requires git.
|
||||
/// </summary>
|
||||
public static async Task<bool> FetchLatestAsync()
|
||||
{
|
||||
if (!IsInstalled()) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var result = await RunGitCommandAsync("fetch --all", FullInstallPath);
|
||||
return !result.Contains("error");
|
||||
await RunGitCommandAsync("fetch --all --tags", FullInstallPath);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"Failed to fetch latest: {e.Message}");
|
||||
UnityEngine.Debug.LogError($"[PSXSplashInstaller] Fetch failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> RunGitCommandAsync(string arguments, string workingDirectory)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// Git helpers
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether git is available on the system PATH.
|
||||
/// </summary>
|
||||
public static bool IsGitAvailable()
|
||||
{
|
||||
var processInfo = new ProcessStartInfo
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = "--version",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
using (var p = Process.Start(psi))
|
||||
{
|
||||
p.WaitForExit(5000);
|
||||
return p.ExitCode == 0;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string RunGitCommandSync(string arguments, string workingDirectory)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = workingDirectory,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using (var process = Process.Start(psi))
|
||||
{
|
||||
string output = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit(10000);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> RunGitCommandAsync(
|
||||
string arguments, string workingDirectory, Action<string> onProgress = null)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "git",
|
||||
Arguments = arguments,
|
||||
@@ -156,48 +314,136 @@ namespace SplashEdit.EditorCode
|
||||
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo = processInfo;
|
||||
var outputBuilder = new System.Text.StringBuilder();
|
||||
var errorBuilder = new System.Text.StringBuilder();
|
||||
process.StartInfo = psi;
|
||||
process.EnableRaisingEvents = true;
|
||||
|
||||
process.OutputDataReceived += (sender, e) =>
|
||||
var stdout = new System.Text.StringBuilder();
|
||||
var stderr = new System.Text.StringBuilder();
|
||||
|
||||
process.OutputDataReceived += (s, e) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
outputBuilder.AppendLine(e.Data);
|
||||
{
|
||||
stdout.AppendLine(e.Data);
|
||||
onProgress?.Invoke(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (sender, e) =>
|
||||
process.ErrorDataReceived += (s, e) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
errorBuilder.AppendLine(e.Data);
|
||||
{
|
||||
stderr.AppendLine(e.Data);
|
||||
// git writes progress to stderr (clone progress, etc.)
|
||||
onProgress?.Invoke(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
var tcs = new TaskCompletionSource<int>();
|
||||
process.Exited += (s, e) => tcs.TrySetResult(process.ExitCode);
|
||||
|
||||
process.Start();
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
// Wait for exit with timeout
|
||||
var timeout = TimeSpan.FromSeconds(30);
|
||||
if (await Task.Run(() => process.WaitForExit((int)timeout.TotalMilliseconds)))
|
||||
{
|
||||
process.WaitForExit(); // Ensure all output is processed
|
||||
var timeoutTask = Task.Delay(TimeSpan.FromMinutes(10));
|
||||
var completedTask = await Task.WhenAny(tcs.Task, timeoutTask);
|
||||
|
||||
string output = outputBuilder.ToString();
|
||||
string error = errorBuilder.ToString();
|
||||
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
if (completedTask == timeoutTask)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"Git error: {error}");
|
||||
try { process.Kill(); } catch { }
|
||||
throw new TimeoutException("Git command timed out after 10 minutes.");
|
||||
}
|
||||
|
||||
return output;
|
||||
int exitCode = await tcs.Task;
|
||||
process.Dispose();
|
||||
|
||||
string output = stdout.ToString();
|
||||
string error = stderr.ToString();
|
||||
|
||||
if (exitCode != 0)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"[git {arguments}] exit code {exitCode}\n{error}");
|
||||
}
|
||||
|
||||
return output + error;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// HTTP helpers (no git needed)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
private static Task<string> HttpGetAsync(string url)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<string>();
|
||||
var request = UnityWebRequest.Get(url);
|
||||
request.SetRequestHeader("User-Agent", "SplashEdit-Unity");
|
||||
request.SetRequestHeader("Accept", "application/vnd.github.v3+json");
|
||||
|
||||
var op = request.SendWebRequest();
|
||||
op.completed += _ =>
|
||||
{
|
||||
if (request.result == UnityWebRequest.Result.Success)
|
||||
tcs.TrySetResult(request.downloadHandler.text);
|
||||
else
|
||||
{
|
||||
process.Kill();
|
||||
throw new TimeoutException("Git command timed out");
|
||||
UnityEngine.Debug.LogWarning($"[PSXSplashInstaller] HTTP GET {url} failed: {request.error}");
|
||||
tcs.TrySetResult(null);
|
||||
}
|
||||
request.Dispose();
|
||||
};
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// JSON parsing (minimal, avoids external dependency)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>
|
||||
/// Minimal JSON parser for the GitHub releases API response.
|
||||
/// Uses Unity's JsonUtility via a wrapper since it can't parse top-level arrays.
|
||||
/// </summary>
|
||||
private static List<ReleaseInfo> ParseReleasesJson(string json)
|
||||
{
|
||||
var releases = new List<ReleaseInfo>();
|
||||
|
||||
string wrapped = "{\"items\":" + json + "}";
|
||||
var wrapper = JsonUtility.FromJson<GitHubReleaseArrayWrapper>(wrapped);
|
||||
|
||||
if (wrapper?.items == null) return releases;
|
||||
|
||||
foreach (var item in wrapper.items)
|
||||
{
|
||||
releases.Add(new ReleaseInfo
|
||||
{
|
||||
TagName = item.tag_name ?? "",
|
||||
Name = item.name ?? item.tag_name ?? "",
|
||||
Body = item.body ?? "",
|
||||
PublishedAt = item.published_at ?? "",
|
||||
IsPrerelease = item.prerelease,
|
||||
IsDraft = item.draft
|
||||
});
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class GitHubReleaseArrayWrapper
|
||||
{
|
||||
public GitHubReleaseJson[] items;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class GitHubReleaseJson
|
||||
{
|
||||
public string tag_name;
|
||||
public string name;
|
||||
public string body;
|
||||
public string published_at;
|
||||
public bool prerelease;
|
||||
public bool draft;
|
||||
}
|
||||
}
|
||||
}
|
||||
104
Editor/PSXTriggerBoxEditor.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
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();
|
||||
|
||||
// Header card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.LabelField("PSX Trigger Box", PSXEditorStyles.CardHeaderStyle);
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// Properties card
|
||||
PSXEditorStyles.BeginCard();
|
||||
EditorGUILayout.PropertyField(sizeProp, new GUIContent("Size"));
|
||||
|
||||
PSXEditorStyles.DrawSeparator(4, 4);
|
||||
|
||||
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();
|
||||
}
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
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<LuaFile>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Editor/PSXTriggerBoxEditor.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5bdf647efcaa11a469e2e99025e3a20e
|
||||
@@ -13,7 +13,6 @@ namespace SplashEdit.EditorCode
|
||||
private Texture2D quantizedTexture;
|
||||
private Texture2D vramTexture; // VRAM representation of the texture
|
||||
private List<VRAMPixel> clut; // Color Lookup Table (CLUT), stored as a 1D list
|
||||
private ushort[] indexedPixelData; // Indexed pixel data for VRAM storage
|
||||
private PSXBPP bpp = PSXBPP.TEX_4BIT;
|
||||
private readonly int previewSize = 256;
|
||||
|
||||
@@ -27,19 +26,25 @@ namespace SplashEdit.EditorCode
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
GUILayout.Label("Quantized Preview", EditorStyles.boldLabel);
|
||||
GUILayout.Label("Quantized Preview", PSXEditorStyles.WindowHeader);
|
||||
|
||||
// Texture input field
|
||||
PSXEditorStyles.BeginCard();
|
||||
originalTexture = (Texture2D)EditorGUILayout.ObjectField("Original Texture", originalTexture, typeof(Texture2D), false);
|
||||
|
||||
// Dropdown for bit depth selection
|
||||
bpp = (PSXBPP)EditorGUILayout.EnumPopup("Bit Depth", bpp);
|
||||
|
||||
EditorGUILayout.Space(4);
|
||||
|
||||
// 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();
|
||||
}
|
||||
PSXEditorStyles.EndCard();
|
||||
|
||||
PSXEditorStyles.DrawSeparator(4, 4);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
@@ -47,8 +52,8 @@ namespace SplashEdit.EditorCode
|
||||
if (originalTexture != null)
|
||||
{
|
||||
GUILayout.BeginVertical();
|
||||
GUILayout.Label("Original Texture");
|
||||
DrawTexturePreview(originalTexture, previewSize, false);
|
||||
GUILayout.Label("Original Texture", PSXEditorStyles.CardHeaderStyle);
|
||||
DrawTexturePreview(originalTexture, previewSize);
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
@@ -56,7 +61,7 @@ namespace SplashEdit.EditorCode
|
||||
if (vramTexture != null)
|
||||
{
|
||||
GUILayout.BeginVertical();
|
||||
GUILayout.Label("VRAM View (Indexed Data as 16bpp)");
|
||||
GUILayout.Label("VRAM View (Indexed Data as 16bpp)", PSXEditorStyles.CardHeaderStyle);
|
||||
DrawTexturePreview(vramTexture, previewSize);
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
@@ -65,7 +70,7 @@ namespace SplashEdit.EditorCode
|
||||
if (quantizedTexture != null)
|
||||
{
|
||||
GUILayout.BeginVertical();
|
||||
GUILayout.Label("Quantized Texture");
|
||||
GUILayout.Label("Quantized Texture", PSXEditorStyles.CardHeaderStyle);
|
||||
DrawTexturePreview(quantizedTexture, previewSize);
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
@@ -75,37 +80,17 @@ namespace SplashEdit.EditorCode
|
||||
// Display the Color Lookup Table (CLUT)
|
||||
if (clut != null)
|
||||
{
|
||||
GUILayout.Label("Color Lookup Table (CLUT)");
|
||||
PSXEditorStyles.DrawSeparator(4, 4);
|
||||
GUILayout.Label("Color Lookup Table (CLUT)", PSXEditorStyles.SectionHeader);
|
||||
DrawCLUT();
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
// Export indexed pixel data
|
||||
if (indexedPixelData != null)
|
||||
{
|
||||
if (GUILayout.Button("Export texture data"))
|
||||
{
|
||||
string path = EditorUtility.SaveFilePanel("Save texture data", "", "pixel_data", "bin");
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write))
|
||||
using (BinaryWriter writer = new BinaryWriter(fileStream))
|
||||
{
|
||||
foreach (ushort value in indexedPixelData)
|
||||
{
|
||||
writer.Write(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PSXEditorStyles.DrawSeparator(4, 4);
|
||||
|
||||
// Export CLUT data
|
||||
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");
|
||||
|
||||
@@ -139,7 +124,7 @@ namespace SplashEdit.EditorCode
|
||||
clut = psxTex.ColorPalette;
|
||||
}
|
||||
|
||||
private void DrawTexturePreview(Texture2D texture, int size, bool flipY = true)
|
||||
private void DrawTexturePreview(Texture2D texture, int size)
|
||||
{
|
||||
// Renders a texture preview within the editor window
|
||||
Rect rect = GUILayoutUtility.GetRect(size, size, GUILayout.ExpandWidth(false));
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SplashEdit.RuntimeCode;
|
||||
using Unity.Collections;
|
||||
@@ -19,23 +18,14 @@ namespace SplashEdit.EditorCode
|
||||
private List<ProhibitedArea> prohibitedAreas = new List<ProhibitedArea>();
|
||||
private Vector2 scrollPosition;
|
||||
private Texture2D vramImage;
|
||||
private Vector2 selectedResolution = new Vector2(320, 240);
|
||||
private bool dualBuffering = true;
|
||||
private bool verticalLayout = true;
|
||||
private static readonly Vector2 selectedResolution = new Vector2(320, 240);
|
||||
private const bool dualBuffering = true;
|
||||
private const bool verticalLayout = true;
|
||||
private Color bufferColor1 = new Color(1, 0, 0, 0.5f);
|
||||
private Color bufferColor2 = new Color(0, 1, 0, 0.5f);
|
||||
private Color prohibitedColor = new Color(1, 0, 0, 0.3f);
|
||||
private PSXData _psxData;
|
||||
|
||||
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();
|
||||
private PSXFontData[] _cachedFonts;
|
||||
|
||||
[MenuItem("PlayStation 1/VRAM Editor")]
|
||||
public static void ShowWindow()
|
||||
@@ -57,7 +47,9 @@ namespace SplashEdit.EditorCode
|
||||
// Ensure minimum window size is applied.
|
||||
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>
|
||||
@@ -144,65 +136,75 @@ namespace SplashEdit.EditorCode
|
||||
vramImage.SetPixel(x, VramHeight - y - 1, packed.vramPixels[x, y].GetUnityColor());
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay custom font textures into the VRAM preview.
|
||||
// Fonts live at x=960 (4bpp = 64 VRAM hwords wide), stacking from y=0.
|
||||
PSXFontData[] fonts;
|
||||
PSXUIExporter.CollectCanvases(selectedResolution, out fonts);
|
||||
_cachedFonts = fonts;
|
||||
if (fonts != null && fonts.Length > 0)
|
||||
{
|
||||
foreach (var font in fonts)
|
||||
{
|
||||
if (font.PixelData == null || font.PixelData.Length == 0) continue;
|
||||
|
||||
int vramX = font.VramX;
|
||||
int vramY = font.VramY;
|
||||
int texH = font.TextureHeight;
|
||||
int bytesPerRow = 256 / 2; // 4bpp: 2 pixels per byte, 256 pixels wide = 128 bytes/row
|
||||
|
||||
// Each byte holds two 4bpp pixels. In VRAM, 4 4bpp pixels = 1 16-bit hword.
|
||||
// So 256 4bpp pixels = 64 VRAM hwords.
|
||||
for (int y = 0; y < texH && (vramY + y) < VramHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < 64 && (vramX + x) < VramWidth; x++)
|
||||
{
|
||||
// Read 4 4bpp pixels from this VRAM hword position
|
||||
int byteIdx = y * bytesPerRow + x * 2;
|
||||
if (byteIdx + 1 >= font.PixelData.Length) continue;
|
||||
byte b0 = font.PixelData[byteIdx];
|
||||
byte b1 = font.PixelData[byteIdx + 1];
|
||||
// Each byte: low nibble = first pixel, high nibble = second
|
||||
// 4 pixels per hword: b0 low, b0 high, b1 low, b1 high
|
||||
bool anyOpaque = ((b0 & 0x0F) | (b0 >> 4) | (b1 & 0x0F) | (b1 >> 4)) != 0;
|
||||
|
||||
if (anyOpaque)
|
||||
{
|
||||
int px = vramX + x;
|
||||
int py = VramHeight - 1 - (vramY + y);
|
||||
if (px < VramWidth && py >= 0)
|
||||
vramImage.SetPixel(px, py, new Color(0.8f, 0.8f, 1f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also show system font area (960, 464)-(1023, 511) = 64x48
|
||||
for (int y = 464; y < 512 && y < VramHeight; y++)
|
||||
{
|
||||
for (int x = 960; x < 1024 && x < VramWidth; x++)
|
||||
{
|
||||
int py = VramHeight - 1 - y;
|
||||
Color existing = vramImage.GetPixel(x, py);
|
||||
if (existing.r < 0.01f && existing.g < 0.01f && existing.b < 0.01f)
|
||||
vramImage.SetPixel(x, py, new Color(0.3f, 0.3f, 0.5f));
|
||||
}
|
||||
}
|
||||
|
||||
vramImage.Apply();
|
||||
|
||||
// Prompt the user to select a file location and save the VRAM data.
|
||||
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()
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
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.
|
||||
selectedResolution = resolutions[EditorGUILayout.Popup("Resolution", System.Array.IndexOf(resolutions, selectedResolution), resolutionsStrings)];
|
||||
|
||||
// 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);
|
||||
PSXEditorStyles.DrawSeparator(6, 6);
|
||||
GUILayout.Label("Prohibited Areas", PSXEditorStyles.SectionHeader);
|
||||
GUILayout.Space(4);
|
||||
|
||||
scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true, GUILayout.MinHeight(300f), GUILayout.ExpandWidth(true));
|
||||
|
||||
@@ -213,10 +215,7 @@ namespace SplashEdit.EditorCode
|
||||
{
|
||||
var area = prohibitedAreas[i];
|
||||
|
||||
GUI.backgroundColor = new Color(0.95f, 0.95f, 0.95f);
|
||||
GUILayout.BeginVertical("box");
|
||||
|
||||
GUI.backgroundColor = Color.white;
|
||||
PSXEditorStyles.BeginCard();
|
||||
|
||||
// Display fields for editing the area
|
||||
area.X = EditorGUILayout.IntField("X Coordinate", area.X);
|
||||
@@ -224,17 +223,16 @@ namespace SplashEdit.EditorCode
|
||||
area.Width = EditorGUILayout.IntField("Width", area.Width);
|
||||
area.Height = EditorGUILayout.IntField("Height", area.Height);
|
||||
|
||||
|
||||
if (GUILayout.Button("Remove", GUILayout.Height(30)))
|
||||
EditorGUILayout.Space(2);
|
||||
if (GUILayout.Button("Remove", PSXEditorStyles.DangerButton, GUILayout.Height(24)))
|
||||
{
|
||||
toRemove.Add(i); // Mark for removal
|
||||
}
|
||||
|
||||
|
||||
prohibitedAreas[i] = area;
|
||||
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.Space(10);
|
||||
PSXEditorStyles.EndCard();
|
||||
GUILayout.Space(4);
|
||||
}
|
||||
|
||||
// Remove the areas marked for deletion outside the loop to avoid skipping elements
|
||||
@@ -246,19 +244,23 @@ namespace SplashEdit.EditorCode
|
||||
GUILayout.EndScrollView();
|
||||
GUILayout.Space(10);
|
||||
|
||||
if (GUILayout.Button("Add Prohibited Area"))
|
||||
if (GUILayout.Button("Add Prohibited Area", PSXEditorStyles.SecondaryButton))
|
||||
{
|
||||
prohibitedAreas.Add(new ProhibitedArea());
|
||||
}
|
||||
|
||||
// Button to initiate texture packing.
|
||||
if (GUILayout.Button("Pack Textures"))
|
||||
PSXEditorStyles.DrawSeparator(4, 4);
|
||||
|
||||
// Button to pack and preview VRAM layout.
|
||||
if (GUILayout.Button("Pack Preview", PSXEditorStyles.PrimaryButton, GUILayout.Height(28)))
|
||||
{
|
||||
PackTextures();
|
||||
}
|
||||
|
||||
// Button to save settings; saving now occurs only on button press.
|
||||
if (GUILayout.Button("Save Settings"))
|
||||
EditorGUILayout.Space(2);
|
||||
|
||||
// Button to save prohibited areas.
|
||||
if (GUILayout.Button("Save Settings", PSXEditorStyles.SuccessButton, GUILayout.Height(28)))
|
||||
{
|
||||
_psxData.OutputResolution = selectedResolution;
|
||||
_psxData.DualBuffering = dualBuffering;
|
||||
@@ -297,6 +299,24 @@ namespace SplashEdit.EditorCode
|
||||
EditorGUI.DrawRect(areaRect, prohibitedColor);
|
||||
}
|
||||
|
||||
// Draw font region overlays.
|
||||
if (_cachedFonts != null)
|
||||
{
|
||||
Color fontColor = new Color(0.2f, 0.4f, 0.9f, 0.25f);
|
||||
foreach (var font in _cachedFonts)
|
||||
{
|
||||
if (font.PixelData == null || font.PixelData.Length == 0) continue;
|
||||
Rect fontRect = new Rect(vramRect.x + font.VramX, vramRect.y + font.VramY, 64, font.TextureHeight);
|
||||
EditorGUI.DrawRect(fontRect, fontColor);
|
||||
GUI.Label(new Rect(fontRect.x + 2, fontRect.y + 2, 60, 16), "Font", EditorStyles.miniLabel);
|
||||
}
|
||||
|
||||
// System font overlay
|
||||
Rect sysFontRect = new Rect(vramRect.x + 960, vramRect.y + 464, 64, 48);
|
||||
EditorGUI.DrawRect(sysFontRect, new Color(0.4f, 0.2f, 0.9f, 0.25f));
|
||||
GUI.Label(new Rect(sysFontRect.x + 2, sysFontRect.y + 2, 60, 16), "SysFont", EditorStyles.miniLabel);
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
BIN
Icons/LuaFile.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55ec05596eb659341b8fdb46cd21ab63
|
||||
guid: 607cfdcd926623447afba2249593f87b
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
BIN
Icons/PSXAudioClip.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a5f4bcf472dcfc44b794a898530a6f0
|
||||
guid: c1ac35b4ac561a6479df60ee4440f138
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
BIN
Icons/PSXCanvas.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
143
Icons/PSXCanvas.png.meta
Normal file
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 356cfa78fb65c4141a6163492c5a70c9
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Icons/PSXCutsceneClip.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
143
Icons/PSXCutsceneClip.png.meta
Normal file
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e44d4c108f1b3b4bbb11d764ee322ba
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Icons/PSXData.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
143
Icons/PSXData.png.meta
Normal file
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56495f2f7c3b793479704907f633cc9f
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Icons/PSXFontAsset.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
143
Icons/PSXFontAsset.png.meta
Normal file
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e7ebf02d9a128040a98c0e8a77f318b
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Icons/PSXInteractable.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
143
Icons/PSXInteractable.png.meta
Normal file
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2693d84ea56d55f41841bccc513aef7a
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
Before Width: | Height: | Size: 6.7 KiB |
BIN
Icons/PSXObjectExporter.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
143
Icons/PSXObjectExporter.png.meta
Normal file
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dae4156e1023c34db04e1a0133e8366
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 13 KiB |
BIN
Icons/PSXPortalLink.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
143
Icons/PSXPortalLink.png.meta
Normal file
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2c33bdfa2d4f6841abb6f1bd2c3ce4c
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Icons/PSXRoom.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
143
Icons/PSXRoom.png.meta
Normal file
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2a0da16256de3a419a3848add40def9
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 13 KiB |
BIN
Icons/PSXTriggerBox.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
143
Icons/PSXTriggerBox.png.meta
Normal file
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 661ef445800490d48bb6486c6b48d7bb
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Icons/PSXUIBox.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
143
Icons/PSXUIBox.png.meta
Normal file
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11ce7fce378375c49a29f10d2c8e1695
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Icons/PSXUIImage.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
143
Icons/PSXUIImage.png.meta
Normal file
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fbae166a0556be14c906804e97f8ce15
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Icons/PSXUIProgressBar.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
143
Icons/PSXUIProgressBar.png.meta
Normal file
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ba32ceba8e78ae4dbad95d3fd57c674
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Icons/PSXUIText.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
143
Icons/PSXUIText.png.meta
Normal file
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38a1d4112773e114c969699f94844e6a
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 0
|
||||
wrapV: 0
|
||||
wrapW: 0
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Android
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: iOS
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
|
||||
guid: afedc2b61a424884b90aeb912c54fe50
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7
|
||||
guid: 873ed6988ff333343b8a535dcf4919b1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8
|
||||
guid: c732908a2854def459b9b5d59ea1d8c6
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9
|
||||
guid: ba66a74ed9417f2408b71435e402676b
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
|
||||
// I tried to make this and now I'm scared to delete this.
|
||||
|
||||
/// <summary>
|
||||
/// Implemented by MonoBehaviours that participate in the PSX scene export pipeline.
|
||||
/// Each exportable object converts its Unity representation into PSX-ready data.
|
||||
|
||||
@@ -137,53 +137,55 @@ namespace SplashEdit.RuntimeCode
|
||||
private class Node
|
||||
{
|
||||
public Vector3 Point;
|
||||
public int Index;
|
||||
public Node Left, Right;
|
||||
}
|
||||
|
||||
private Node root;
|
||||
private List<Vector3> points;
|
||||
|
||||
public KDTree(List<Vector3> points)
|
||||
{
|
||||
this.points = points;
|
||||
root = Build(points, 0);
|
||||
var indexed = new List<(Vector3 point, int index)>();
|
||||
for (int i = 0; i < points.Count; i++)
|
||||
indexed.Add((points[i], i));
|
||||
root = Build(indexed, 0);
|
||||
}
|
||||
|
||||
private Node Build(List<Vector3> points, int depth)
|
||||
private Node Build(List<(Vector3 point, int index)> items, int depth)
|
||||
{
|
||||
if (points.Count == 0) return null;
|
||||
if (items.Count == 0) return null;
|
||||
|
||||
int axis = depth % 3;
|
||||
points.Sort((a, b) => a[axis].CompareTo(b[axis]));
|
||||
int median = points.Count / 2;
|
||||
items.Sort((a, b) => a.point[axis].CompareTo(b.point[axis]));
|
||||
int median = items.Count / 2;
|
||||
|
||||
return new Node
|
||||
{
|
||||
Point = points[median],
|
||||
Left = Build(points.Take(median).ToList(), depth + 1),
|
||||
Right = Build(points.Skip(median + 1).ToList(), depth + 1)
|
||||
Point = items[median].point,
|
||||
Index = items[median].index,
|
||||
Left = Build(items.Take(median).ToList(), depth + 1),
|
||||
Right = Build(items.Skip(median + 1).ToList(), depth + 1)
|
||||
};
|
||||
}
|
||||
|
||||
public int FindNearestIndex(Vector3 target)
|
||||
{
|
||||
Vector3 nearest = FindNearest(root, target, 0, root.Point);
|
||||
return points.IndexOf(nearest);
|
||||
return FindNearest(root, target, 0, root).Index;
|
||||
}
|
||||
|
||||
private Vector3 FindNearest(Node node, Vector3 target, int depth, Vector3 best)
|
||||
private Node FindNearest(Node node, Vector3 target, int depth, Node best)
|
||||
{
|
||||
if (node == null) return best;
|
||||
|
||||
if (Vector3.SqrMagnitude(target - node.Point) < Vector3.SqrMagnitude(target - best))
|
||||
best = node.Point;
|
||||
if (Vector3.SqrMagnitude(target - node.Point) < Vector3.SqrMagnitude(target - best.Point))
|
||||
best = node;
|
||||
|
||||
int axis = depth % 3;
|
||||
Node first = target[axis] < node.Point[axis] ? node.Left : node.Right;
|
||||
Node second = first == node.Left ? node.Right : node.Left;
|
||||
|
||||
best = FindNearest(first, target, depth + 1, best);
|
||||
if (Mathf.Pow(target[axis] - node.Point[axis], 2) < Vector3.SqrMagnitude(target - best))
|
||||
if (Mathf.Pow(target[axis] - node.Point[axis], 2) < Vector3.SqrMagnitude(target - best.Point))
|
||||
best = FindNearest(second, target, depth + 1, best);
|
||||
|
||||
return best;
|
||||
|
||||
@@ -2,6 +2,7 @@ using UnityEngine;
|
||||
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
[Icon("Packages/net.psxsplash.splashedit/Icons/LuaFile.png")]
|
||||
public class LuaFile : ScriptableObject
|
||||
{
|
||||
[SerializeField] private string luaScript;
|
||||
|
||||
@@ -4,8 +4,6 @@ namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Pre-converted audio clip data ready for splashpack serialization.
|
||||
/// Populated by the Editor (PSXSceneExporter) so Runtime code never
|
||||
/// touches PSXAudioConverter.
|
||||
/// </summary>
|
||||
public struct AudioClipExport
|
||||
{
|
||||
@@ -18,10 +16,11 @@ namespace SplashEdit.RuntimeCode
|
||||
/// <summary>
|
||||
/// Attach to a GameObject to include an audio clip in the PS1 build.
|
||||
/// 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 for runtime loading.
|
||||
/// </summary>
|
||||
[AddComponentMenu("PSX/Audio Source")]
|
||||
public class PSXAudioSource : MonoBehaviour
|
||||
[AddComponentMenu("PSX/PSX Audio Clip")]
|
||||
[Icon("Packages/net.psxsplash.splashedit/Icons/PSXAudioClip.png")]
|
||||
public class PSXAudioClip : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Name used to identify this clip in Lua (Audio.Play(\"name\"))." )]
|
||||
public string ClipName = "";
|
||||
2
Runtime/PSXAudioClip.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0da2803235be654438e86fe9d9a954d4
|
||||
27
Runtime/PSXAudioEvent.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
/// <summary>
|
||||
/// A frame-based audio trigger within a cutscene.
|
||||
/// When the cutscene reaches this frame, the named audio clip is played.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PSXAudioEvent
|
||||
{
|
||||
[Tooltip("Frame at which to trigger this audio clip.")]
|
||||
public int Frame;
|
||||
|
||||
[Tooltip("Name of the audio clip (must match a PSXAudioClip ClipName in the scene).")]
|
||||
public string ClipName = "";
|
||||
|
||||
[Tooltip("Playback volume (0 = silent, 128 = max).")]
|
||||
[Range(0, 128)]
|
||||
public int Volume = 100;
|
||||
|
||||
[Tooltip("Stereo pan (0 = hard left, 64 = center, 127 = hard right).")]
|
||||
[Range(0, 127)]
|
||||
public int Pan = 64;
|
||||
}
|
||||
}
|
||||
2
Runtime/PSXAudioEvent.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 264e92578fac5014aa24c1e38e116b3b
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c4c3feb30e8c264baddc3a5e774473b
|
||||
122
Runtime/PSXCanvas.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks a Unity Canvas as a PSX UI canvas for splashpack export.
|
||||
/// Attach to a GameObject that also has a Unity Canvas component.
|
||||
/// Children with PSXUIImage / PSXUIBox / PSXUIText / PSXUIProgressBar
|
||||
/// components will be exported as UI elements in this canvas.
|
||||
/// Auto-configures the Canvas to the PSX resolution from PSXData settings.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Canvas))]
|
||||
[DisallowMultipleComponent]
|
||||
[ExecuteAlways]
|
||||
[AddComponentMenu("PSX/UI/PSX Canvas")]
|
||||
[Icon("Packages/net.psxsplash.splashedit/Icons/PSXCanvas.png")]
|
||||
public class PSXCanvas : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Name used to reference this canvas from Lua (max 24 chars). Must be unique per scene.")]
|
||||
[SerializeField] private string canvasName = "canvas";
|
||||
|
||||
[Tooltip("Whether this canvas is visible when the scene first loads.")]
|
||||
[SerializeField] private bool startVisible = true;
|
||||
|
||||
[Tooltip("Render order (0 = back, higher = front). Canvases render back-to-front.")]
|
||||
[Range(0, 255)]
|
||||
[SerializeField] private int sortOrder = 0;
|
||||
|
||||
[Tooltip("Optional custom font for text elements in this canvas. If null, uses the built-in system font (8x16).")]
|
||||
[SerializeField] private PSXFontAsset defaultFont;
|
||||
|
||||
/// <summary>Canvas name for Lua access. Truncated to 24 chars on export.</summary>
|
||||
public string CanvasName => canvasName;
|
||||
|
||||
/// <summary>Initial visibility flag written into the splashpack.</summary>
|
||||
public bool StartVisible => startVisible;
|
||||
|
||||
/// <summary>Sort order in 0-255 range.</summary>
|
||||
public byte SortOrder => (byte)Mathf.Clamp(sortOrder, 0, 255);
|
||||
|
||||
/// <summary>Default font for text elements. Null = system font.</summary>
|
||||
public PSXFontAsset DefaultFont => defaultFont;
|
||||
|
||||
/// <summary>
|
||||
/// PSX target resolution read from the PSXData asset. Falls back to 320x240.
|
||||
/// Cached per domain reload for efficiency.
|
||||
/// </summary>
|
||||
public static Vector2 PSXResolution
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!s_resolutionCached)
|
||||
{
|
||||
s_cachedResolution = LoadResolutionFromProject();
|
||||
s_resolutionCached = true;
|
||||
}
|
||||
return s_cachedResolution;
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector2 s_cachedResolution = new Vector2(320, 240);
|
||||
private static bool s_resolutionCached = false;
|
||||
|
||||
/// <summary>Invalidate the cached resolution (call when PSXData changes).</summary>
|
||||
public static void InvalidateResolutionCache()
|
||||
{
|
||||
s_resolutionCached = false;
|
||||
}
|
||||
|
||||
private static Vector2 LoadResolutionFromProject()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
var data = AssetDatabase.LoadAssetAtPath<PSXData>("Assets/PSXData.asset");
|
||||
if (data != null)
|
||||
return data.OutputResolution;
|
||||
#endif
|
||||
return new Vector2(320, 240);
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
InvalidateResolutionCache();
|
||||
ConfigureCanvas();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
ConfigureCanvas();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
// Delay to avoid modifying in OnValidate directly
|
||||
UnityEditor.EditorApplication.delayCall += ConfigureCanvas;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Force the Canvas + CanvasScaler to match the PSX resolution from project settings.
|
||||
/// </summary>
|
||||
public void ConfigureCanvas()
|
||||
{
|
||||
if (this == null) return;
|
||||
|
||||
Vector2 res = PSXResolution;
|
||||
|
||||
Canvas canvas = GetComponent<Canvas>();
|
||||
if (canvas != null)
|
||||
{
|
||||
canvas.renderMode = RenderMode.WorldSpace;
|
||||
}
|
||||
|
||||
RectTransform rt = GetComponent<RectTransform>();
|
||||
rt.sizeDelta = new Vector2(res.x, res.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Runtime/PSXCanvas.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc481397cd94e03409e462478df09d58
|
||||
88
Runtime/PSXCanvasData.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Pre-computed data for one UI canvas and its elements,
|
||||
/// ready for binary serialization by <see cref="PSXSceneWriter"/>.
|
||||
/// Populated by <see cref="PSXUIExporter"/> during the export pipeline.
|
||||
/// </summary>
|
||||
public struct PSXCanvasData
|
||||
{
|
||||
/// <summary>Canvas name (max 24 chars, truncated on export).</summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>Initial visibility flag.</summary>
|
||||
public bool StartVisible;
|
||||
|
||||
/// <summary>Sort order (0 = back, 255 = front).</summary>
|
||||
public byte SortOrder;
|
||||
|
||||
/// <summary>Exported elements belonging to this canvas.</summary>
|
||||
public PSXUIElementData[] Elements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-computed data for one UI element, ready for binary serialization.
|
||||
/// Matches the 48-byte on-disk element record parsed by uisystem.cpp.
|
||||
/// </summary>
|
||||
public struct PSXUIElementData
|
||||
{
|
||||
// Identity
|
||||
public PSXUIElementType Type;
|
||||
public bool StartVisible;
|
||||
public string Name; // max 24 chars
|
||||
|
||||
// Layout (PS1 pixel coords, already Y-inverted)
|
||||
public short X, Y, W, H;
|
||||
|
||||
// Anchors (8.8 fixed-point: 0=0.0, 128=0.5, 255≈1.0)
|
||||
public byte AnchorMinX, AnchorMinY;
|
||||
public byte AnchorMaxX, AnchorMaxY;
|
||||
|
||||
// Primary color (RGB)
|
||||
public byte ColorR, ColorG, ColorB;
|
||||
|
||||
// Type-specific: Image
|
||||
public byte TexpageX, TexpageY;
|
||||
public ushort ClutX, ClutY;
|
||||
public byte U0, V0, U1, V1;
|
||||
public byte BitDepthIndex; // 0=4bit, 1=8bit, 2=16bit
|
||||
|
||||
// Type-specific: Progress
|
||||
public byte BgR, BgG, BgB;
|
||||
public byte ProgressValue;
|
||||
|
||||
// Type-specific: Text
|
||||
public string DefaultText; // max 63 chars
|
||||
public byte FontIndex; // 0 = system font, 1+ = custom font
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Export data for a custom font to be embedded in the splashpack.
|
||||
/// </summary>
|
||||
public struct PSXFontData
|
||||
{
|
||||
/// <summary>Source font asset (for identification/dedup).</summary>
|
||||
public PSXFontAsset Source;
|
||||
|
||||
/// <summary>Glyph cell width in pixels.</summary>
|
||||
public byte GlyphWidth;
|
||||
|
||||
/// <summary>Glyph cell height in pixels.</summary>
|
||||
public byte GlyphHeight;
|
||||
|
||||
/// <summary>VRAM X position for upload (16-bit pixel units).</summary>
|
||||
public ushort VramX;
|
||||
|
||||
/// <summary>VRAM Y position for upload (16-bit pixel units).</summary>
|
||||
public ushort VramY;
|
||||
|
||||
/// <summary>Texture height in pixels (width is always 256 in 4bpp = 64 VRAM hwords).</summary>
|
||||
public ushort TextureHeight;
|
||||
|
||||
/// <summary>Packed 4bpp pixel data ready for VRAM upload.</summary>
|
||||
public byte[] PixelData;
|
||||
|
||||
/// <summary>Per-character advance widths (96 entries, ASCII 0x20-0x7F) for proportional rendering.</summary>
|
||||
public byte[] AdvanceWidths;
|
||||
}
|
||||
}
|
||||
2
Runtime/PSXCanvasData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4142a9858eccd04eac0b826af8f3621
|
||||
@@ -15,7 +15,6 @@ namespace SplashEdit.RuntimeCode
|
||||
Solid = 0x01,
|
||||
Slope = 0x02,
|
||||
Stairs = 0x04,
|
||||
Trigger = 0x08,
|
||||
NoWalk = 0x10,
|
||||
}
|
||||
|
||||
@@ -84,14 +83,17 @@ namespace SplashEdit.RuntimeCode
|
||||
|
||||
foreach (var exporter in exporters)
|
||||
{
|
||||
// Dynamic objects use runtime AABB colliders, skip them
|
||||
if (exporter.CollisionType == PSXCollisionType.Dynamic)
|
||||
continue;
|
||||
|
||||
PSXCollisionType effectiveType = exporter.CollisionType;
|
||||
|
||||
if (effectiveType == PSXCollisionType.None)
|
||||
{
|
||||
if (autoIncludeSolid)
|
||||
{
|
||||
// Auto-include as Solid so all geometry blocks the player
|
||||
effectiveType = PSXCollisionType.Solid;
|
||||
effectiveType = PSXCollisionType.Static;
|
||||
autoIncluded++;
|
||||
}
|
||||
else
|
||||
@@ -100,11 +102,8 @@ namespace SplashEdit.RuntimeCode
|
||||
}
|
||||
}
|
||||
|
||||
// Get the collision mesh (custom or render mesh)
|
||||
MeshFilter mf = exporter.GetComponent<MeshFilter>();
|
||||
Mesh collisionMesh = exporter.CustomCollisionMesh != null
|
||||
? exporter.CustomCollisionMesh
|
||||
: mf?.sharedMesh;
|
||||
Mesh collisionMesh = mf?.sharedMesh;
|
||||
|
||||
if (collisionMesh == null)
|
||||
continue;
|
||||
@@ -130,39 +129,26 @@ namespace SplashEdit.RuntimeCode
|
||||
// Determine surface flags
|
||||
byte flags = 0;
|
||||
|
||||
if (effectiveType == PSXCollisionType.Trigger)
|
||||
{
|
||||
flags = (byte)PSXSurfaceFlag.Trigger;
|
||||
}
|
||||
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 (exporter.ObjectFlags.HasFlag(PSXObjectFlags.Static) &&
|
||||
dotUp < 0.95f && dotUp > cosWalkable)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
_allTriangles.Add(new CollisionTriExport
|
||||
{
|
||||
|
||||
28
Runtime/PSXCutsceneClip.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
/// <summary>
|
||||
/// A cutscene asset containing keyframed tracks and audio events.
|
||||
/// Create via right-click → Create → PSX → Cutscene Clip.
|
||||
/// Reference these assets anywhere in the project; the exporter collects
|
||||
/// all PSXCutsceneClip assets via Resources.FindObjectsOfTypeAll.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "NewCutscene", menuName = "PSX/Cutscene Clip", order = 100)]
|
||||
[Icon("Packages/net.psxsplash.splashedit/Icons/PSXCutsceneClip.png")]
|
||||
public class PSXCutsceneClip : ScriptableObject
|
||||
{
|
||||
[Tooltip("Name used to reference this cutscene from Lua (max 24 chars). Must be unique per scene.")]
|
||||
public string CutsceneName = "cutscene";
|
||||
|
||||
[Tooltip("Total duration in frames at 30fps. E.g. 90 = 3 seconds.")]
|
||||
public int DurationFrames = 90;
|
||||
|
||||
[Tooltip("Tracks driving properties over time.")]
|
||||
public List<PSXCutsceneTrack> Tracks = new List<PSXCutsceneTrack>();
|
||||
|
||||
[Tooltip("Audio events triggered at specific frames.")]
|
||||
public List<PSXAudioEvent> AudioEvents = new List<PSXAudioEvent>();
|
||||
}
|
||||
}
|
||||
2
Runtime/PSXCutsceneClip.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99c0b28de0bbbf7449afc28106b605dc
|
||||
389
Runtime/PSXCutsceneExporter.cs
Normal file
@@ -0,0 +1,389 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializes PSXCutsceneClip data into the splashpack v12 binary format.
|
||||
/// Called from PSXSceneWriter.Write() after all other data sections.
|
||||
/// </summary>
|
||||
public static class PSXCutsceneExporter
|
||||
{
|
||||
// Match C++ limits
|
||||
private const int MAX_CUTSCENES = 16;
|
||||
private const int MAX_TRACKS = 8;
|
||||
private const int MAX_KEYFRAMES = 64;
|
||||
private const int MAX_AUDIO_EVENTS = 64;
|
||||
private const int MAX_NAME_LEN = 24;
|
||||
|
||||
/// <summary>
|
||||
/// Angle conversion: degrees to psyqo::Angle raw value.
|
||||
/// psyqo::Angle = FixedPoint<10>, stored in pi-units.
|
||||
/// 1.0_pi = 1024 raw = 180 degrees. So: raw = degrees * 1024 / 180.
|
||||
/// </summary>
|
||||
private static short DegreesToAngleRaw(float degrees)
|
||||
{
|
||||
float raw = degrees * 1024.0f / 180.0f;
|
||||
return (short)Mathf.Clamp(Mathf.RoundToInt(raw), -32768, 32767);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write all cutscene data and return the byte position of the cutscene table
|
||||
/// so the header can be backfilled.
|
||||
/// </summary>
|
||||
/// <param name="writer">Binary writer positioned after all prior sections.</param>
|
||||
/// <param name="cutscenes">Cutscene clips to export (may be null/empty).</param>
|
||||
/// <param name="exporters">Scene object exporters for name validation.</param>
|
||||
/// <param name="audioSources">Audio sources for clip name → index resolution.</param>
|
||||
/// <param name="gteScaling">GTE scaling factor.</param>
|
||||
/// <param name="cutsceneTableStart">Returns the file position where the cutscene table starts.</param>
|
||||
/// <param name="log">Optional log callback.</param>
|
||||
public static void ExportCutscenes(
|
||||
BinaryWriter writer,
|
||||
PSXCutsceneClip[] cutscenes,
|
||||
PSXObjectExporter[] exporters,
|
||||
PSXAudioClip[] audioSources,
|
||||
float gteScaling,
|
||||
out long cutsceneTableStart,
|
||||
Action<string, LogType> log = null)
|
||||
{
|
||||
cutsceneTableStart = 0;
|
||||
|
||||
if (cutscenes == null || cutscenes.Length == 0)
|
||||
return;
|
||||
|
||||
if (cutscenes.Length > MAX_CUTSCENES)
|
||||
{
|
||||
log?.Invoke($"Too many cutscenes ({cutscenes.Length} > {MAX_CUTSCENES}). Only the first {MAX_CUTSCENES} will be exported.", LogType.Warning);
|
||||
var trimmed = new PSXCutsceneClip[MAX_CUTSCENES];
|
||||
Array.Copy(cutscenes, trimmed, MAX_CUTSCENES);
|
||||
cutscenes = trimmed;
|
||||
}
|
||||
|
||||
// Build audio source name → index lookup
|
||||
Dictionary<string, int> audioNameToIndex = new Dictionary<string, int>();
|
||||
if (audioSources != null)
|
||||
{
|
||||
for (int i = 0; i < audioSources.Length; i++)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(audioSources[i].ClipName) && !audioNameToIndex.ContainsKey(audioSources[i].ClipName))
|
||||
audioNameToIndex[audioSources[i].ClipName] = i;
|
||||
}
|
||||
}
|
||||
|
||||
AlignToFourBytes(writer);
|
||||
|
||||
// ── Cutscene Table ──
|
||||
cutsceneTableStart = writer.BaseStream.Position;
|
||||
|
||||
// SPLASHPACKCutsceneEntry: 12 bytes each
|
||||
// Write placeholders first, then backfill
|
||||
long[] entryPositions = new long[cutscenes.Length];
|
||||
for (int i = 0; i < cutscenes.Length; i++)
|
||||
{
|
||||
entryPositions[i] = writer.BaseStream.Position;
|
||||
writer.Write((uint)0); // dataOffset placeholder
|
||||
writer.Write((byte)0); // nameLen placeholder
|
||||
writer.Write((byte)0); // pad
|
||||
writer.Write((byte)0); // pad
|
||||
writer.Write((byte)0); // pad
|
||||
writer.Write((uint)0); // nameOffset placeholder
|
||||
}
|
||||
|
||||
// ── Per-cutscene data ──
|
||||
for (int ci = 0; ci < cutscenes.Length; ci++)
|
||||
{
|
||||
PSXCutsceneClip clip = cutscenes[ci];
|
||||
AlignToFourBytes(writer);
|
||||
|
||||
// Record data offset
|
||||
long dataPos = writer.BaseStream.Position;
|
||||
|
||||
// Validate and clamp
|
||||
int trackCount = Mathf.Min(clip.Tracks?.Count ?? 0, MAX_TRACKS);
|
||||
int audioEventCount = 0;
|
||||
|
||||
// Count valid audio events
|
||||
List<PSXAudioEvent> validEvents = new List<PSXAudioEvent>();
|
||||
if (clip.AudioEvents != null)
|
||||
{
|
||||
foreach (var evt in clip.AudioEvents)
|
||||
{
|
||||
if (audioNameToIndex.ContainsKey(evt.ClipName))
|
||||
{
|
||||
validEvents.Add(evt);
|
||||
if (validEvents.Count >= MAX_AUDIO_EVENTS) break;
|
||||
}
|
||||
else
|
||||
{
|
||||
log?.Invoke($"Cutscene '{clip.CutsceneName}': audio event clip '{evt.ClipName}' not found in scene audio sources. Skipping.", LogType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
audioEventCount = validEvents.Count;
|
||||
|
||||
// Sort audio events by frame (required for linear scan on PS1)
|
||||
validEvents.Sort((a, b) => a.Frame.CompareTo(b.Frame));
|
||||
|
||||
// SPLASHPACKCutscene: 12 bytes
|
||||
long tracksOffsetPlaceholder;
|
||||
long audioEventsOffsetPlaceholder;
|
||||
|
||||
writer.Write((ushort)clip.DurationFrames);
|
||||
writer.Write((byte)trackCount);
|
||||
writer.Write((byte)audioEventCount);
|
||||
tracksOffsetPlaceholder = writer.BaseStream.Position;
|
||||
writer.Write((uint)0); // tracksOffset placeholder
|
||||
audioEventsOffsetPlaceholder = writer.BaseStream.Position;
|
||||
writer.Write((uint)0); // audioEventsOffset placeholder
|
||||
|
||||
// ── Tracks ──
|
||||
AlignToFourBytes(writer);
|
||||
long tracksStart = writer.BaseStream.Position;
|
||||
|
||||
// SPLASHPACKCutsceneTrack: 12 bytes each
|
||||
long[] trackObjectNameOffsets = new long[trackCount];
|
||||
long[] trackKeyframesOffsets = new long[trackCount];
|
||||
|
||||
for (int ti = 0; ti < trackCount; ti++)
|
||||
{
|
||||
PSXCutsceneTrack track = clip.Tracks[ti];
|
||||
string objName = GetTrackTargetName(track);
|
||||
|
||||
int kfCount = Mathf.Min(track.Keyframes?.Count ?? 0, MAX_KEYFRAMES);
|
||||
|
||||
writer.Write((byte)track.TrackType);
|
||||
writer.Write((byte)kfCount);
|
||||
writer.Write((byte)objName.Length);
|
||||
writer.Write((byte)0); // pad
|
||||
trackObjectNameOffsets[ti] = writer.BaseStream.Position;
|
||||
writer.Write((uint)0); // objectNameOffset placeholder
|
||||
trackKeyframesOffsets[ti] = writer.BaseStream.Position;
|
||||
writer.Write((uint)0); // keyframesOffset placeholder
|
||||
}
|
||||
|
||||
// ── Keyframe data (per track) ──
|
||||
for (int ti = 0; ti < trackCount; ti++)
|
||||
{
|
||||
PSXCutsceneTrack track = clip.Tracks[ti];
|
||||
int kfCount = Mathf.Min(track.Keyframes?.Count ?? 0, MAX_KEYFRAMES);
|
||||
|
||||
AlignToFourBytes(writer);
|
||||
long kfStart = writer.BaseStream.Position;
|
||||
|
||||
// Sort keyframes by frame
|
||||
var sortedKf = new List<PSXKeyframe>(track.Keyframes ?? new List<PSXKeyframe>());
|
||||
sortedKf.Sort((a, b) => a.Frame.CompareTo(b.Frame));
|
||||
|
||||
for (int ki = 0; ki < kfCount; ki++)
|
||||
{
|
||||
PSXKeyframe kf = sortedKf[ki];
|
||||
// Pack interp mode into upper 3 bits, frame into lower 13 bits
|
||||
ushort frameAndInterp = (ushort)((((int)kf.Interp & 0x7) << 13) | (kf.Frame & 0x1FFF));
|
||||
writer.Write(frameAndInterp);
|
||||
|
||||
switch (track.TrackType)
|
||||
{
|
||||
case PSXTrackType.CameraPosition:
|
||||
case PSXTrackType.ObjectPosition:
|
||||
{
|
||||
// Position: convert to fp12, negate Y for PSX coords
|
||||
float gte = gteScaling;
|
||||
short px = PSXTrig.ConvertCoordinateToPSX(kf.Value.x, gte);
|
||||
short py = PSXTrig.ConvertCoordinateToPSX(-kf.Value.y, gte);
|
||||
short pz = PSXTrig.ConvertCoordinateToPSX(kf.Value.z, gte);
|
||||
writer.Write(px);
|
||||
writer.Write(py);
|
||||
writer.Write(pz);
|
||||
break;
|
||||
}
|
||||
case PSXTrackType.CameraRotation:
|
||||
{
|
||||
// Rotation: degrees → psyqo::Angle raw (pi-units)
|
||||
short rx = DegreesToAngleRaw(kf.Value.x);
|
||||
short ry = DegreesToAngleRaw(kf.Value.y);
|
||||
short rz = DegreesToAngleRaw(kf.Value.z);
|
||||
writer.Write(rx);
|
||||
writer.Write(ry);
|
||||
writer.Write(rz);
|
||||
break;
|
||||
}
|
||||
case PSXTrackType.ObjectRotation:
|
||||
{
|
||||
// Full XYZ rotation in degrees -> pi-units
|
||||
short rx = DegreesToAngleRaw(kf.Value.x);
|
||||
short ry = DegreesToAngleRaw(kf.Value.y);
|
||||
short rz = DegreesToAngleRaw(kf.Value.z);
|
||||
writer.Write(rx);
|
||||
writer.Write(ry);
|
||||
writer.Write(rz);
|
||||
break;
|
||||
}
|
||||
case PSXTrackType.ObjectActive:
|
||||
{
|
||||
writer.Write((short)(kf.Value.x > 0.5f ? 1 : 0));
|
||||
writer.Write((short)0);
|
||||
writer.Write((short)0);
|
||||
break;
|
||||
}
|
||||
case PSXTrackType.UICanvasVisible:
|
||||
case PSXTrackType.UIElementVisible:
|
||||
{
|
||||
// Step: values[0] = 0 or 1
|
||||
writer.Write((short)(kf.Value.x > 0.5f ? 1 : 0));
|
||||
writer.Write((short)0);
|
||||
writer.Write((short)0);
|
||||
break;
|
||||
}
|
||||
case PSXTrackType.UIProgress:
|
||||
{
|
||||
// values[0] = progress 0-100 as int16
|
||||
writer.Write((short)Mathf.Clamp(Mathf.RoundToInt(kf.Value.x), 0, 100));
|
||||
writer.Write((short)0);
|
||||
writer.Write((short)0);
|
||||
break;
|
||||
}
|
||||
case PSXTrackType.UIPosition:
|
||||
{
|
||||
// values[0] = x, values[1] = y (PSX screen coordinates, raw int16)
|
||||
writer.Write((short)Mathf.RoundToInt(kf.Value.x));
|
||||
writer.Write((short)Mathf.RoundToInt(kf.Value.y));
|
||||
writer.Write((short)0);
|
||||
break;
|
||||
}
|
||||
case PSXTrackType.UIColor:
|
||||
{
|
||||
// values[0] = r, values[1] = g, values[2] = b (0-255)
|
||||
writer.Write((short)Mathf.Clamp(Mathf.RoundToInt(kf.Value.x), 0, 255));
|
||||
writer.Write((short)Mathf.Clamp(Mathf.RoundToInt(kf.Value.y), 0, 255));
|
||||
writer.Write((short)Mathf.Clamp(Mathf.RoundToInt(kf.Value.z), 0, 255));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backfill keyframes offset
|
||||
{
|
||||
long curPos = writer.BaseStream.Position;
|
||||
writer.Seek((int)trackKeyframesOffsets[ti], SeekOrigin.Begin);
|
||||
writer.Write((uint)kfStart);
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Object / UI target name strings (per track) ──
|
||||
for (int ti = 0; ti < trackCount; ti++)
|
||||
{
|
||||
PSXCutsceneTrack track = clip.Tracks[ti];
|
||||
string objName = GetTrackTargetName(track);
|
||||
|
||||
if (objName.Length > 0)
|
||||
{
|
||||
long namePos = writer.BaseStream.Position;
|
||||
byte[] nameBytes = Encoding.UTF8.GetBytes(objName);
|
||||
writer.Write(nameBytes);
|
||||
writer.Write((byte)0); // null terminator
|
||||
|
||||
long curPos = writer.BaseStream.Position;
|
||||
writer.Seek((int)trackObjectNameOffsets[ti], SeekOrigin.Begin);
|
||||
writer.Write((uint)namePos);
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
// else: objectNameOffset stays 0
|
||||
}
|
||||
|
||||
// ── Audio events ──
|
||||
AlignToFourBytes(writer);
|
||||
long audioEventsStart = writer.BaseStream.Position;
|
||||
|
||||
foreach (var evt in validEvents)
|
||||
{
|
||||
int clipIdx = audioNameToIndex[evt.ClipName];
|
||||
writer.Write((ushort)evt.Frame);
|
||||
writer.Write((byte)clipIdx);
|
||||
writer.Write((byte)Mathf.Clamp(evt.Volume, 0, 128));
|
||||
writer.Write((byte)Mathf.Clamp(evt.Pan, 0, 127));
|
||||
writer.Write((byte)0); // pad
|
||||
writer.Write((byte)0); // pad
|
||||
writer.Write((byte)0); // pad
|
||||
}
|
||||
|
||||
// ── Cutscene name string ──
|
||||
string csName = clip.CutsceneName ?? "unnamed";
|
||||
if (csName.Length > MAX_NAME_LEN) csName = csName.Substring(0, MAX_NAME_LEN);
|
||||
long nameStartPos = writer.BaseStream.Position;
|
||||
byte[] csNameBytes = Encoding.UTF8.GetBytes(csName);
|
||||
writer.Write(csNameBytes);
|
||||
writer.Write((byte)0); // null terminator
|
||||
|
||||
// ── Backfill SPLASHPACKCutscene offsets ──
|
||||
{
|
||||
long curPos = writer.BaseStream.Position;
|
||||
|
||||
// tracksOffset
|
||||
writer.Seek((int)tracksOffsetPlaceholder, SeekOrigin.Begin);
|
||||
writer.Write((uint)tracksStart);
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
{
|
||||
long curPos = writer.BaseStream.Position;
|
||||
|
||||
// audioEventsOffset
|
||||
writer.Seek((int)audioEventsOffsetPlaceholder, SeekOrigin.Begin);
|
||||
writer.Write((uint)(audioEventCount > 0 ? audioEventsStart : 0));
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
// ── Backfill cutscene table entry ──
|
||||
{
|
||||
long curPos = writer.BaseStream.Position;
|
||||
writer.Seek((int)entryPositions[ci], SeekOrigin.Begin);
|
||||
writer.Write((uint)dataPos); // dataOffset
|
||||
writer.Write((byte)csNameBytes.Length); // nameLen
|
||||
writer.Write((byte)0); // pad
|
||||
writer.Write((byte)0); // pad
|
||||
writer.Write((byte)0); // pad
|
||||
writer.Write((uint)nameStartPos); // nameOffset
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
log?.Invoke($"{cutscenes.Length} cutscene(s) exported.", LogType.Log);
|
||||
}
|
||||
|
||||
private static void AlignToFourBytes(BinaryWriter writer)
|
||||
{
|
||||
long pos = writer.BaseStream.Position;
|
||||
int padding = (int)(4 - (pos % 4)) % 4;
|
||||
if (padding > 0)
|
||||
writer.Write(new byte[padding]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the target name string for a track.
|
||||
/// Camera tracks: empty. Object tracks: ObjectName.
|
||||
/// UICanvasVisible: UICanvasName.
|
||||
/// UI element tracks: "UICanvasName/UIElementName".
|
||||
/// </summary>
|
||||
private static string GetTrackTargetName(PSXCutsceneTrack track)
|
||||
{
|
||||
bool isCameraTrack = track.TrackType == PSXTrackType.CameraPosition || track.TrackType == PSXTrackType.CameraRotation;
|
||||
if (isCameraTrack) return "";
|
||||
|
||||
string name;
|
||||
if (track.IsUIElementTrack)
|
||||
name = (track.UICanvasName ?? "") + "/" + (track.UIElementName ?? "");
|
||||
else if (track.IsUITrack)
|
||||
name = track.UICanvasName ?? "";
|
||||
else
|
||||
name = track.ObjectName ?? "";
|
||||
|
||||
if (name.Length > MAX_NAME_LEN)
|
||||
name = name.Substring(0, MAX_NAME_LEN);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Runtime/PSXCutsceneExporter.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d915e0da5e09ea348a020d077f0725da
|
||||
34
Runtime/PSXCutsceneTrack.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
/// <summary>
|
||||
/// A single track within a cutscene, driving one property on one target.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PSXCutsceneTrack
|
||||
{
|
||||
[Tooltip("What property this track drives.")]
|
||||
public PSXTrackType TrackType;
|
||||
|
||||
[Tooltip("Target GameObject name (must match a PSXObjectExporter). Leave empty for camera/UI tracks.")]
|
||||
public string ObjectName = "";
|
||||
|
||||
[Tooltip("For UI tracks: canvas name (e.g. 'hud'). Used by UICanvasVisible and to resolve elements.")]
|
||||
public string UICanvasName = "";
|
||||
|
||||
[Tooltip("For UI element tracks: element name within the canvas. Used by UIElementVisible, UIProgress, UIPosition, UIColor.")]
|
||||
public string UIElementName = "";
|
||||
|
||||
[Tooltip("Keyframes for this track. Sort by frame number.")]
|
||||
public List<PSXKeyframe> Keyframes = new List<PSXKeyframe>();
|
||||
|
||||
/// <summary>Returns true if this track type targets a UI canvas or element.</summary>
|
||||
public bool IsUITrack => TrackType >= PSXTrackType.UICanvasVisible && TrackType <= PSXTrackType.UIColor;
|
||||
|
||||
/// <summary>Returns true if this track type targets a UI element (not just a canvas).</summary>
|
||||
public bool IsUIElementTrack => TrackType >= PSXTrackType.UIElementVisible && TrackType <= PSXTrackType.UIColor;
|
||||
}
|
||||
}
|
||||
2
Runtime/PSXCutsceneTrack.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c289e12325ed44499d49bc7a570c8de
|
||||
@@ -5,6 +5,7 @@ namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
|
||||
[CreateAssetMenu(fileName = "PSXData", menuName = "PSXSplash/PS1 Project Data")]
|
||||
[Icon("Packages/net.psxsplash.splashedit/Icons/PSXData.png")]
|
||||
public class PSXData : ScriptableObject
|
||||
{
|
||||
|
||||
|
||||
458
Runtime/PSXFontAsset.cs
Normal file
@@ -0,0 +1,458 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
[CreateAssetMenu(fileName = "New PSXFont", menuName = "PSX/Font Asset")]
|
||||
[Icon("Packages/net.psxsplash.splashedit/Icons/PSXFontAsset.png")]
|
||||
public class PSXFontAsset : ScriptableObject
|
||||
{
|
||||
[Header("Source - Option A: TrueType/OTF Font")]
|
||||
[Tooltip("Assign a Unity Font asset (TTF/OTF). Click 'Generate Bitmap' to rasterize.")]
|
||||
[SerializeField] private Font sourceFont;
|
||||
|
||||
[Tooltip("Font size in pixels. Larger = more detail but uses more VRAM.\n" +
|
||||
"The actual glyph cell size is auto-computed to fit within PS1 texture page limits.")]
|
||||
[Range(6, 32)]
|
||||
[SerializeField] private int fontSize = 16;
|
||||
|
||||
[Header("Source - Option B: Manual Bitmap")]
|
||||
[Tooltip("Font bitmap texture. Must be 256 pixels wide.\n" +
|
||||
"Glyphs in ASCII order from 0x20, transparent = bg, opaque = fg.")]
|
||||
[SerializeField] private Texture2D fontTexture;
|
||||
|
||||
[Header("Glyph Metrics")]
|
||||
[Tooltip("Width of each glyph cell (auto-set from font, editable for manual bitmap).\n" +
|
||||
"Must divide 256 evenly: 4, 8, 16, or 32.")]
|
||||
[SerializeField] private int glyphWidth = 8;
|
||||
|
||||
[Tooltip("Height of each glyph cell (auto-set from font, editable for manual bitmap).")]
|
||||
[SerializeField] private int glyphHeight = 16;
|
||||
|
||||
[HideInInspector]
|
||||
[SerializeField] private byte[] storedAdvanceWidths;
|
||||
|
||||
// Valid glyph widths: must divide 256 evenly for PSYQo texture UV wrapping.
|
||||
private static readonly int[] ValidGlyphWidths = { 4, 8, 16, 32 };
|
||||
|
||||
// PS1 texture page is 256 pixels tall. Font texture MUST fit in one page.
|
||||
private const int MAX_TEXTURE_PAGE_HEIGHT = 256;
|
||||
|
||||
public Font SourceFont => sourceFont;
|
||||
public int FontSize => fontSize;
|
||||
public Texture2D FontTexture => fontTexture;
|
||||
public int GlyphWidth => glyphWidth;
|
||||
public int GlyphHeight => glyphHeight;
|
||||
|
||||
/// <summary>Per-character advance widths (96 entries, ASCII 0x20-0x7F). Computed during generation.</summary>
|
||||
public byte[] AdvanceWidths => storedAdvanceWidths;
|
||||
|
||||
public int GlyphsPerRow => 256 / glyphWidth;
|
||||
public int RowCount => Mathf.CeilToInt(95f / GlyphsPerRow);
|
||||
public int TextureHeight => RowCount * glyphHeight;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void GenerateBitmapFromFont()
|
||||
{
|
||||
if (sourceFont == null)
|
||||
{
|
||||
Debug.LogWarning("PSXFontAsset: No source font assigned.");
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Step 1: Populate the font atlas ──
|
||||
string ascii = "";
|
||||
for (int c = 0x20; c <= 0x7E; c++) ascii += (char)c;
|
||||
sourceFont.RequestCharactersInTexture(ascii, fontSize, FontStyle.Normal);
|
||||
|
||||
// ── Step 2: Get readable copy of atlas texture ──
|
||||
// For non-dynamic fonts, the atlas may only be populated at the native size.
|
||||
// Try the requested size first, then fall back to size=0.
|
||||
Texture fontTex = sourceFont.material != null ? sourceFont.material.mainTexture : null;
|
||||
if (fontTex == null || fontTex.width == 0 || fontTex.height == 0)
|
||||
{
|
||||
// Retry with size=0 (native size) for non-dynamic fonts
|
||||
sourceFont.RequestCharactersInTexture(ascii, 0, FontStyle.Normal);
|
||||
fontTex = sourceFont.material != null ? sourceFont.material.mainTexture : null;
|
||||
}
|
||||
if (fontTex == null)
|
||||
{
|
||||
Debug.LogError("PSXFontAsset: Font atlas is null. Set Character to 'ASCII Default Set' in font import settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
int fontTexW = fontTex.width;
|
||||
int fontTexH = fontTex.height;
|
||||
if (fontTexW == 0 || fontTexH == 0)
|
||||
{
|
||||
Debug.LogError("PSXFontAsset: Font atlas has zero dimensions. Try re-importing the font with 'ASCII Default Set'.");
|
||||
return;
|
||||
}
|
||||
|
||||
Color[] fontPixels;
|
||||
{
|
||||
RenderTexture rt = RenderTexture.GetTemporary(fontTexW, fontTexH, 0, RenderTextureFormat.ARGB32);
|
||||
Graphics.Blit(fontTex, rt);
|
||||
RenderTexture prev = RenderTexture.active;
|
||||
RenderTexture.active = rt;
|
||||
Texture2D readable = new Texture2D(fontTexW, fontTexH, TextureFormat.RGBA32, false);
|
||||
readable.ReadPixels(new Rect(0, 0, fontTexW, fontTexH), 0, 0);
|
||||
readable.Apply();
|
||||
RenderTexture.active = prev;
|
||||
RenderTexture.ReleaseTemporary(rt);
|
||||
fontPixels = readable.GetPixels();
|
||||
DestroyImmediate(readable);
|
||||
}
|
||||
|
||||
// Verify atlas isn't blank
|
||||
bool hasAnyPixel = false;
|
||||
for (int i = 0; i < fontPixels.Length && !hasAnyPixel; i++)
|
||||
{
|
||||
if (fontPixels[i].a > 0.1f) hasAnyPixel = true;
|
||||
}
|
||||
if (!hasAnyPixel)
|
||||
{
|
||||
Debug.LogError("PSXFontAsset: Font atlas is blank. Set Character to 'ASCII Default Set' in font import settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Step 3: Get character info ──
|
||||
// Non-dynamic fonts only respond to size=0 or their native size.
|
||||
// Dynamic fonts respond to any size.
|
||||
CharacterInfo[] charInfos = new CharacterInfo[95];
|
||||
bool[] charValid = new bool[95];
|
||||
int validCount = 0;
|
||||
int workingSize = fontSize;
|
||||
|
||||
// Try requested fontSize first
|
||||
for (int c = 0x20; c <= 0x7E; c++)
|
||||
{
|
||||
int idx = c - 0x20;
|
||||
if (sourceFont.GetCharacterInfo((char)c, out charInfos[idx], fontSize, FontStyle.Normal))
|
||||
{
|
||||
charValid[idx] = true;
|
||||
validCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// If that failed, try size=0 (non-dynamic fonts need this)
|
||||
if (validCount == 0)
|
||||
{
|
||||
sourceFont.RequestCharactersInTexture(ascii, 0, FontStyle.Normal);
|
||||
for (int c = 0x20; c <= 0x7E; c++)
|
||||
{
|
||||
int idx = c - 0x20;
|
||||
if (sourceFont.GetCharacterInfo((char)c, out charInfos[idx], 0, FontStyle.Normal))
|
||||
{
|
||||
charValid[idx] = true;
|
||||
validCount++;
|
||||
}
|
||||
}
|
||||
if (validCount > 0)
|
||||
{
|
||||
workingSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: read characterInfo array directly
|
||||
if (validCount == 0 && sourceFont.characterInfo != null)
|
||||
{
|
||||
foreach (CharacterInfo fci in sourceFont.characterInfo)
|
||||
{
|
||||
int c = fci.index;
|
||||
if (c >= 0x20 && c <= 0x7E)
|
||||
{
|
||||
charInfos[c - 0x20] = fci;
|
||||
charValid[c - 0x20] = true;
|
||||
validCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (validCount == 0)
|
||||
{
|
||||
Debug.LogError("PSXFontAsset: Could not get character info from font.");
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Step 4: Choose glyph cell dimensions ──
|
||||
// Constraints:
|
||||
// - glyphWidth must divide 256 (valid: 4, 8, 16, 32)
|
||||
// - ceil(95 / (256/glyphWidth)) * glyphHeight <= 256 (must fit in one texture page)
|
||||
// - glyphHeight in [4, 32]
|
||||
// Strategy: pick the smallest valid width where everything fits.
|
||||
// Glyphs that exceed the cell are scaled to fit.
|
||||
|
||||
int measuredMaxW = 0, measuredMaxH = 0;
|
||||
for (int idx = 1; idx < 95; idx++) // skip space
|
||||
{
|
||||
if (!charValid[idx]) continue;
|
||||
CharacterInfo ci = charInfos[idx];
|
||||
int pw = Mathf.Abs(ci.maxX - ci.minX);
|
||||
int ph = Mathf.Abs(ci.maxY - ci.minY);
|
||||
if (pw > measuredMaxW) measuredMaxW = pw;
|
||||
if (ph > measuredMaxH) measuredMaxH = ph;
|
||||
}
|
||||
|
||||
// Target height based on measured glyphs + margin
|
||||
int targetH = Mathf.Clamp(measuredMaxH + 2, 4, 32);
|
||||
|
||||
// Find the best valid width: start from the IDEAL (closest to measured width)
|
||||
// and go smaller only if the texture wouldn't fit in 256px vertically.
|
||||
// This maximizes glyph quality by using the widest cells that fit.
|
||||
int bestW = -1, bestH = -1;
|
||||
|
||||
// Find ideal: smallest valid width >= measured glyph width
|
||||
int idealIdx = ValidGlyphWidths.Length - 1; // default to largest (32)
|
||||
for (int i = 0; i < ValidGlyphWidths.Length; i++)
|
||||
{
|
||||
if (ValidGlyphWidths[i] >= measuredMaxW)
|
||||
{
|
||||
idealIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Try from ideal downward until we find one that fits
|
||||
for (int i = idealIdx; i >= 0; i--)
|
||||
{
|
||||
int vw = ValidGlyphWidths[i];
|
||||
int perRow = 256 / vw;
|
||||
int rows = Mathf.CeilToInt(95f / perRow);
|
||||
int totalH = rows * targetH;
|
||||
if (totalH <= MAX_TEXTURE_PAGE_HEIGHT)
|
||||
{
|
||||
bestW = vw;
|
||||
bestH = targetH;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing fits even at width=4, clamp height
|
||||
if (bestW < 0)
|
||||
{
|
||||
bestW = 4;
|
||||
int rows4 = Mathf.CeilToInt(95f / 64); // 64 per row at width 4
|
||||
bestH = Mathf.Clamp(MAX_TEXTURE_PAGE_HEIGHT / rows4, 4, 32);
|
||||
Debug.LogWarning($"PSXFontAsset: Font too large for PS1 texture page. " +
|
||||
$"Clamping to {bestW}x{bestH} cells.");
|
||||
}
|
||||
|
||||
glyphWidth = bestW;
|
||||
glyphHeight = bestH;
|
||||
|
||||
int texW = 256;
|
||||
int glyphsPerRow = texW / glyphWidth;
|
||||
int rowCount = Mathf.CeilToInt(95f / glyphsPerRow);
|
||||
int texH = rowCount * glyphHeight;
|
||||
|
||||
// Compute baseline metrics for proper vertical positioning.
|
||||
// Characters sit on a common baseline. Ascenders go up, descenders go down.
|
||||
int maxAscender = 0; // highest point above baseline (positive)
|
||||
int maxDescender = 0; // lowest point below baseline (negative)
|
||||
for (int idx = 1; idx < 95; idx++)
|
||||
{
|
||||
if (!charValid[idx]) continue;
|
||||
CharacterInfo ci = charInfos[idx];
|
||||
if (ci.maxY > maxAscender) maxAscender = ci.maxY;
|
||||
if (ci.minY < maxDescender) maxDescender = ci.minY;
|
||||
}
|
||||
int totalFontH = maxAscender - maxDescender;
|
||||
|
||||
// Vertical scale only if font exceeds cell height
|
||||
float vScale = 1f;
|
||||
int usableH = glyphHeight - 2;
|
||||
if (totalFontH > usableH)
|
||||
vScale = (float)usableH / totalFontH;
|
||||
|
||||
// NO horizontal scaling. Glyphs rendered at native width, left-aligned.
|
||||
// This makes the native advance widths match the bitmap exactly for
|
||||
// proportional rendering. Characters wider than cell get clipped (rare).
|
||||
|
||||
// ── Step 5: Render glyphs into grid ──
|
||||
// Each glyph is LEFT-ALIGNED at native width for proportional rendering.
|
||||
// The advance widths from CharacterInfo match native glyph proportions.
|
||||
Texture2D bmp = new Texture2D(texW, texH, TextureFormat.RGBA32, false);
|
||||
bmp.filterMode = FilterMode.Point;
|
||||
bmp.wrapMode = TextureWrapMode.Clamp;
|
||||
|
||||
Color[] clearPixels = new Color[texW * texH];
|
||||
bmp.SetPixels(clearPixels);
|
||||
|
||||
int renderedCount = 0;
|
||||
|
||||
for (int idx = 0; idx < 95; idx++)
|
||||
{
|
||||
if (!charValid[idx]) continue;
|
||||
CharacterInfo ci = charInfos[idx];
|
||||
|
||||
int col = idx % glyphsPerRow;
|
||||
int row = idx / glyphsPerRow;
|
||||
int cellX = col * glyphWidth;
|
||||
int cellY = row * glyphHeight;
|
||||
|
||||
int gw = Mathf.Abs(ci.maxX - ci.minX);
|
||||
int gh = Mathf.Abs(ci.maxY - ci.minY);
|
||||
if (gw <= 0 || gh <= 0) continue;
|
||||
|
||||
// Use all four UV corners to handle atlas rotation.
|
||||
// Unity's atlas packer can rotate glyphs 90 degrees to pack efficiently.
|
||||
// Wide characters like 'm' and 'M' are commonly rotated.
|
||||
// Bilinear interpolation across the UV quad handles any orientation.
|
||||
Vector2 uvBL = ci.uvBottomLeft;
|
||||
Vector2 uvBR = ci.uvBottomRight;
|
||||
Vector2 uvTL = ci.uvTopLeft;
|
||||
Vector2 uvTR = ci.uvTopRight;
|
||||
|
||||
// Native width (clipped to cell), scaled height
|
||||
int renderW = Mathf.Min(gw, glyphWidth);
|
||||
int renderH = Mathf.Max(1, Mathf.RoundToInt(gh * vScale));
|
||||
|
||||
// Y offset: baseline positioning
|
||||
int baselineFromTop = 1 + Mathf.RoundToInt(maxAscender * vScale);
|
||||
int glyphTopFromBaseline = Mathf.RoundToInt(ci.maxY * vScale);
|
||||
int offsetY = baselineFromTop - glyphTopFromBaseline;
|
||||
if (offsetY < 0) offsetY = 0;
|
||||
|
||||
// Include left bearing so glyph sits at correct position within
|
||||
// the advance space. Negative bearing (left overhang) clamped to 0.
|
||||
int offsetX = Mathf.Max(0, ci.minX);
|
||||
|
||||
bool anyPixel = false;
|
||||
|
||||
for (int py = 0; py < renderH && (offsetY + py) < glyphHeight; py++)
|
||||
{
|
||||
for (int px = 0; px < renderW && (offsetX + px) < glyphWidth; px++)
|
||||
{
|
||||
// Scale to fit if glyph wider than cell, 1:1 otherwise
|
||||
float srcU = (px + 0.5f) / renderW;
|
||||
float srcV = (py + 0.5f) / renderH;
|
||||
|
||||
// Bilinear interpolation across the UV quad (handles rotation)
|
||||
// Bottom edge: lerp BL->BR by srcU
|
||||
// Top edge: lerp TL->TR by srcU
|
||||
// Then lerp bottom->top by (1-srcV) for top-down rendering
|
||||
float t = 1f - srcV; // 0=bottom, 1=top -> invert for top-down
|
||||
float u = Mathf.Lerp(
|
||||
Mathf.Lerp(uvBL.x, uvBR.x, srcU),
|
||||
Mathf.Lerp(uvTL.x, uvTR.x, srcU), t);
|
||||
float v = Mathf.Lerp(
|
||||
Mathf.Lerp(uvBL.y, uvBR.y, srcU),
|
||||
Mathf.Lerp(uvTL.y, uvTR.y, srcU), t);
|
||||
|
||||
int sx = Mathf.Clamp(Mathf.FloorToInt(u * fontTexW), 0, fontTexW - 1);
|
||||
int sy = Mathf.Clamp(Mathf.FloorToInt(v * fontTexH), 0, fontTexH - 1);
|
||||
Color sc = fontPixels[sy * fontTexW + sx];
|
||||
|
||||
if (sc.a <= 0.3f) continue;
|
||||
|
||||
int outX = cellX + offsetX + px;
|
||||
int outY = texH - 1 - (cellY + offsetY + py);
|
||||
if (outX >= 0 && outX < texW && outY >= 0 && outY < texH)
|
||||
{
|
||||
bmp.SetPixel(outX, outY, Color.white);
|
||||
anyPixel = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (anyPixel) renderedCount++;
|
||||
}
|
||||
|
||||
bmp.Apply();
|
||||
|
||||
if (renderedCount == 0)
|
||||
{
|
||||
Debug.LogError("PSXFontAsset: Generated bitmap is empty.");
|
||||
DestroyImmediate(bmp);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store advance widths from the same CharacterInfo used for rendering.
|
||||
// This guarantees advances match the bitmap glyphs exactly.
|
||||
storedAdvanceWidths = new byte[96];
|
||||
for (int idx = 0; idx < 96; idx++)
|
||||
{
|
||||
if (idx < 95 && charValid[idx])
|
||||
{
|
||||
CharacterInfo ci = charInfos[idx];
|
||||
storedAdvanceWidths[idx] = (byte)Mathf.Clamp(Mathf.CeilToInt(ci.advance), 1, 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
storedAdvanceWidths[idx] = (byte)glyphWidth; // fallback
|
||||
}
|
||||
}
|
||||
|
||||
// ── Step 6: Save ──
|
||||
string path = AssetDatabase.GetAssetPath(this);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
fontTexture = bmp;
|
||||
return;
|
||||
}
|
||||
|
||||
string dir = System.IO.Path.GetDirectoryName(path);
|
||||
string texPath = dir + "/" + name + "_bitmap.png";
|
||||
System.IO.File.WriteAllBytes(texPath, bmp.EncodeToPNG());
|
||||
DestroyImmediate(bmp);
|
||||
|
||||
AssetDatabase.ImportAsset(texPath, ImportAssetOptions.ForceUpdate);
|
||||
TextureImporter importer = AssetImporter.GetAtPath(texPath) as TextureImporter;
|
||||
if (importer != null)
|
||||
{
|
||||
importer.textureType = TextureImporterType.Default;
|
||||
importer.filterMode = FilterMode.Point;
|
||||
importer.textureCompression = TextureImporterCompression.Uncompressed;
|
||||
importer.isReadable = true;
|
||||
importer.npotScale = TextureImporterNPOTScale.None;
|
||||
importer.mipmapEnabled = false;
|
||||
importer.alphaIsTransparency = true;
|
||||
importer.SaveAndReimport();
|
||||
}
|
||||
|
||||
fontTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(texPath);
|
||||
EditorUtility.SetDirty(this);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
#endif
|
||||
|
||||
public byte[] ConvertTo4BPP()
|
||||
{
|
||||
if (fontTexture == null) return null;
|
||||
|
||||
int texW = 256;
|
||||
int texH = TextureHeight;
|
||||
int bytesPerRow = texW / 2;
|
||||
byte[] result = new byte[bytesPerRow * texH];
|
||||
|
||||
Color[] pixels = fontTexture.GetPixels(0, 0, fontTexture.width, fontTexture.height);
|
||||
int srcW = fontTexture.width;
|
||||
int srcH = fontTexture.height;
|
||||
|
||||
for (int y = 0; y < texH; y++)
|
||||
{
|
||||
for (int x = 0; x < texW; x += 2)
|
||||
{
|
||||
byte lo = SamplePixel(pixels, srcW, srcH, x, y);
|
||||
byte hi = SamplePixel(pixels, srcW, srcH, x + 1, y);
|
||||
result[y * bytesPerRow + x / 2] = (byte)(lo | (hi << 4));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte SamplePixel(Color[] pixels, int srcW, int srcH, int x, int y)
|
||||
{
|
||||
if (x >= srcW || y >= srcH) return 0;
|
||||
int srcY = srcH - 1 - y; // top-down (PS1) to bottom-up (Unity)
|
||||
if (srcY < 0 || srcY >= srcH) return 0;
|
||||
Color c = pixels[srcY * srcW + x];
|
||||
return c.a > 0.5f ? (byte)1 : (byte)0;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Runtime/PSXFontAsset.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5bc0d8f6252841439e81a8406c37859
|
||||
@@ -8,6 +8,7 @@ namespace SplashEdit.RuntimeCode
|
||||
/// the onInteract Lua event fires.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(PSXObjectExporter))]
|
||||
[Icon("Packages/net.psxsplash.splashedit/Icons/PSXInteractable.png")]
|
||||
public class PSXInteractable : MonoBehaviour
|
||||
{
|
||||
[Header("Interaction Settings")]
|
||||
@@ -15,7 +16,7 @@ namespace SplashEdit.RuntimeCode
|
||||
[SerializeField] private float interactionRadius = 2.0f;
|
||||
|
||||
[Tooltip("Button that triggers interaction (0-15, matches PS1 button mapping)")]
|
||||
[SerializeField] private int interactButton = 5; // Default to Cross button
|
||||
[SerializeField] private int interactButton = 14; // Default to Cross button
|
||||
|
||||
[Tooltip("Can this object be interacted with multiple times?")]
|
||||
[SerializeField] private bool isRepeatable = true;
|
||||
@@ -23,39 +24,35 @@ namespace SplashEdit.RuntimeCode
|
||||
[Tooltip("Cooldown between interactions (in frames, 60 = 1 second at NTSC)")]
|
||||
[SerializeField] private ushort cooldownFrames = 30;
|
||||
|
||||
[Tooltip("Show interaction prompt when in range (requires UI system)")]
|
||||
[SerializeField] private bool showPrompt = true;
|
||||
[Tooltip("Show a UI canvas when the player is in range")]
|
||||
[SerializeField] private bool showPrompt = false;
|
||||
|
||||
[Tooltip("Name of the PSXCanvas to show when the player is in range")]
|
||||
[SerializeField] private string promptCanvasName = "";
|
||||
|
||||
[Header("Advanced")]
|
||||
[Tooltip("Require line-of-sight to player for interaction")]
|
||||
[Tooltip("Require the player to be facing this object to interact")]
|
||||
[SerializeField] private bool requireLineOfSight = false;
|
||||
|
||||
[Tooltip("Custom interaction point offset from object center")]
|
||||
[SerializeField] private Vector3 interactionOffset = Vector3.zero;
|
||||
|
||||
// Public accessors for export
|
||||
public float InteractionRadius => interactionRadius;
|
||||
public int InteractButton => interactButton;
|
||||
public bool IsRepeatable => isRepeatable;
|
||||
public ushort CooldownFrames => cooldownFrames;
|
||||
public bool ShowPrompt => showPrompt;
|
||||
public string PromptCanvasName => promptCanvasName;
|
||||
public bool RequireLineOfSight => requireLineOfSight;
|
||||
public Vector3 InteractionOffset => interactionOffset;
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
// Draw interaction radius
|
||||
Gizmos.color = new Color(1f, 1f, 0f, 0.3f); // Yellow, semi-transparent
|
||||
Vector3 center = transform.position + interactionOffset;
|
||||
Vector3 center = transform.position;
|
||||
Gizmos.DrawWireSphere(center, interactionRadius);
|
||||
|
||||
// Draw filled sphere with lower alpha
|
||||
Gizmos.color = new Color(1f, 1f, 0f, 0.1f);
|
||||
Gizmos.DrawSphere(center, interactionRadius);
|
||||
|
||||
// Draw interaction point
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawSphere(center, 0.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
Runtime/PSXInterpMode.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Per-keyframe interpolation mode. Must match the C++ InterpMode enum in cutscene.hh.
|
||||
/// Packed into the upper 3 bits of the 16-bit frame field on export.
|
||||
/// </summary>
|
||||
public enum PSXInterpMode : byte
|
||||
{
|
||||
Linear = 0,
|
||||
Step = 1,
|
||||
EaseIn = 2,
|
||||
EaseOut = 3,
|
||||
EaseInOut = 4,
|
||||
}
|
||||
}
|
||||
2
Runtime/PSXInterpMode.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f925971a446a614a9f4f3e61f2395c0
|
||||
26
Runtime/PSXKeyframe.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
/// <summary>
|
||||
/// A single keyframe in a cutscene track.
|
||||
/// Value interpretation depends on track type:
|
||||
/// CameraPosition / ObjectPosition: Unity world-space position (x, y, z)
|
||||
/// CameraRotation: Euler angles in degrees (x=pitch, y=yaw, z=roll)
|
||||
/// ObjectRotation: y component = rotation in degrees
|
||||
/// ObjectActive: x component = 0.0 (inactive) or 1.0 (active)
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PSXKeyframe
|
||||
{
|
||||
[Tooltip("Frame number (0 = start of cutscene). At 30fps, frame 30 = 1 second.")]
|
||||
public int Frame;
|
||||
|
||||
[Tooltip("Keyframe value. Interpretation depends on track type.")]
|
||||
public Vector3 Value;
|
||||
|
||||
[Tooltip("Interpolation mode from this keyframe to the next.")]
|
||||
public PSXInterpMode Interp = PSXInterpMode.Linear;
|
||||
}
|
||||
}
|
||||
2
Runtime/PSXKeyframe.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b2679967786bcf4e81793694cd3dfeb
|
||||
@@ -76,6 +76,8 @@ namespace SplashEdit.RuntimeCode
|
||||
finalColor += lightContribution;
|
||||
}
|
||||
|
||||
// Clamp to 0.8 to leave headroom for PS1 2x color blending mode,
|
||||
// which doubles vertex colors. Without this cap, bright areas would clip.
|
||||
finalColor.r = Mathf.Clamp(finalColor.r, 0.0f, 0.8f);
|
||||
finalColor.g = Mathf.Clamp(finalColor.g, 0.0f, 0.8f);
|
||||
finalColor.b = Mathf.Clamp(finalColor.b, 0.0f, 0.8f);
|
||||
|
||||