Core Game Loop Additions
This commit is contained in:
@@ -910,6 +910,120 @@ Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 4134353195883994, guid: 28f2883c42945194b9a7df1e5c8544c5, type: 3}
|
||||
m_PrefabInstance: {fileID: 320265325}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1 &330081557
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 330081561}
|
||||
- component: {fileID: 330081560}
|
||||
- component: {fileID: 330081559}
|
||||
- component: {fileID: 330081558}
|
||||
m_Layer: 0
|
||||
m_Name: ExpedPillar1
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!23 &330081558
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 330081557}
|
||||
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_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 69964ae457ecfd245af12cd95fa5eaed, 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: 1
|
||||
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_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!136 &330081559
|
||||
CapsuleCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 330081557}
|
||||
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: 2
|
||||
m_Radius: 0.5
|
||||
m_Height: 2
|
||||
m_Direction: 1
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &330081560
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 330081557}
|
||||
m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!4 &330081561
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 330081557}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 1026, y: 5, z: 6}
|
||||
m_LocalScale: {x: 2.5, y: 6, z: 2.5}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &330585543
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -1069,11 +1183,126 @@ MonoBehaviour:
|
||||
OrthoSize: 10
|
||||
FollowSharpness: 8
|
||||
FallbackTarget: {x: 3, y: 0, z: 4}
|
||||
AimLeadDistance: 2.5
|
||||
--- !u!4 &331593291 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 4855556370681302, guid: 65f76c5f923964045833f09a3f767a16, type: 3}
|
||||
m_PrefabInstance: {fileID: 232019258}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1 &384552424
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 384552428}
|
||||
- component: {fileID: 384552427}
|
||||
- component: {fileID: 384552426}
|
||||
- component: {fileID: 384552425}
|
||||
m_Layer: 0
|
||||
m_Name: ExpedPillar4
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!23 &384552425
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 384552424}
|
||||
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_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 69964ae457ecfd245af12cd95fa5eaed, 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: 1
|
||||
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_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!136 &384552426
|
||||
CapsuleCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 384552424}
|
||||
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: 2
|
||||
m_Radius: 0.5
|
||||
m_Height: 2
|
||||
m_Direction: 1
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &384552427
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 384552424}
|
||||
m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!4 &384552428
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 384552424}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 994, y: 4, z: -26}
|
||||
m_LocalScale: {x: 2, y: 5, z: 2}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1001 &390565387
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -1553,6 +1782,120 @@ PrefabInstance:
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 0af7253adfe9a6c4ca7be0a1573f3c77, type: 3}
|
||||
--- !u!1 &476566464
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 476566468}
|
||||
- component: {fileID: 476566467}
|
||||
- component: {fileID: 476566466}
|
||||
- component: {fileID: 476566465}
|
||||
m_Layer: 0
|
||||
m_Name: ExpedPillar2
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!23 &476566465
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 476566464}
|
||||
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_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 69964ae457ecfd245af12cd95fa5eaed, 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: 1
|
||||
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_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!136 &476566466
|
||||
CapsuleCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 476566464}
|
||||
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: 2
|
||||
m_Radius: 0.5
|
||||
m_Height: 2
|
||||
m_Direction: 1
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &476566467
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 476566464}
|
||||
m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!4 &476566468
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 476566464}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 974, y: 5, z: -6}
|
||||
m_LocalScale: {x: 2.5, y: 6, z: 2.5}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1001 &515680011
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -3448,6 +3791,119 @@ PrefabInstance:
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 0af7253adfe9a6c4ca7be0a1573f3c77, type: 3}
|
||||
--- !u!1 &1541455365
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1541455369}
|
||||
- component: {fileID: 1541455368}
|
||||
- component: {fileID: 1541455367}
|
||||
- component: {fileID: 1541455366}
|
||||
m_Layer: 0
|
||||
m_Name: ExpeditionGround
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!23 &1541455366
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1541455365}
|
||||
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_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 6afcc12e55743ac45b265795f24238dd, 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: 1
|
||||
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_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!64 &1541455367
|
||||
MeshCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1541455365}
|
||||
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: 5
|
||||
m_Convex: 0
|
||||
m_CookingOptions: 30
|
||||
m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!33 &1541455368
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1541455365}
|
||||
m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!4 &1541455369
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1541455365}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 1000, y: 0, z: 0}
|
||||
m_LocalScale: {x: 8, y: 1, z: 8}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1001 &1577269770
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -4283,6 +4739,120 @@ PrefabInstance:
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: c037f4cd64354fd428bbf6b6007c235d, type: 3}
|
||||
--- !u!1 &2009335283
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2009335287}
|
||||
- component: {fileID: 2009335286}
|
||||
- component: {fileID: 2009335285}
|
||||
- component: {fileID: 2009335284}
|
||||
m_Layer: 0
|
||||
m_Name: ExpedPillar3
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!23 &2009335284
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2009335283}
|
||||
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_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 69964ae457ecfd245af12cd95fa5eaed, 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: 1
|
||||
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_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!136 &2009335285
|
||||
CapsuleCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2009335283}
|
||||
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: 2
|
||||
m_Radius: 0.5
|
||||
m_Height: 2
|
||||
m_Direction: 1
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &2009335286
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2009335283}
|
||||
m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!4 &2009335287
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2009335283}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 1006, y: 4, z: 26}
|
||||
m_LocalScale: {x: 2, y: 5, z: 2}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1001 &2017077544
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -4619,3 +5189,8 @@ SceneRoots:
|
||||
- {fileID: 228729546}
|
||||
- {fileID: 1736670160}
|
||||
- {fileID: 1116745582}
|
||||
- {fileID: 1541455369}
|
||||
- {fileID: 330081561}
|
||||
- {fileID: 476566468}
|
||||
- {fileID: 2009335287}
|
||||
- {fileID: 384552428}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ddd381e1325c5ab488ff60c7e7371a70
|
||||
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
|
||||
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:
|
||||
Binary file not shown.
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b4a5ded5202520459e9961d37e871c8
|
||||
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
|
||||
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:
|
||||
Binary file not shown.
@@ -0,0 +1,117 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9155a3ec3c4385a4d87c148d86cfaa97
|
||||
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
|
||||
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:
|
||||
@@ -0,0 +1,137 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: M_ExpeditionGround
|
||||
m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap:
|
||||
RenderType: Opaque
|
||||
disabledShaderPasses:
|
||||
- MOTIONVECTORS
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _BaseMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _BumpMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailAlbedoMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailMask:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailNormalMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _EmissionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MetallicGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _OcclusionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _ParallaxMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _SpecGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_Lightmaps:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_LightmapsInd:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- unity_ShadowMasks:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _AddPrecomputedVelocity: 0
|
||||
- _AlphaClip: 0
|
||||
- _AlphaToMask: 0
|
||||
- _Blend: 0
|
||||
- _BlendModePreserveSpecular: 1
|
||||
- _BumpScale: 1
|
||||
- _ClearCoatMask: 0
|
||||
- _ClearCoatSmoothness: 0
|
||||
- _Cull: 2
|
||||
- _Cutoff: 0.5
|
||||
- _DetailAlbedoMapScale: 1
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _DstBlendAlpha: 0
|
||||
- _EnvironmentReflections: 1
|
||||
- _GlossMapScale: 0
|
||||
- _Glossiness: 0
|
||||
- _GlossyReflections: 0
|
||||
- _Metallic: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Parallax: 0.005
|
||||
- _QueueOffset: 0
|
||||
- _ReceiveShadows: 1
|
||||
- _Smoothness: 0.5
|
||||
- _SmoothnessTextureChannel: 0
|
||||
- _SpecularHighlights: 1
|
||||
- _SrcBlend: 1
|
||||
- _SrcBlendAlpha: 1
|
||||
- _Surface: 0
|
||||
- _WorkflowMode: 1
|
||||
- _XRMotionVectorsPass: 1
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 0.33, g: 0.28, b: 0.48, a: 1}
|
||||
- _Color: {r: 0.32999998, g: 0.27999997, b: 0.47999996, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
--- !u!114 &7570948517623919954
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 11
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion
|
||||
version: 10
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6afcc12e55743ac45b265795f24238dd
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,86 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &3885353946372160549
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3572766465862231365}
|
||||
- component: {fileID: 9053853372340598254}
|
||||
- component: {fileID: 6834786618115927220}
|
||||
- component: {fileID: 5858859957262695065}
|
||||
m_Layer: 0
|
||||
m_Name: CycleDirector
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &3572766465862231365
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 2.5, y: 2.5, z: 2.5}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &9053853372340598254
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c16549610bfe4458aa9389201d072bb6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.Entities.Hybrid::Unity.Entities.Hybrid.Baking.LinkedEntityGroupAuthoring
|
||||
--- !u!114 &6834786618115927220
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7c79d771cedb4794bf100ce60df5f764, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.NetCode.Authoring.Hybrid::Unity.NetCode.GhostAuthoringComponent
|
||||
HasOwner: 0
|
||||
SupportAutoCommandTarget: 1
|
||||
TrackInterpolationDelay: 0
|
||||
GhostGroup: 0
|
||||
UsePreSerialization: 0
|
||||
UseSingleBaseline: 0
|
||||
RollbackPredictedSpawnedGhostState: 0
|
||||
RollbackPredictionOnStructuralChanges: 1
|
||||
DefaultGhostMode: 0
|
||||
SupportedGhostModes: 3
|
||||
OptimizationMode: 0
|
||||
Importance: 1
|
||||
MaxSendRate: 0
|
||||
prefabId:
|
||||
--- !u!114 &5858859957262695065
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 843fd0d567f48ad46840dcce0ce84bbc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.CycleDirectorAuthoring
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 529ca7203da40f5489e9e3040ed1fc22
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,149 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &3885353946372160549
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3572766465862231365}
|
||||
- component: {fileID: 3909651526955663392}
|
||||
- component: {fileID: 3320445911748035220}
|
||||
- component: {fileID: 9053853372340598254}
|
||||
- component: {fileID: 6834786618115927220}
|
||||
- component: {fileID: 88785505736215562}
|
||||
m_Layer: 0
|
||||
m_Name: ResourceNode
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &3572766465862231365
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 2.5, y: 2.5, z: 2.5}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &3909651526955663392
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
m_Mesh: {fileID: 4300000, guid: abc00000000010690097314383055197, type: 3}
|
||||
--- !u!23 &3320445911748035220
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
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_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: ba23fa98368bb4a4997bfd08547c83ee, 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: 1
|
||||
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_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!114 &9053853372340598254
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c16549610bfe4458aa9389201d072bb6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.Entities.Hybrid::Unity.Entities.Hybrid.Baking.LinkedEntityGroupAuthoring
|
||||
--- !u!114 &6834786618115927220
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7c79d771cedb4794bf100ce60df5f764, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Unity.NetCode.Authoring.Hybrid::Unity.NetCode.GhostAuthoringComponent
|
||||
HasOwner: 0
|
||||
SupportAutoCommandTarget: 1
|
||||
TrackInterpolationDelay: 0
|
||||
GhostGroup: 0
|
||||
UsePreSerialization: 0
|
||||
UseSingleBaseline: 0
|
||||
RollbackPredictedSpawnedGhostState: 0
|
||||
RollbackPredictionOnStructuralChanges: 1
|
||||
DefaultGhostMode: 0
|
||||
SupportedGhostModes: 3
|
||||
OptimizationMode: 0
|
||||
Importance: 1
|
||||
MaxSendRate: 0
|
||||
prefabId: 8565e5eb00679fb45b8b7dac1e2ae9f3
|
||||
--- !u!114 &88785505736215562
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3885353946372160549}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 174563c586d9a0f4bb84cca191f4a1f0, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.ResourceNodeAuthoring
|
||||
Kind: 1
|
||||
Amount: 30
|
||||
HarvestPerHit: 5
|
||||
HitRadius: 1.2
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8565e5eb00679fb45b8b7dac1e2ae9f3
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c81ee1ba775463a4d940bb397a01f4e6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,39 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Authoring for the baked <see cref="ResourceFieldSpawner"/> singleton (mirrors StorageSpawnerAuthoring).
|
||||
/// Place once in the gameplay subscene and assign the resource-node ghost prefab; ExpeditionFieldSystem
|
||||
/// scatters the field each Expedition. Carries no transform.
|
||||
/// </summary>
|
||||
public class ResourceFieldSpawnerAuthoring : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Resource-node ghost prefab. Must carry ResourceNodeAuthoring + a GhostAuthoringComponent (ownerless, interpolated).")]
|
||||
public GameObject NodePrefab;
|
||||
|
||||
[Tooltip("Number of nodes per expedition.")]
|
||||
[Min(1)] public int Count = 8;
|
||||
|
||||
[Tooltip("Scatter radius (world units) around the expedition origin.")]
|
||||
[Min(1f)] public float Radius = 12f;
|
||||
|
||||
private class ResourceFieldSpawnerBaker : Baker<ResourceFieldSpawnerAuthoring>
|
||||
{
|
||||
public override void Bake(ResourceFieldSpawnerAuthoring authoring)
|
||||
{
|
||||
var entity = GetEntity(authoring, TransformUsageFlags.None);
|
||||
AddComponent(entity, new ResourceFieldSpawner
|
||||
{
|
||||
Prefab = authoring.NodePrefab != null
|
||||
? GetEntity(authoring.NodePrefab, TransformUsageFlags.Dynamic)
|
||||
: Entity.Null,
|
||||
Count = authoring.Count,
|
||||
Radius = authoring.Radius,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9e840f8b95266140b0ac5bd4e81391b
|
||||
@@ -0,0 +1,45 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Authoring for a resource-node ghost prefab (ownerless interpolated — duplicate from UpgradePickup.prefab
|
||||
/// so the GhostAuthoringComponent comes free). Bakes <see cref="ResourceNode"/> + <see cref="HitRadius"/>
|
||||
/// (reused for the harvest hit test) + <see cref="RegionTag"/>{Expedition} so GhostRelevancy scopes the node
|
||||
/// to expedition players. The field spawner overrides ResourceId (round-robin) and Position per instance.
|
||||
/// </summary>
|
||||
public class ResourceNodeAuthoring : MonoBehaviour
|
||||
{
|
||||
public enum ResourceKind : byte { Aether = 1, Ore = 2, Biomass = 3 }
|
||||
|
||||
[Tooltip("Default resource type (the spawner round-robins this per node).")]
|
||||
public ResourceKind Kind = ResourceKind.Aether;
|
||||
|
||||
[Tooltip("Total resource units in the node before it depletes.")]
|
||||
[Min(1)] public int Amount = 30;
|
||||
|
||||
[Tooltip("Units harvested per projectile hit.")]
|
||||
[Min(1f)] public float HarvestPerHit = 5f;
|
||||
|
||||
[Tooltip("Hit radius (world units) for the harvest sweep.")]
|
||||
[Min(0f)] public float HitRadius = 1.2f;
|
||||
|
||||
private class ResourceNodeBaker : Baker<ResourceNodeAuthoring>
|
||||
{
|
||||
public override void Bake(ResourceNodeAuthoring authoring)
|
||||
{
|
||||
var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
|
||||
AddComponent(entity, new ResourceNode
|
||||
{
|
||||
ResourceId = (byte)authoring.Kind,
|
||||
Remaining = authoring.Amount,
|
||||
HarvestPerHit = authoring.HarvestPerHit,
|
||||
});
|
||||
AddComponent(entity, new HitRadius { Value = authoring.HitRadius });
|
||||
AddComponent(entity, new RegionTag { Region = RegionId.Expedition });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 174563c586d9a0f4bb84cca191f4a1f0
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b2e77367c16a9a41943145e582954d1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Authoring for the GLOBAL cycle-director ghost prefab: an ownerless INTERPOLATED ghost (the
|
||||
/// GhostAuthoringComponent is inherited when this prefab is duplicated from UpgradePickup.prefab) that
|
||||
/// carries the replicated macro-loop state (<see cref="CycleState"/>) and the shared resource ledger
|
||||
/// (a <see cref="StorageEntry"/> buffer marked by <see cref="ResourceLedger"/>). It is GLOBAL — it must
|
||||
/// carry NO <see cref="RegionTag"/> so GhostRelevancy keeps it relevant to every connection regardless of
|
||||
/// region. The server CycleDirectorSpawnSystem overrides the baked CycleState at spawn (real PhaseEndTick)
|
||||
/// and adds the server-only CycleRuntime.
|
||||
/// </summary>
|
||||
public class CycleDirectorAuthoring : MonoBehaviour
|
||||
{
|
||||
private class CycleDirectorBaker : Baker<CycleDirectorAuthoring>
|
||||
{
|
||||
public override void Bake(CycleDirectorAuthoring authoring)
|
||||
{
|
||||
var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
|
||||
AddComponent(entity, new CycleState
|
||||
{
|
||||
Phase = CyclePhase.Expedition,
|
||||
CycleNumber = 1,
|
||||
PhaseEndTick = 0u,
|
||||
});
|
||||
AddComponent<ResourceLedger>(entity);
|
||||
AddBuffer<StorageEntry>(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 843fd0d567f48ad46840dcce0ce84bbc
|
||||
@@ -0,0 +1,31 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Authoring for the baked <see cref="CycleDirectorSpawner"/> singleton (mirrors StorageSpawnerAuthoring).
|
||||
/// Place once in the gameplay subscene; the server-only CycleDirectorSpawnSystem reads it, instantiates the
|
||||
/// global cycle-director ghost, then destroys the singleton so it fires exactly once. Carries no transform.
|
||||
/// </summary>
|
||||
public class CycleDirectorSpawnerAuthoring : MonoBehaviour
|
||||
{
|
||||
[Tooltip("Cycle-director ghost prefab. Must carry CycleDirectorAuthoring + a GhostAuthoringComponent (ownerless, interpolated).")]
|
||||
public GameObject DirectorPrefab;
|
||||
|
||||
private class CycleDirectorSpawnerBaker : Baker<CycleDirectorSpawnerAuthoring>
|
||||
{
|
||||
public override void Bake(CycleDirectorSpawnerAuthoring authoring)
|
||||
{
|
||||
var entity = GetEntity(authoring, TransformUsageFlags.None);
|
||||
AddComponent(entity, new CycleDirectorSpawner
|
||||
{
|
||||
Prefab = authoring.DirectorPrefab != null
|
||||
? GetEntity(authoring.DirectorPrefab, TransformUsageFlags.Dynamic)
|
||||
: Entity.Null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f4d2cb1e17d6a1429525674969dd3f0
|
||||
@@ -0,0 +1,45 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ProjectM.Authoring
|
||||
{
|
||||
/// <summary>
|
||||
/// Authoring for a walk-in <see cref="ExpeditionGate"/>. Place on a visible gate object in the gameplay
|
||||
/// subscene; baked into both worlds at the gate's position (the server reads its LocalTransform for the
|
||||
/// overlap test, the client renders the mesh). Set From/To regions + the arrival point in the destination
|
||||
/// region (offset from that region's gate so the player doesn't immediately re-trigger).
|
||||
/// </summary>
|
||||
public class ExpeditionGateAuthoring : MonoBehaviour
|
||||
{
|
||||
public enum Region : byte { Base = 0, Expedition = 1 }
|
||||
|
||||
[Tooltip("Region a player must be in for this gate to act on them.")]
|
||||
public Region From = Region.Base;
|
||||
|
||||
[Tooltip("Region the player is transited to.")]
|
||||
public Region To = Region.Expedition;
|
||||
|
||||
[Min(0.5f)] public float Radius = 2.5f;
|
||||
|
||||
[Tooltip("Where the player arrives in the destination region (offset from that region's gate).")]
|
||||
public Vector3 ArrivalPos = new Vector3(1000f, 1f, 0f);
|
||||
|
||||
private class ExpeditionGateBaker : Baker<ExpeditionGateAuthoring>
|
||||
{
|
||||
public override void Bake(ExpeditionGateAuthoring authoring)
|
||||
{
|
||||
// Dynamic so the baked entity carries a LocalTransform the server can read for the overlap test.
|
||||
var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
|
||||
AddComponent(entity, new ExpeditionGate
|
||||
{
|
||||
FromRegion = (byte)authoring.From,
|
||||
ToRegion = (byte)authoring.To,
|
||||
Radius = authoring.Radius,
|
||||
ArrivalPos = new float3(authoring.ArrivalPos.x, authoring.ArrivalPos.y, authoring.ArrivalPos.z),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22f744b59ad23834abe28fc09b661005
|
||||
@@ -24,6 +24,9 @@ namespace ProjectM.Client
|
||||
RectTransform _cooldownFill;
|
||||
Text _healthText;
|
||||
Text _threatText;
|
||||
Text _phaseText;
|
||||
Text _resourceText;
|
||||
Text _locationText;
|
||||
GameObject _respawnOverlay;
|
||||
EntityQuery _huskQuery;
|
||||
|
||||
@@ -48,6 +51,55 @@ namespace ProjectM.Client
|
||||
|
||||
bool haveTick = SystemAPI.TryGetSingleton<NetworkTime>(out var nt);
|
||||
|
||||
// Macro-loop HUD (phase + cycle + countdown + location), read before the per-player early-out so it persists pre-spawn.
|
||||
bool haveCycle = SystemAPI.TryGetSingleton<CycleState>(out var cyc);
|
||||
if (_phaseText != null && haveCycle)
|
||||
{
|
||||
var endTick = new NetworkTick(cyc.PhaseEndTick);
|
||||
string detail;
|
||||
if (cyc.Phase == CyclePhase.Defend)
|
||||
detail = _huskQuery.CalculateEntityCount() + " HUSKS";
|
||||
else if (haveTick && cyc.PhaseEndTick != 0 && endTick.IsValid && endTick.IsNewerThan(nt.ServerTick))
|
||||
detail = (endTick.TicksSince(nt.ServerTick) / 60) + "s";
|
||||
else
|
||||
detail = "";
|
||||
_phaseText.text = PhaseLabel(cyc.Phase) + (detail.Length > 0 ? " - " + detail : "") + " CYCLE " + cyc.CycleNumber;
|
||||
_phaseText.color = PhaseColor(cyc.Phase);
|
||||
}
|
||||
else if (_phaseText != null)
|
||||
{
|
||||
_phaseText.text = "";
|
||||
}
|
||||
|
||||
if (_locationText != null)
|
||||
{
|
||||
var cam = Camera.main;
|
||||
bool onExpedition = cam != null && cam.transform.position.x > 500f;
|
||||
_locationText.text = onExpedition
|
||||
? "ON EXPEDITION - return through the gate"
|
||||
: "AT BASE" + (haveCycle && cyc.Phase == CyclePhase.Expedition ? " - step into the gate to deploy" : "");
|
||||
_locationText.color = onExpedition ? new Color(1f, 0.8f, 0.4f) : new Color(0.6f, 0.85f, 1f);
|
||||
}
|
||||
|
||||
if (_resourceText != null)
|
||||
{
|
||||
string res = "";
|
||||
if (SystemAPI.TryGetSingletonEntity<ResourceLedger>(out var ledgerE))
|
||||
{
|
||||
var buf = SystemAPI.GetBuffer<StorageEntry>(ledgerE);
|
||||
int aether = 0, ore = 0, bio = 0;
|
||||
for (int i = 0; i < buf.Length; i++)
|
||||
{
|
||||
var en = buf[i];
|
||||
if (en.ItemId == ResourceId.Aether) aether = en.Count;
|
||||
else if (en.ItemId == ResourceId.Ore) ore = en.Count;
|
||||
else if (en.ItemId == ResourceId.Biomass) bio = en.Count;
|
||||
}
|
||||
res = "AETHER " + aether + " ORE " + ore + " BIO " + bio;
|
||||
}
|
||||
_resourceText.text = res;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
float hp = 0f, maxHp = 1f, cdFrac = 1f;
|
||||
bool dead = false, shielded = false;
|
||||
@@ -77,7 +129,7 @@ namespace ProjectM.Client
|
||||
break;
|
||||
}
|
||||
|
||||
_canvas.enabled = found;
|
||||
_canvas.enabled = found || haveCycle;
|
||||
if (!found) return;
|
||||
|
||||
float frac = maxHp > 0f ? Mathf.Clamp01(hp / maxHp) : 0f;
|
||||
@@ -139,6 +191,27 @@ namespace ProjectM.Client
|
||||
trt.anchorMin = new Vector2(1, 1); trt.anchorMax = new Vector2(1, 1); trt.pivot = new Vector2(1, 1);
|
||||
trt.anchoredPosition = new Vector2(-40, -30); trt.sizeDelta = new Vector2(380, 50);
|
||||
|
||||
// Cycle phase + number (top-center).
|
||||
_phaseText = MakeText("PhaseText", _canvas.transform, "EXPEDITION CYCLE 1", 34, TextAnchor.UpperCenter,
|
||||
new Color(0.55f, 0.9f, 1f), font);
|
||||
var prt = _phaseText.rectTransform;
|
||||
prt.anchorMin = new Vector2(0.5f, 1f); prt.anchorMax = new Vector2(0.5f, 1f); prt.pivot = new Vector2(0.5f, 1f);
|
||||
prt.anchoredPosition = new Vector2(0, -24); prt.sizeDelta = new Vector2(600, 50);
|
||||
|
||||
// Resource ledger counts (top-center, below phase).
|
||||
_resourceText = MakeText("ResourceText", _canvas.transform, "", 24, TextAnchor.UpperCenter,
|
||||
new Color(0.7f, 0.95f, 0.8f), font);
|
||||
var rrt = _resourceText.rectTransform;
|
||||
rrt.anchorMin = new Vector2(0.5f, 1f); rrt.anchorMax = new Vector2(0.5f, 1f); rrt.pivot = new Vector2(0.5f, 1f);
|
||||
rrt.anchoredPosition = new Vector2(0, -64); rrt.sizeDelta = new Vector2(600, 40);
|
||||
|
||||
// Location + gate hint (top-center, below resources).
|
||||
_locationText = MakeText("LocationText", _canvas.transform, "", 22, TextAnchor.UpperCenter,
|
||||
new Color(0.6f, 0.85f, 1f), font);
|
||||
var lrt = _locationText.rectTransform;
|
||||
lrt.anchorMin = new Vector2(0.5f, 1f); lrt.anchorMax = new Vector2(0.5f, 1f); lrt.pivot = new Vector2(0.5f, 1f);
|
||||
lrt.anchoredPosition = new Vector2(0, -96); lrt.sizeDelta = new Vector2(760, 36);
|
||||
|
||||
// Downed / respawning overlay (full screen, toggled by Dead).
|
||||
_respawnOverlay = new GameObject("RespawnOverlay", typeof(RectTransform));
|
||||
_respawnOverlay.transform.SetParent(_canvas.transform, false);
|
||||
@@ -211,5 +284,27 @@ namespace ProjectM.Client
|
||||
if (f == null) f = Font.CreateDynamicFontFromOSFont(new[] { "Arial", "Liberation Sans", "DejaVu Sans" }, 28);
|
||||
return f;
|
||||
}
|
||||
|
||||
static Color PhaseColor(byte phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case CyclePhase.Expedition: return new Color(0.45f, 0.85f, 1f);
|
||||
case CyclePhase.Defend: return new Color(1f, 0.5f, 0.3f);
|
||||
case CyclePhase.Build: return new Color(0.45f, 0.95f, 0.6f);
|
||||
default: return Color.white;
|
||||
}
|
||||
}
|
||||
|
||||
static string PhaseLabel(byte phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case CyclePhase.Expedition: return "EXPEDITION";
|
||||
case CyclePhase.Defend: return "DEFEND";
|
||||
case CyclePhase.Build: return "BUILD";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@ namespace ProjectM.Server
|
||||
if (!serverTick.IsValid)
|
||||
return;
|
||||
uint now = serverTick.TickIndexForValidTick;
|
||||
// M6 Aether Cycle: the base-defense wave only runs during the Defend phase.
|
||||
if (SystemAPI.TryGetSingleton<CycleState>(out var cycle) && cycle.Phase != CyclePhase.Defend)
|
||||
return;
|
||||
|
||||
var director = SystemAPI.GetSingleton<WaveDirector>();
|
||||
var directorEntity = SystemAPI.GetSingletonEntity<WaveDirector>();
|
||||
@@ -84,6 +87,8 @@ namespace ProjectM.Server
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
var husk = ecb.Instantiate(prefabs[prefabIdx].Prefab);
|
||||
ecb.SetComponent(husk, LocalTransform.FromPosition(pos));
|
||||
// Husks belong to the base region (hidden from expedition players by relevancy).
|
||||
ecb.AddComponent(husk, new RegionTag { Region = RegionId.Base });
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
|
||||
|
||||
@@ -53,6 +53,8 @@ namespace ProjectM.Server
|
||||
var player = ecb.Instantiate(spawner.PlayerPrefab);
|
||||
ecb.SetComponent(player, LocalTransform.FromPosition(center + PlayerSpawnMath.SpawnOffset(networkId.Value, spawner.SpawnRingRadius, spawner.RingSlots)));
|
||||
ecb.SetComponent(player, new GhostOwner { NetworkId = networkId.Value });
|
||||
// Tag the player into the base region (M6 region/relevancy split).
|
||||
ecb.AddComponent(player, new RegionTag { Region = RegionId.Base });
|
||||
|
||||
// Auto-despawn the player when its owning connection is removed.
|
||||
ecb.AppendToBuffer(connection, new LinkedEntityGroup { Value = player });
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c169eff521b1ff748b598a1b1a895196
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,88 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-only procedural expedition-field manager. Edge-triggered off the cycle phase (via the server-only
|
||||
/// <see cref="CycleRuntime.PrevPhase"/>): on ENTERING Expedition for a not-yet-seeded cycle it scatters
|
||||
/// <see cref="ResourceFieldSpawner.Count"/> resource-node ghosts (seeded by CycleNumber via
|
||||
/// <see cref="Unity.Mathematics.Random"/>) around the expedition region origin, each
|
||||
/// <see cref="RegionTag"/>{Expedition}; on LEAVING Expedition it destroys every node. Runs in the plain
|
||||
/// server SimulationSystemGroup <c>[UpdateAfter(CyclePhaseSystem)]</c> so the phase edge is observed the
|
||||
/// same tick. Server-authoritative; clients despawn nodes via GhostDespawnSystem. Per-cycle reproducible
|
||||
/// (the seed is the monotonic int CycleNumber, compared by equality — never tick math; never seed 0).
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateInGroup(typeof(SimulationSystemGroup))]
|
||||
[UpdateAfter(typeof(CyclePhaseSystem))]
|
||||
public partial struct ExpeditionFieldSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
state.RequireForUpdate<ResourceFieldSpawner>();
|
||||
state.RequireForUpdate<CycleState>();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var cycleEntity = SystemAPI.GetSingletonEntity<CycleState>();
|
||||
var cycle = SystemAPI.GetComponent<CycleState>(cycleEntity);
|
||||
var runtime = SystemAPI.GetComponent<CycleRuntime>(cycleEntity);
|
||||
var spawner = SystemAPI.GetSingleton<ResourceFieldSpawner>();
|
||||
|
||||
float3 baseCenter = new float3(0f, 1f, 0f);
|
||||
if (SystemAPI.TryGetSingleton<BaseAnchor>(out var anchor))
|
||||
baseCenter = BaseGridMath.PlotCenter(anchor);
|
||||
float3 origin = RegionMath.RegionOrigin(RegionId.Expedition, baseCenter);
|
||||
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
// SPAWN edge: entered Expedition for a cycle we have not seeded yet.
|
||||
if (cycle.Phase == CyclePhase.Expedition
|
||||
&& runtime.LastSpawnedCycle != cycle.CycleNumber
|
||||
&& spawner.Prefab != Entity.Null)
|
||||
{
|
||||
var baseXform = SystemAPI.GetComponent<LocalTransform>(spawner.Prefab);
|
||||
var prefabNode = SystemAPI.GetComponent<ResourceNode>(spawner.Prefab);
|
||||
var rng = new Random((uint)math.max(1, cycle.CycleNumber));
|
||||
int count = math.max(1, spawner.Count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var node = ecb.Instantiate(spawner.Prefab);
|
||||
|
||||
float ang = rng.NextFloat(0f, math.PI * 2f);
|
||||
float rad = spawner.Radius * math.sqrt(rng.NextFloat(0f, 1f));
|
||||
var xform = baseXform;
|
||||
xform.Position = origin + new float3(math.cos(ang) * rad, 0f, math.sin(ang) * rad);
|
||||
ecb.SetComponent(node, xform);
|
||||
|
||||
// Round-robin the resource type (Aether / Ore / Biomass) over the prefab's baked node.
|
||||
var rn = prefabNode;
|
||||
rn.ResourceId = (byte)(ResourceId.Aether + (byte)(i % 3));
|
||||
ecb.SetComponent(node, rn);
|
||||
}
|
||||
runtime.LastSpawnedCycle = cycle.CycleNumber;
|
||||
}
|
||||
|
||||
// DESTROY edge: left Expedition — clear the whole field.
|
||||
if (runtime.PrevPhase == CyclePhase.Expedition && cycle.Phase != CyclePhase.Expedition)
|
||||
{
|
||||
foreach (var (rn, e) in SystemAPI.Query<RefRO<ResourceNode>>().WithEntityAccess())
|
||||
ecb.DestroyEntity(e);
|
||||
}
|
||||
|
||||
runtime.PrevPhase = cycle.Phase;
|
||||
SystemAPI.SetComponent(cycleEntity, runtime);
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9267d7809e68ea54caa55378f33e67f6
|
||||
@@ -0,0 +1,134 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.NetCode;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-only resource harvest: sweeps each surviving projectile's this-tick travel segment against
|
||||
/// resource-node ghosts and deposits <see cref="ResourceNode.HarvestPerHit"/> of the node's
|
||||
/// <see cref="ResourceNode.ResourceId"/> into the GLOBAL resource ledger (the CycleDirector's
|
||||
/// <see cref="StorageEntry"/> buffer, resolved via <see cref="ResourceLedger"/> — NEVER
|
||||
/// GetSingleton<StorageEntry>, which would collide with the base storage container). Runs in the plain
|
||||
/// server SimulationSystemGroup <c>[UpdateAfter(PredictedSimulationSystemGroup)]</c> — after
|
||||
/// ProjectileDamageSystem has already consumed Health-target hits and range-expired projectiles, so this
|
||||
/// only sees true survivors. The swept segment is reconstructed from <see cref="Projectile.LastStep"/>
|
||||
/// (written by ProjectileMoveSystem in the fixed-step group), so it is tunnelling-safe WITHOUT depending on
|
||||
/// this plain group's variable-frame DeltaTime. A node hit by two projectiles in one tick deposits twice
|
||||
/// but is destroyed exactly once. Relies on the asserted ~1000-unit base/expedition coordinate gap so a
|
||||
/// base projectile can never geometrically reach an expedition node.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateInGroup(typeof(SimulationSystemGroup))]
|
||||
[UpdateAfter(typeof(PredictedSimulationSystemGroup))]
|
||||
public partial struct ResourceHarvestSystem : ISystem
|
||||
{
|
||||
const float k_ProjectileRadius = 0.2f;
|
||||
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
state.RequireForUpdate<Projectile>();
|
||||
state.RequireForUpdate<ResourceNode>();
|
||||
state.RequireForUpdate<ResourceLedger>();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var ledgerEntity = SystemAPI.GetSingletonEntity<ResourceLedger>();
|
||||
var ledger = SystemAPI.GetBuffer<StorageEntry>(ledgerEntity);
|
||||
|
||||
// Snapshot all nodes once this tick.
|
||||
var nodeEntities = new NativeList<Entity>(Allocator.Temp);
|
||||
var nodePos = new NativeList<float2>(Allocator.Temp);
|
||||
var nodeRadius = new NativeList<float>(Allocator.Temp);
|
||||
var nodeRemaining = new NativeList<int>(Allocator.Temp);
|
||||
var nodeResource = new NativeList<byte>(Allocator.Temp);
|
||||
var nodePerHit = new NativeList<float>(Allocator.Temp);
|
||||
|
||||
foreach (var (xform, hr, node, e) in
|
||||
SystemAPI.Query<RefRO<LocalTransform>, RefRO<HitRadius>, RefRO<ResourceNode>>().WithEntityAccess())
|
||||
{
|
||||
nodeEntities.Add(e);
|
||||
nodePos.Add(xform.ValueRO.Position.xz);
|
||||
nodeRadius.Add(hr.ValueRO.Value);
|
||||
nodeRemaining.Add(node.ValueRO.Remaining);
|
||||
nodeResource.Add(node.ValueRO.ResourceId);
|
||||
nodePerHit.Add(node.ValueRO.HarvestPerHit);
|
||||
}
|
||||
|
||||
var destroyed = new NativeArray<bool>(nodeEntities.Length, Allocator.Temp);
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
foreach (var (xform, proj, projEntity) in
|
||||
SystemAPI.Query<RefRO<LocalTransform>, RefRO<Projectile>>().WithEntityAccess())
|
||||
{
|
||||
float3 cur = xform.ValueRO.Position;
|
||||
float2 segEnd = cur.xz;
|
||||
float2 segStart = segEnd - proj.ValueRO.Direction * proj.ValueRO.LastStep;
|
||||
float2 seg = segEnd - segStart;
|
||||
float segLenSq = math.lengthsq(seg);
|
||||
|
||||
int bestIdx = -1;
|
||||
float bestT = float.MaxValue;
|
||||
for (int i = 0; i < nodeEntities.Length; i++)
|
||||
{
|
||||
if (destroyed[i]) continue;
|
||||
float2 tp = nodePos[i];
|
||||
float t = segLenSq > 1e-8f ? math.saturate(math.dot(tp - segStart, seg) / segLenSq) : 0f;
|
||||
float2 closest = segStart + t * seg;
|
||||
float hitDist = nodeRadius[i] + k_ProjectileRadius;
|
||||
if (math.distancesq(tp, closest) <= hitDist * hitDist && t < bestT)
|
||||
{
|
||||
bestT = t;
|
||||
bestIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIdx < 0)
|
||||
continue;
|
||||
|
||||
int amount = (int)nodePerHit[bestIdx];
|
||||
StorageMath.Deposit(ledger, nodeResource[bestIdx], amount);
|
||||
int rem = nodeRemaining[bestIdx] - amount;
|
||||
nodeRemaining[bestIdx] = rem;
|
||||
ecb.DestroyEntity(projEntity);
|
||||
|
||||
if (rem <= 0)
|
||||
{
|
||||
if (!destroyed[bestIdx])
|
||||
{
|
||||
destroyed[bestIdx] = true;
|
||||
ecb.DestroyEntity(nodeEntities[bestIdx]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Persist the decremented Remaining (replicated GhostField) so depletion carries across ticks.
|
||||
SystemAPI.SetComponent(nodeEntities[bestIdx], new ResourceNode
|
||||
{
|
||||
ResourceId = nodeResource[bestIdx],
|
||||
Remaining = rem,
|
||||
HarvestPerHit = nodePerHit[bestIdx],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
destroyed.Dispose();
|
||||
nodeEntities.Dispose();
|
||||
nodePos.Dispose();
|
||||
nodeRadius.Dispose();
|
||||
nodeRemaining.Dispose();
|
||||
nodeResource.Dispose();
|
||||
nodePerHit.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e1ce72e524297a48a08085c68ff908e
|
||||
@@ -43,6 +43,8 @@ namespace ProjectM.Server
|
||||
var xform = SystemAPI.GetComponent<LocalTransform>(spawner.Prefab);
|
||||
xform.Position = position;
|
||||
ecb.SetComponent(container, xform);
|
||||
// M6: scope the shared storage to the base region for ghost relevancy.
|
||||
ecb.AddComponent(container, new RegionTag { Region = RegionId.Base });
|
||||
}
|
||||
|
||||
// One-shot: remove the spawner so RequireForUpdate fails and the system idles.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee7b17ccce78a094abf6f8008ecbef36
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-only, one-shot spawner for the GLOBAL cycle-director ghost (mirrors SharedStorageSpawnSystem,
|
||||
/// but MINUS the RegionTag — the director must stay global so GhostRelevancy keeps it relevant to every
|
||||
/// region). On its first update it reads the baked <see cref="CycleDirectorSpawner"/> + NetworkTime,
|
||||
/// instantiates the ghost, initializes <see cref="CycleState"/> (Expedition, cycle 1, PhaseEndTick =
|
||||
/// now + <see cref="CyclePhase.ExpeditionTicks"/>), adds the server-only <see cref="CycleRuntime"/>, and
|
||||
/// places it at the base center (preserving the prefab's baked LocalTransform scale — FromPosition would
|
||||
/// reset the replicated Scale GhostField), then destroys the spawner so it idles.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial struct CycleDirectorSpawnSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
state.RequireForUpdate<CycleDirectorSpawner>();
|
||||
state.RequireForUpdate<NetworkTime>();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var serverTick = SystemAPI.GetSingleton<NetworkTime>().ServerTick;
|
||||
if (!serverTick.IsValid)
|
||||
return;
|
||||
uint now = serverTick.TickIndexForValidTick;
|
||||
|
||||
var spawnerEntity = SystemAPI.GetSingletonEntity<CycleDirectorSpawner>();
|
||||
var spawner = SystemAPI.GetComponent<CycleDirectorSpawner>(spawnerEntity);
|
||||
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
if (spawner.Prefab != Entity.Null)
|
||||
{
|
||||
var director = ecb.Instantiate(spawner.Prefab);
|
||||
|
||||
// Place at the base center, preserving the prefab's baked scale/rotation.
|
||||
var xform = SystemAPI.GetComponent<LocalTransform>(spawner.Prefab);
|
||||
if (SystemAPI.TryGetSingleton<BaseAnchor>(out var anchor))
|
||||
xform.Position = BaseGridMath.PlotCenter(anchor);
|
||||
ecb.SetComponent(director, xform);
|
||||
|
||||
// Override the baked CycleState with the real start tick; add server-only bookkeeping.
|
||||
ecb.SetComponent(director, new CycleState
|
||||
{
|
||||
Phase = CyclePhase.Expedition,
|
||||
CycleNumber = 1,
|
||||
PhaseEndTick = TickUtil.NonZero(now + CyclePhase.ExpeditionTicks),
|
||||
});
|
||||
ecb.AddComponent(director, new CycleRuntime { DefendStartWave = 0 });
|
||||
}
|
||||
|
||||
// One-shot: remove the spawner so RequireForUpdate fails and the system idles.
|
||||
ecb.DestroyEntity(spawnerEntity);
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bdfd3e27e8a3e924c93d6af962e0df05
|
||||
@@ -0,0 +1,93 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-authoritative macro-loop director for "The Aether Cycle": Expedition (timed) -> Defend
|
||||
/// (wave-driven) -> Build (timed) -> next cycle. Maintains the <see cref="CycleState"/> singleton and gates
|
||||
/// <see cref="WaveSystem"/> so the base-defense wave only spawns during Defend. Runs in the plain server
|
||||
/// SimulationSystemGroup (NOT prediction) before <see cref="WaveSystem"/>. All timing is wrap-safe
|
||||
/// NetworkTick math (<see cref="TickUtil.NonZero"/> + <see cref="Unity.NetCode.NetworkTick.IsNewerThan"/>),
|
||||
/// never raw uint compares. The CycleState/CycleRuntime live on the runtime-spawned CycleDirector ghost.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateInGroup(typeof(SimulationSystemGroup))]
|
||||
[UpdateBefore(typeof(WaveSystem))]
|
||||
public partial struct CyclePhaseSystem : ISystem
|
||||
{
|
||||
EntityQuery m_AliveHusks;
|
||||
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
state.RequireForUpdate<NetworkTime>();
|
||||
state.RequireForUpdate<CycleState>();
|
||||
m_AliveHusks = state.GetEntityQuery(ComponentType.ReadOnly<EnemyTag>());
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var serverTick = SystemAPI.GetSingleton<NetworkTime>().ServerTick;
|
||||
if (!serverTick.IsValid)
|
||||
return;
|
||||
uint now = serverTick.TickIndexForValidTick;
|
||||
|
||||
var cycleEntity = SystemAPI.GetSingletonEntity<CycleState>();
|
||||
|
||||
var cycle = SystemAPI.GetComponent<CycleState>(cycleEntity);
|
||||
var runtime = SystemAPI.GetComponent<CycleRuntime>(cycleEntity);
|
||||
|
||||
bool timedPhaseDue =
|
||||
cycle.PhaseEndTick != 0 && !new NetworkTick(cycle.PhaseEndTick).IsNewerThan(serverTick);
|
||||
|
||||
switch (cycle.Phase)
|
||||
{
|
||||
case CyclePhase.Expedition:
|
||||
if (timedPhaseDue)
|
||||
{
|
||||
cycle.Phase = CyclePhase.Defend;
|
||||
cycle.PhaseEndTick = 0; // Defend is wave-driven, not timed.
|
||||
runtime.DefendStartWave =
|
||||
SystemAPI.TryGetSingleton<WaveState>(out var w) ? w.WaveNumber : 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case CyclePhase.Defend:
|
||||
if (DefendCleared(ref state, runtime.DefendStartWave))
|
||||
{
|
||||
cycle.Phase = CyclePhase.Build;
|
||||
cycle.PhaseEndTick = TickUtil.NonZero(now + CyclePhase.BuildTicks);
|
||||
}
|
||||
break;
|
||||
|
||||
case CyclePhase.Build:
|
||||
if (timedPhaseDue)
|
||||
{
|
||||
cycle.Phase = CyclePhase.Expedition;
|
||||
cycle.CycleNumber += 1;
|
||||
cycle.PhaseEndTick = TickUtil.NonZero(now + CyclePhase.ExpeditionTicks);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
SystemAPI.SetComponent(cycleEntity, cycle);
|
||||
SystemAPI.SetComponent(cycleEntity, runtime);
|
||||
}
|
||||
|
||||
// The Defend wave has run for this phase (WaveNumber advanced past the captured start), is fully
|
||||
// spawned, and no Husks remain alive.
|
||||
bool DefendCleared(ref SystemState state, int defendStartWave)
|
||||
{
|
||||
if (!SystemAPI.TryGetSingleton<WaveState>(out var wave))
|
||||
return false;
|
||||
return wave.WaveNumber > defendStartWave
|
||||
&& wave.RemainingToSpawn == 0
|
||||
&& m_AliveHusks.CalculateEntityCount() == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c325c252dce9fba4a938d5c8db903042
|
||||
@@ -0,0 +1,85 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-only walk-in gate transit: a player who walks within a gate's radius (and whose region matches the
|
||||
/// gate's <see cref="ExpeditionGate.FromRegion"/>) is transited to the gate's ToRegion at its ArrivalPos
|
||||
/// (RegionTag flipped + LocalTransform teleported — GhostRelevancy re-scopes their ghosts, as in
|
||||
/// <c>RegionTransitSystem</c>). Returning to the BASE during the Expedition phase expires the Expedition
|
||||
/// timer so Defend starts early ("timer cap + early return"). Plain server SimulationSystemGroup
|
||||
/// <c>[UpdateAfter(CyclePhaseSystem)]</c>. Arrival points are offset from the destination gate so a transited
|
||||
/// player does not immediately re-trigger.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateInGroup(typeof(SimulationSystemGroup))]
|
||||
[UpdateAfter(typeof(CyclePhaseSystem))]
|
||||
public partial struct ExpeditionGateSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
state.RequireForUpdate<ExpeditionGate>();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
// Snapshot gates once.
|
||||
var gateFrom = new NativeList<byte>(Allocator.Temp);
|
||||
var gateTo = new NativeList<byte>(Allocator.Temp);
|
||||
var gateRadiusSq = new NativeList<float>(Allocator.Temp);
|
||||
var gatePos = new NativeList<float2>(Allocator.Temp);
|
||||
var gateArrival = new NativeList<float3>(Allocator.Temp);
|
||||
foreach (var (gate, xform) in SystemAPI.Query<RefRO<ExpeditionGate>, RefRO<LocalTransform>>())
|
||||
{
|
||||
gateFrom.Add(gate.ValueRO.FromRegion);
|
||||
gateTo.Add(gate.ValueRO.ToRegion);
|
||||
gateRadiusSq.Add(gate.ValueRO.Radius * gate.ValueRO.Radius);
|
||||
gatePos.Add(xform.ValueRO.Position.xz);
|
||||
gateArrival.Add(gate.ValueRO.ArrivalPos);
|
||||
}
|
||||
|
||||
bool returnedToBase = false;
|
||||
foreach (var (region, xform) in
|
||||
SystemAPI.Query<RefRW<RegionTag>, RefRW<LocalTransform>>().WithAll<PlayerTag>())
|
||||
{
|
||||
byte r = region.ValueRO.Region;
|
||||
float2 pp = xform.ValueRO.Position.xz;
|
||||
for (int i = 0; i < gateFrom.Length; i++)
|
||||
{
|
||||
if (gateFrom[i] != r) continue;
|
||||
if (math.distancesq(pp, gatePos[i]) > gateRadiusSq[i]) continue;
|
||||
region.ValueRW.Region = gateTo[i];
|
||||
xform.ValueRW.Position = gateArrival[i];
|
||||
if (gateTo[i] == RegionId.Base)
|
||||
returnedToBase = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gateFrom.Dispose();
|
||||
gateTo.Dispose();
|
||||
gateRadiusSq.Dispose();
|
||||
gatePos.Dispose();
|
||||
gateArrival.Dispose();
|
||||
|
||||
// Early return: a player came back to base mid-Expedition -> expire the Expedition timer (-> Defend).
|
||||
if (returnedToBase && SystemAPI.TryGetSingletonEntity<CycleState>(out var cycleEntity))
|
||||
{
|
||||
var cs = SystemAPI.GetComponent<CycleState>(cycleEntity);
|
||||
if (cs.Phase == CyclePhase.Expedition)
|
||||
{
|
||||
cs.PhaseEndTick = 1; // CyclePhaseSystem sees timedPhaseDue next tick -> Defend
|
||||
SystemAPI.SetComponent(cycleEntity, cs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4292536f663eb5c4d92688f6c5bb0368
|
||||
@@ -0,0 +1,69 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-authoritative per-connection ghost relevancy for the base/expedition region split. Each tick:
|
||||
/// build a connection -> region map from the region-tagged player ghosts, then mark every region-tagged
|
||||
/// ghost IRRELEVANT to each connection whose player is in a DIFFERENT region. Uses
|
||||
/// <see cref="GhostRelevancyMode.SetIsIrrelevant"/> so untagged/global ghosts (e.g. the cycle director)
|
||||
/// stay relevant to everyone for free — only cross-region ghosts are hidden. Runs in the
|
||||
/// <see cref="GhostSimulationSystemGroup"/> (before GhostSendSystem reads the set). The set holds
|
||||
/// (connection, ghost) pairs for the CURRENT simulated tick only, so it is cleared and repopulated every
|
||||
/// update. A connection with no spawned player yet is absent from the map and simply sees everything.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateInGroup(typeof(GhostSimulationSystemGroup))]
|
||||
public partial struct RegionRelevancySystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
state.RequireForUpdate<GhostRelevancy>();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
// Map each in-game connection (by NetworkId) to its player's region.
|
||||
var connRegion = new NativeHashMap<int, byte>(8, Allocator.Temp);
|
||||
foreach (var (owner, region) in
|
||||
SystemAPI.Query<RefRO<GhostOwner>, RefRO<RegionTag>>().WithAll<PlayerTag>())
|
||||
{
|
||||
connRegion[owner.ValueRO.NetworkId] = region.ValueRO.Region;
|
||||
}
|
||||
|
||||
ref var relevancy = ref SystemAPI.GetSingletonRW<GhostRelevancy>().ValueRW;
|
||||
relevancy.GhostRelevancyMode = GhostRelevancyMode.SetIsIrrelevant;
|
||||
var set = relevancy.GhostRelevancySet;
|
||||
set.Clear();
|
||||
|
||||
if (!connRegion.IsEmpty)
|
||||
{
|
||||
var conns = connRegion.GetKeyValueArrays(Allocator.Temp);
|
||||
foreach (var (ghost, region) in
|
||||
SystemAPI.Query<RefRO<GhostInstance>, RefRO<RegionTag>>())
|
||||
{
|
||||
int ghostId = ghost.ValueRO.ghostId;
|
||||
if (ghostId == 0)
|
||||
continue; // ghost id not assigned yet this tick
|
||||
|
||||
byte ghostRegion = region.ValueRO.Region;
|
||||
for (int i = 0; i < conns.Keys.Length; i++)
|
||||
{
|
||||
if (conns.Values[i] != ghostRegion)
|
||||
set.Add(new RelevantGhostForConnection { Connection = conns.Keys[i], Ghost = ghostId }, 1);
|
||||
}
|
||||
}
|
||||
conns.Dispose();
|
||||
}
|
||||
|
||||
connRegion.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db2f33e6ce7ce9346b3dfbcd5e562ed6
|
||||
@@ -0,0 +1,71 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-authoritative handler for <see cref="RegionTransitRequest"/> RPCs. Resolves the sender's player
|
||||
/// (via the source connection's <see cref="NetworkId"/> -> <see cref="GhostOwner"/>), flips its
|
||||
/// <see cref="RegionTag"/> to the requested region, and teleports it to that region's origin
|
||||
/// (<see cref="RegionMath.RegionOrigin"/>, centered on the base via <see cref="BaseGridMath.PlotCenter"/>).
|
||||
/// Runs in the default server SimulationSystemGroup (NOT the prediction loop) so the transit applies once;
|
||||
/// the next snapshot reconciles the owner-predicted client and <see cref="RegionRelevancySystem"/> re-scopes
|
||||
/// which region's ghosts the connection receives. Mirrors the <see cref="StorageOpReceiveSystem"/> RPC shape.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial struct RegionTransitSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
state.RequireForUpdate<BaseAnchor>();
|
||||
|
||||
var builder = new EntityQueryBuilder(Allocator.Temp)
|
||||
.WithAll<RegionTransitRequest, ReceiveRpcCommandRequest>();
|
||||
state.RequireForUpdate(state.GetEntityQuery(builder));
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var baseCenter = BaseGridMath.PlotCenter(SystemAPI.GetSingleton<BaseAnchor>());
|
||||
|
||||
// Map connection NetworkId -> player entity.
|
||||
var playerByConn = new NativeHashMap<int, Entity>(8, Allocator.Temp);
|
||||
foreach (var (owner, entity) in
|
||||
SystemAPI.Query<RefRO<GhostOwner>>().WithAll<PlayerTag, RegionTag>().WithEntityAccess())
|
||||
{
|
||||
playerByConn[owner.ValueRO.NetworkId] = entity;
|
||||
}
|
||||
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
foreach (var (request, receive, requestEntity) in
|
||||
SystemAPI.Query<RefRO<RegionTransitRequest>, RefRO<ReceiveRpcCommandRequest>>().WithEntityAccess())
|
||||
{
|
||||
var connEntity = receive.ValueRO.SourceConnection;
|
||||
if (SystemAPI.HasComponent<NetworkId>(connEntity))
|
||||
{
|
||||
int connId = SystemAPI.GetComponent<NetworkId>(connEntity).Value;
|
||||
if (playerByConn.TryGetValue(connId, out var player))
|
||||
{
|
||||
byte target = request.ValueRO.TargetRegion;
|
||||
SystemAPI.GetComponentRW<RegionTag>(player).ValueRW.Region = target;
|
||||
SystemAPI.GetComponentRW<LocalTransform>(player).ValueRW.Position =
|
||||
RegionMath.RegionOrigin(target, baseCenter);
|
||||
}
|
||||
}
|
||||
|
||||
ecb.DestroyEntity(requestEntity);
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
playerByConn.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44d6ce89f189d984c83cd213d86d4b02
|
||||
@@ -35,5 +35,9 @@ namespace ProjectM.Simulation
|
||||
|
||||
/// <summary>Integrated distance travelled (predicted on client + authoritative on server). Not replicated.</summary>
|
||||
public float DistanceTravelled;
|
||||
|
||||
/// <summary>This tick's travel step (Speed*dt), written by ProjectileMoveSystem so a plain-group harvest
|
||||
/// sweep is tunnelling-safe without depending on its own variable-frame clock. Server-local; not replicated.</summary>
|
||||
public float LastStep;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace ProjectM.Simulation
|
||||
.WithAll<Simulate>())
|
||||
{
|
||||
float step = projectile.ValueRO.Speed * dt;
|
||||
projectile.ValueRW.LastStep = step;
|
||||
float3 dir = new float3(projectile.ValueRO.Direction.x, 0f, projectile.ValueRO.Direction.y);
|
||||
|
||||
transform.ValueRW.Position += dir * step;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 655041f384f27064e82ddc6dec87ce86
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Tag marking the single GLOBAL shared-resource ledger — the entity whose [GhostField]
|
||||
/// <see cref="StorageEntry"/> buffer holds harvested resources (Aether/ore/biomass) replicated to ALL
|
||||
/// connections. It lives on the ownerless interpolated CycleDirector ghost, which carries NO
|
||||
/// <see cref="RegionTag"/>, so GhostRelevancy (SetIsIrrelevant) keeps it relevant to players in EVERY
|
||||
/// region — base AND expedition — unlike the region-tagged base storage container (which relevancy hides
|
||||
/// from expedition players). Server systems resolve the ledger via
|
||||
/// <c>GetSingletonEntity<ResourceLedger>()</c> then <c>GetBuffer<StorageEntry>()</c> — NEVER
|
||||
/// <c>GetSingleton<StorageEntry></c> (the base container owns a second StorageEntry buffer, so a
|
||||
/// buffer-typed singleton query would throw "multiple instances").
|
||||
/// </summary>
|
||||
public struct ResourceLedger : IComponentData { }
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78ea3121ee0b2db4992b1a2c5dd8d34b
|
||||
@@ -0,0 +1,21 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Baked singleton holding the resource-node ghost prefab + field shape. ExpeditionFieldSystem reads it to
|
||||
/// scatter <see cref="Count"/> nodes within <see cref="Radius"/> of the expedition region origin on each
|
||||
/// Expedition phase entry (seeded by the cycle number). Mirrors <see cref="StorageSpawner"/>.
|
||||
/// </summary>
|
||||
public struct ResourceFieldSpawner : IComponentData
|
||||
{
|
||||
/// <summary>Baked resource-node ghost prefab to instantiate.</summary>
|
||||
public Entity Prefab;
|
||||
|
||||
/// <summary>Number of nodes to scatter per expedition.</summary>
|
||||
public int Count;
|
||||
|
||||
/// <summary>Scatter radius (world units) around the expedition region origin.</summary>
|
||||
public float Radius;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 120cf21540ce86640b32921fa224b974
|
||||
@@ -0,0 +1,41 @@
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>Resource-type ids for harvested materials (a byte, not an enum, per the cross-assembly enum-in-Burst hazard).</summary>
|
||||
public static class ResourceId
|
||||
{
|
||||
/// <summary>Unused / empty sentinel (aligns with StorageMath's 0-itemId no-op).</summary>
|
||||
public const byte None = 0;
|
||||
|
||||
/// <summary>Magic energy — powers abilities / charging.</summary>
|
||||
public const byte Aether = 1;
|
||||
|
||||
/// <summary>Raw ore — structures / building.</summary>
|
||||
public const byte Ore = 2;
|
||||
|
||||
/// <summary>Biomass — misc / crafting.</summary>
|
||||
public const byte Biomass = 3;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A harvestable resource node in the procedural expedition field — an ownerless INTERPOLATED ghost
|
||||
/// (region-tagged Expedition) that clients see and shoot. The server-only ResourceHarvestSystem sweeps
|
||||
/// projectiles against it; each hit deposits <see cref="HarvestPerHit"/> of <see cref="ResourceId"/> into
|
||||
/// the GLOBAL resource ledger and decrements <see cref="Remaining"/>; the node despawns at <= 0.
|
||||
/// ResourceId/Remaining are [GhostField] so clients can tint by type and (later) show depletion;
|
||||
/// HarvestPerHit is baked, server-only.
|
||||
/// </summary>
|
||||
public struct ResourceNode : IComponentData
|
||||
{
|
||||
/// <summary>Which resource this node yields (see <see cref="ResourceId"/>).</summary>
|
||||
[GhostField] public byte ResourceId;
|
||||
|
||||
/// <summary>Remaining resource units; the node despawns when this reaches 0.</summary>
|
||||
[GhostField] public int Remaining;
|
||||
|
||||
/// <summary>Units yielded per projectile hit (baked; server-only).</summary>
|
||||
public float HarvestPerHit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8504e2af2e9a694d9a7dc30c61cc69a
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee67f9c921637c84b92bfae0edb3d3e9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,61 @@
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Macro-loop state for "The Aether Cycle": which phase the run is in, the cycle number, and the server
|
||||
/// tick the current (timed) phase ends. Server-authoritative, maintained by CyclePhaseSystem. Currently a
|
||||
/// server-side singleton; the [GhostField]s below are inert until it is moved onto the runtime-spawned
|
||||
/// CycleDirector ghost (when the client HUD is wired), at which point the same struct replicates unchanged.
|
||||
/// The Defend phase is NOT timed — it ends when the base-defense wave is cleared — so PhaseEndTick is only
|
||||
/// meaningful in Expedition/Build (0 during Defend).
|
||||
/// </summary>
|
||||
public struct CycleState : IComponentData
|
||||
{
|
||||
/// <summary>Current phase (see <see cref="CyclePhase"/>).</summary>
|
||||
[GhostField] public byte Phase;
|
||||
|
||||
/// <summary>1-based cycle counter (increments when a new Expedition begins).</summary>
|
||||
[GhostField] public int CycleNumber;
|
||||
|
||||
/// <summary>Server tick the current timed phase ends (Expedition/Build only; 0 in Defend).</summary>
|
||||
[GhostField] public uint PhaseEndTick;
|
||||
}
|
||||
|
||||
/// <summary>Phase constants for <see cref="CycleState.Phase"/> (a byte, not an enum, for trivial Burst/serialization).</summary>
|
||||
public static class CyclePhase
|
||||
{
|
||||
/// <summary>Out in the procedural field gathering resources (timed).</summary>
|
||||
public const byte Expedition = 0;
|
||||
|
||||
/// <summary>The base is under assault by a Husk wave (ends when the wave is cleared).</summary>
|
||||
public const byte Defend = 1;
|
||||
|
||||
/// <summary>Calm at base: spend resources to build/upgrade (timed).</summary>
|
||||
public const byte Build = 2;
|
||||
|
||||
/// <summary>Expedition phase duration in server ticks (SimulationTickRate = 60). Tunable; short for the M6 slice.</summary>
|
||||
public const uint ExpeditionTicks = 3600; // ~60s cap (early return via the gate ends it sooner)
|
||||
|
||||
/// <summary>Build phase duration in server ticks.</summary>
|
||||
public const uint BuildTicks = 1200; // ~20s
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-only bookkeeping for the cycle state machine that must NOT replicate (kept separate from the
|
||||
/// replicated <see cref="CycleState"/>). Records the wave number captured when the Defend phase began so
|
||||
/// the director can detect "this Defend's wave has now been spawned and cleared".
|
||||
/// </summary>
|
||||
public struct CycleRuntime : IComponentData
|
||||
{
|
||||
/// <summary>WaveState.WaveNumber captured at the moment the current Defend phase started.</summary>
|
||||
public int DefendStartWave;
|
||||
|
||||
/// <summary>Cycle phase from the previous tick — lets ExpeditionFieldSystem edge-detect entering/leaving Expedition.</summary>
|
||||
public byte PrevPhase;
|
||||
|
||||
/// <summary>CycleNumber the expedition field was last seeded for (compared by int equality, never tick math).</summary>
|
||||
public int LastSpawnedCycle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca714d222c4d2ed48aaaad7bbe6ec8fc
|
||||
@@ -0,0 +1,16 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Singleton baked into the gameplay subscene holding the cycle-director ghost prefab. A one-shot server
|
||||
/// system (<c>CycleDirectorSpawnSystem</c>) instantiates the prefab — the GLOBAL <see cref="CycleState"/>
|
||||
/// + shared resource-ledger ghost — exactly once, then destroys this singleton. Mirrors
|
||||
/// <see cref="StorageSpawner"/>. Carries no transform; only the prefab needs one.
|
||||
/// </summary>
|
||||
public struct CycleDirectorSpawner : IComponentData
|
||||
{
|
||||
/// <summary>Baked cycle-director ghost prefab to instantiate.</summary>
|
||||
public Entity Prefab;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0fcb1bcbea82cc419d4f134c9589619
|
||||
@@ -0,0 +1,28 @@
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// A walk-in travel gate between world regions. A baked entity (visible mesh + this component) at a fixed
|
||||
/// position; the server <c>ExpeditionGateSystem</c> transits a player who walks within <see cref="Radius"/>
|
||||
/// and whose region matches <see cref="FromRegion"/> to <see cref="ToRegion"/>, placing them at
|
||||
/// <see cref="ArrivalPos"/> (offset from the destination gate so they do not immediately re-trigger).
|
||||
/// Returning to the base during the Expedition phase also starts Defend early (the "timer cap + early
|
||||
/// return" pacing).
|
||||
/// </summary>
|
||||
public struct ExpeditionGate : IComponentData
|
||||
{
|
||||
/// <summary>Region a player must currently be in for this gate to act on them (see <see cref="RegionId"/>).</summary>
|
||||
public byte FromRegion;
|
||||
|
||||
/// <summary>Region the player is transited to.</summary>
|
||||
public byte ToRegion;
|
||||
|
||||
/// <summary>Planar (XZ) trigger radius in world units.</summary>
|
||||
public float Radius;
|
||||
|
||||
/// <summary>World position the player arrives at in the destination region.</summary>
|
||||
public float3 ArrivalPos;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed28d6b4a4f0b0844b851cecaadeb93f
|
||||
@@ -0,0 +1,49 @@
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies which world REGION an entity belongs to. M6 splits the single server world into two
|
||||
/// spatial regions at a large coordinate offset — the persistent home <see cref="RegionId.Base"/> and
|
||||
/// the procedurally-arranged <see cref="RegionId.Expedition"/> — and uses per-connection GhostRelevancy
|
||||
/// to replicate each region only to the connections whose player is currently in it. Server-side only
|
||||
/// (NOT a [GhostField]; the server makes all relevancy decisions). Added to players on spawn and to
|
||||
/// every region-scoped ghost the server spawns. Untagged ghosts are global (relevant to everyone).
|
||||
/// </summary>
|
||||
public struct RegionTag : IComponentData
|
||||
{
|
||||
/// <summary>Region id (see <see cref="RegionId"/>): 0 = base, 1 = expedition.</summary>
|
||||
public byte Region;
|
||||
}
|
||||
|
||||
/// <summary>Region ids for <see cref="RegionTag.Region"/> (a byte, not an enum, to keep server/Burst code trivial).</summary>
|
||||
public static class RegionId
|
||||
{
|
||||
/// <summary>The persistent, shared home base.</summary>
|
||||
public const byte Base = 0;
|
||||
|
||||
/// <summary>The procedural expedition field (offset far from the base on +X).</summary>
|
||||
public const byte Expedition = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deterministic mapping of a region id to its world-space origin. The base region keeps the existing
|
||||
/// home-base coordinates; the expedition region lives at a large +X offset so the two never overlap in
|
||||
/// the single shared PhysicsWorld. Pure (no RNG/wall-clock) — server-authoritative teleports and field
|
||||
/// spawners resolve region positions through here.
|
||||
/// </summary>
|
||||
public static class RegionMath
|
||||
{
|
||||
/// <summary>World-space X offset of the expedition region from the base region.</summary>
|
||||
public const float ExpeditionOffsetX = 1000f;
|
||||
|
||||
/// <summary>World-space origin of <paramref name="region"/>, given the base center (BaseGridMath.PlotCenter).</summary>
|
||||
public static float3 RegionOrigin(byte region, float3 baseCenter)
|
||||
{
|
||||
return region == RegionId.Expedition
|
||||
? baseCenter + new float3(ExpeditionOffsetX, 0f, 0f)
|
||||
: baseCenter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09694e58455f41b439cdf791c3c421d8
|
||||
@@ -0,0 +1,17 @@
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Client -> server request to move the sender's player between world regions (base <-> expedition).
|
||||
/// A one-off action, so an RPC (not a per-tick predicted input), mirroring <see cref="StorageOpRequest"/>.
|
||||
/// TargetRegion is a byte (see <see cref="RegionId"/>) to keep the generated serializer trivial. The
|
||||
/// server teleports the sender's player to the region origin and flips its <see cref="RegionTag"/>, which
|
||||
/// re-scopes GhostRelevancy so the client gains the target region's ghosts and drops the old region's.
|
||||
/// </summary>
|
||||
public struct RegionTransitRequest : IRpcCommand
|
||||
{
|
||||
/// <summary>Destination region id (see <see cref="RegionId"/>).</summary>
|
||||
public byte TargetRegion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 32c76b46284ed1845a412ca030de2499
|
||||
@@ -119,6 +119,160 @@ NavMeshSettings:
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &17637045
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 17637047}
|
||||
- component: {fileID: 17637046}
|
||||
m_Layer: 0
|
||||
m_Name: ResourceFieldSpawner
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &17637046
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 17637045}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: e9e840f8b95266140b0ac5bd4e81391b, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.ResourceFieldSpawnerAuthoring
|
||||
NodePrefab: {fileID: 3885353946372160549, guid: 8565e5eb00679fb45b8b7dac1e2ae9f3, type: 3}
|
||||
Count: 8
|
||||
Radius: 12
|
||||
--- !u!4 &17637047
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 17637045}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
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 &236770150
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 236770154}
|
||||
- component: {fileID: 236770153}
|
||||
- component: {fileID: 236770152}
|
||||
- component: {fileID: 236770151}
|
||||
m_Layer: 0
|
||||
m_Name: ReturnGate
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &236770151
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 236770150}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 22f744b59ad23834abe28fc09b661005, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.ExpeditionGateAuthoring
|
||||
From: 1
|
||||
To: 0
|
||||
Radius: 2.5
|
||||
ArrivalPos: {x: 0, y: 1, z: 0}
|
||||
--- !u!23 &236770152
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 236770150}
|
||||
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_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 175e5bfb2ad02704caa3d1753aad499d, 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: 1
|
||||
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_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!33 &236770153
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 236770150}
|
||||
m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!4 &236770154
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 236770150}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 1000, y: 1, z: 8}
|
||||
m_LocalScale: {x: 1.5, y: 2.5, z: 1.5}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &409538537
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -327,6 +481,113 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1192434514
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1192434518}
|
||||
- component: {fileID: 1192434517}
|
||||
- component: {fileID: 1192434516}
|
||||
- component: {fileID: 1192434515}
|
||||
m_Layer: 0
|
||||
m_Name: BaseGate
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1192434515
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1192434514}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 22f744b59ad23834abe28fc09b661005, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.ExpeditionGateAuthoring
|
||||
From: 0
|
||||
To: 1
|
||||
Radius: 2.5
|
||||
ArrivalPos: {x: 1000, y: 1, z: 0}
|
||||
--- !u!23 &1192434516
|
||||
MeshRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1192434514}
|
||||
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_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 2100000, guid: 175e5bfb2ad02704caa3d1753aad499d, 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: 1
|
||||
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_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!33 &1192434517
|
||||
MeshFilter:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1192434514}
|
||||
m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0}
|
||||
--- !u!4 &1192434518
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1192434514}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1, z: 8}
|
||||
m_LocalScale: {x: 1.5, y: 2.5, z: 1.5}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1301940439
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -843,6 +1104,51 @@ Transform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &2038854530
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2038854532}
|
||||
- component: {fileID: 2038854531}
|
||||
m_Layer: 0
|
||||
m_Name: CycleDirectorSpawner
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &2038854531
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2038854530}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1f4d2cb1e17d6a1429525674969dd3f0, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.CycleDirectorSpawnerAuthoring
|
||||
DirectorPrefab: {fileID: 3885353946372160549, guid: 529ca7203da40f5489e9e3040ed1fc22, type: 3}
|
||||
--- !u!4 &2038854532
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2038854530}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
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 &2143686865
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -907,3 +1213,7 @@ SceneRoots:
|
||||
- {fileID: 874705788}
|
||||
- {fileID: 1930969067}
|
||||
- {fileID: 1301940441}
|
||||
- {fileID: 2038854532}
|
||||
- {fileID: 17637047}
|
||||
- {fileID: 1192434518}
|
||||
- {fileID: 236770154}
|
||||
|
||||
@@ -126,6 +126,20 @@ The KBM/gamepad aim rework is [[DR-012_Aim_Controls_Cursor_Gamepad]] / [[2026-06
|
||||
- **A static presentation bridge must reset on play-enter.** `AimPresentation.Scheme` (mirrors `PrototypeCameraRig`/`VFXConfig` statics) needs `[UnityEngine.RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]` to reset — statics survive **fast-enter-playmode** domain reloads, and a stale value flashes the wrong cursor/reticle for the first frames (caught by the adversarial review).
|
||||
- **Cursor/reticle = client `PresentationSystemGroup` `SystemBase` (`AimReticleSystem`) that OBSERVES, never mutates.** A flat world-space ground ring (primitive quad, `Sprites/Default` with a null-guard fallback, procedural ring texture) is the aim indicator for BOTH schemes — KBM at the cursor's ground-projection point, gamepad a fixed distance ahead along replicated `PlayerFacing`. The hardware cursor is **hidden while aiming + focused** (`Application.isFocused`-gated) and restored on focus-loss / `OnDestroy`. A radial **dead-zone** (`AimMath.PlanarAimFromRay` `deadZoneRadius`) holds facing when the cursor is over the character. **The KBM ground point is re-raycast INSIDE `AimReticleSystem`** (PresentationSystemGroup runs after the follow-cam's LateUpdate), not latched from the gather (`GhostInputSystemGroup`, before the move) — latching there drifts the ring a frame behind the cursor under the moving camera. Optional camera **aim look-ahead** (`PrototypeCameraRig.AimLeadDistance`, tunable) leads the framed point toward `PlayerFacing` (not the live cursor projection, to avoid a feedback loop). Headless validation: drive `DebugInputInjectionSystem` (now stamps `Scheme`) + force `AimPresentation.Scheme`; the **real cursor / live device-switch needs a focused Game view** (the unfocused editor can't inject mouse position).
|
||||
|
||||
### Build gotchas (learned — M6 Aether Cycle core loop, 2026-06-03)
|
||||
|
||||
The M6 core-loop slice (Expedition→Defend→Build) + the base/expedition world split. See [[DR-013_M6_Aether_Cycle_Region_Split]] / [[2026-06-03_M6_Aether_Cycle_CoreLoop]]. **Stages 0–1 done; 2–4 are the continuation.**
|
||||
|
||||
- **Base/expedition split = coordinate-region + per-connection `GhostRelevancy`, NOT `SceneSystem` streaming** (supersedes DR-008's framing). One server world; the expedition lives at `base + (1000,0,0)`; a server `RegionRelevancySystem` in `GhostSimulationSystemGroup` (before `GhostSendSystem`) sets `GhostRelevancyMode.SetIsIrrelevant` and each tick marks region-tagged ghosts irrelevant to connections whose player is in a different region. **Use `SetIsIrrelevant` (not `SetIsRelevant`)** so untagged/global ghosts (the future cycle director) stay relevant to everyone for free — you only enumerate cross-region ghosts to hide. Verify the API on the installed Netcode (1.13.2) with `unity_reflect`: `GhostRelevancy` singleton has `GhostRelevancyMode` + `NativeParallelHashMap<RelevantGhostForConnection,int> GhostRelevancySet`; `RelevantGhostForConnection{ int Connection; int Ghost }` (`Connection`=`NetworkId.Value`, `Ghost`=`GhostInstance.ghostId`). `RegionTag{byte Region}` is **server-only (NOT a `[GhostField]`)** — the server makes all relevancy decisions; the client just gains/loses ghosts. Reuses the runtime-ghost spawn path verbatim (no baked ghosts → no prespawn handshake), no async-load race; co-op drop-in is free.
|
||||
- **Region transit + cycle phase use the established byte-RPC + tick-safe + server-`SimulationSystemGroup` patterns.** `RegionTransitRequest{byte TargetRegion}` (resolve sender via `ReceiveRpcCommandRequest.SourceConnection`→`NetworkId`→`GhostOwner`, flip `RegionTag`, teleport `LocalTransform`). The macro loop is a server-only `CycleState` singleton ([GhostField]-pre-annotated for the later CycleDirector ghost) driven by `CyclePhaseSystem` (`[UpdateBefore(WaveSystem)]`); it **gates `WaveSystem`** with a one-line `if (TryGetSingleton<CycleState>(out var c) && c.Phase != CyclePhase.Defend) return;`. All phase timers are wrap-safe `NetworkTick` (`TickUtil.NonZero` + `IsNewerThan`), never raw `uint <`.
|
||||
- **Editing an existing `[BurstCompile]` ISystem's SystemAPI query set on an UNFOCUSED editor can leave a STALE Burst binary** (managed assembly recompiles with shifted source-gen query indices, Burst's async recompile doesn't finish) → runtime `InvalidOperationException: "… required component type was not declared in the EntityQuery"` thrown from an *unrelated* `GetSingleton<T>` in that system. **Tell:** the Burst stack reports the *old* line number for the failing call. Same family as the M2 Burst-cache gotcha. **Workaround:** `Jobs ▸ Burst ▸ Enable Compilation` OFF for the session (verify `BurstCompiler.Options.EnableBurstCompilation==false`) — everything runs the fresh managed source-gen. **Permanent fix = restart Unity** to clear the cache, then re-enable Burst. Prefer a **focused** editor for Burst-affecting edits.
|
||||
- **Shared GLOBAL game-state (cycle phase, resource ledger, goal meter) rides an UNTAGGED ghost, never a region-tagged one** — `SetIsIrrelevant` hides a region-tagged ghost (e.g. the base storage) from players in the *other* region. The M6 resource **ledger** is a `StorageEntry` buffer on the global `CycleDirector` ghost, resolved via a distinct `ResourceLedger` tag — **never `GetSingleton<StorageEntry>`** (the base storage container owns a second `StorageEntry` buffer → "multiple instances" throw). Runtime-proven: the director stays relevant to an expedition player while the base storage despawns.
|
||||
- **A hit/area sweep that runs in the PLAIN `SimulationSystemGroup` must NOT use `SystemAPI.Time.DeltaTime`** — that group sees the variable *wall-frame* delta, not the fixed tick step, so a `cur - dir*Speed*dt` segment is wrong. Store the per-tick step on the projectile (`Projectile.LastStep`, written by `ProjectileMoveSystem` in the fixed-step predicted group) and reconstruct the swept segment as `cur - dir*LastStep` — tunnel-safe with zero dependence on the consuming system's clock. `ResourceHarvestSystem` runs `[UpdateAfter(PredictedSimulationSystemGroup)]` so it only sees projectiles that survived `ProjectileDamageSystem` (relies on the ~1000u base/expedition coordinate gap so a base shot can't reach a node). A node hit by N projectiles in one tick: deposit per hit but `ecb.DestroyEntity` **at-most-once** (destroyed-bitset + local Remaining copy — a double destroy throws at Playback); persist the decremented `[GhostField] Remaining` via `SetComponent` so depletion carries across ticks.
|
||||
- **New ghost prefab recipe (proven M6):** `manage_asset duplicate` UpgradePickup.prefab → `manage_prefabs modify_contents` (swap the authoring MonoBehaviour; **strip MeshFilter+MeshRenderer for an invisible state-holder**, keep them for a visible node). Wire the baked spawner into the gameplay subscene: `manage_scene load additive` → `set_active_scene Gameplay` → `manage_gameobject create` (+ `manage_components set_property` for the prefab ref, verify via `mcpforunity://scene/gameobject/{id}/component/...`) → `save` → `set_active_scene SampleScene` → `close_scene` (re-bakes on Play).
|
||||
- **Run an adversarial design-review Workflow (3 critics: netcode/relevancy, determinism/prediction, reuse/scope → synthesize) BEFORE coding a netcode-heavy slice** — for M6 Stage 2 it caught every one of the above pre-implementation (relevancy trap, singleton collision, dt-trap, double-destroy, lazy-create hazard).
|
||||
- **`manage_gameobject create` `component_properties` SILENTLY DROPS enum + Vector3 fields** (it set object-refs and simple scalars, but baked authoring enums/`Vector3` stayed at their C# defaults — two gates baked identical, one worked only by coincidence). **Always set those via a follow-up `manage_components set_property` (with a `properties` dict) and VERIFY through the `mcpforunity://scene/gameobject/{id}/component/{Type}` resource** (or, for a ghost, by reading the baked component in `execute_code` after Play). Same caveat applies to `manage_prefabs modify_contents` `component_properties`. Per-renderer color via `manage_material set_renderer_color` defaults to a runtime **PropertyBlock that does NOT persist into Play** — create a material asset (`manage_material create`) and `assign_material_to_renderer`, or use a prefab-stage assign, for colors that survive a domain reload.
|
||||
- **Walk-in region gates (M6 visibility pass):** a baked `ExpeditionGate{FromRegion,ToRegion,Radius,ArrivalPos}` entity (visible primitive, collider stripped so you pass through) + a server `ExpeditionGateSystem` (plain group, `[UpdateAfter(CyclePhaseSystem)]`) proximity-transits a player whose `RegionTag` matches `FromRegion` (flip RegionTag + teleport to `ArrivalPos`, offset from the destination gate so no re-trigger). Returning to base mid-Expedition expires the cycle timer → Defend ("timer cap + early return"). The expedition is a *place* = cosmetic ground/pillars in **SampleScene** at the +1000 offset (classic URP, like SyntyWorld), not the DOTS subscene; gameplay nodes/gates are the baked subscene entities.
|
||||
|
||||
## Bootstrap & worlds
|
||||
|
||||
- `ProjectM.Simulation.GameBootstrap : ClientServerBootstrap` → overrides `Initialize`, sets `AutoConnectPort = 7979` (in-editor auto-connect over IPC; set in M1 — was 0), calls `CreateDefaultClientServerWorlds()`. Entering Play Mode creates separate `ServerWorld` (`WorldFlags.GameServer`) and `ClientWorld` (`WorldFlags.GameClient`) — verified.
|
||||
|
||||
@@ -19,7 +19,7 @@ permalink: gamevault/06-roadmap/milestones
|
||||
| **M5.5 — Game feel & identity** | Bridge "tech-demo → game": the **Husk** enemy (server AI, interpolated ghost), player death/respawn, combat juice (damage numbers/VFX/SFX/camera shake), a core HUD, and a sci-fi look pass — under the new fiction ([[Identity]], sci-fi frontier colony) | ✅ Done 2026-06-02 — runtime-validated on 6.4.7: Husks spawn(6)+replicate+chase+strike; death→respawn loop; HUD (health/cooldown/threat/downed); emissive dark-sci-fi look. EditMode **74/74**. ctx7-verified APIs. **Deepened same day:** auto-target on Husks, replicated respawn-invulnerability, and a `WaveSystem` threat director (escalating waves of 3 Husk variants — Grunt/Swarmer/Brute) replacing the flat sustain — runtime-validated (wave 1→2 escalation 4→6, distinct maxHP 30/15/80). [[DR-009_GameFeel_Identity_FirstBlood]], [[2026-06-02_GameFeel_Identity]], [[2026-06-02_GameFeel_Deepening]] |
|
||||
| **— 2026-06-03 Visual & controls polish —** | Non-milestone polish layered on M5.5 (no mechanical rework): HDRP→URP art import + reusable converter; a cohesive **Synty** sci-fi colony world (cosmetic SampleScene GameObjects) + **GabrielAguiar** combat VFX; **KBM mouse-cursor aim + gamepad aim** with last-actuation device auto-switch (rides the existing `PlayerInput.Aim` ghost field). | ✅ Done 2026-06-03 — [[DR-010_Art_Import_URP_Conversion_Visual_Upgrade]], [[DR-011_Synty_World_VFX_Integration]], [[DR-012_Aim_Controls_Cursor_Gamepad]] |
|
||||
| **— 2026-06-03 Pre-M6 cleanup —** | Loose-ends pass before M6: vault roadmap reconcile, Unity-template + orphaned-material removal, rate-limited turning, console/runtime health gate. | ✅ Done 2026-06-03 — [[2026-06-03_Pre_M6_Cleanup]] |
|
||||
| **M6 — Build/placement** | Server-authoritative grid build placement via RPC | ⬜ |
|
||||
| **M6 — The Aether Cycle (core loop)** | Reframed from "grid build placement" into the first vertical slice of the **core game loop**: Expedition (gather) → Defend (wave) → Build/Charge (spend), persistent base + procedural sorties, escalating toward a goal. Build placement is now Stage 3 of this milestone. | 🚧 In progress 2026-06-03 — **Stages 0–2 done + runtime-validated** on 6.4.7: **base/expedition split via coordinate-region + `GhostRelevancy`** (player transit despawns/re-grants the other region's ghosts; server==client); a **server phase-director** (Expedition→Defend→Build→Expedition auto-cycle, cycle 1→2, Husk `WaveSystem` only in Defend, escalation 4→6); and **resources + harvest** — a **global CycleDirector ghost** carrying the replicated `CycleState` + a shared resource **ledger** (relevant in every region, unlike the base storage), a procedural **expedition field** (8 resource-node ghosts seeded per cycle, region-scoped), and a tunnel-safe **harvest** sweep depositing into the ledger; client **HUD** shows phase + resource counts. Supersedes DR-008's "split requires streaming" framing. **Stages 3–4 (build placement/turret/ability-tiers, persistence/goal) = continuation.** — [[DR-013_M6_Aether_Cycle_Region_Split]], [[2026-06-03_M6_Aether_Cycle_CoreLoop]] |
|
||||
| **M7 — Automation** | Self-running tick-based production chains (deterministic offline catch-up) | ⬜ |
|
||||
|
||||
Promote items from [[Backlog]] here when committed.
|
||||
@@ -0,0 +1,106 @@
|
||||
---
|
||||
date: 2026-06-03
|
||||
type: session
|
||||
tags: [session, m6, core-loop, netcode, ghost-relevancy, design]
|
||||
---
|
||||
|
||||
# Session 2026-06-03 — M6 "The Aether Cycle": core-loop research, plan, and Stages 0–1
|
||||
|
||||
## Goal
|
||||
|
||||
Re-align M6 from the narrow "grid build placement via RPC" into **the first vertical slice of the actual core
|
||||
game loop**. Operator brief: a magic + sci-fi premise (start weak/amnesiac, a voice toward "THEM", harvest
|
||||
magical energy to build a base + charge abilities, procedural expeditions, periodic base-defense). Research
|
||||
good directions and **determine the core game loop in this slice**.
|
||||
|
||||
## Done
|
||||
|
||||
**Research + design (`/dots-dev`).** Three-stream research: codebase foundation map (M1–M5.5), co-op
|
||||
roguelite-base-defense loop precedent (Dome Keeper, Deep Rock, Hades, Risk of Rain, Vampire Survivors, Drill
|
||||
Core…), and magic+sci-fi narrative/progression (Warframe/Control/Bastion/Noita/Returnal). Synthesised **"The
|
||||
Aether Cycle"** — a Dome-Keeper two-phase loop: **Expedition** (gather, soft incursion timer) → **Defend** (a
|
||||
wave hits the base) → **Build/Charge** (spend resources on structures *and* ability tiers, one shared economy)
|
||||
→ repeat, escalating, toward a goal meter. Four operator decisions: persistent base + sorties; separate
|
||||
base/expedition scenes; multiple resource types; "The Awakening Engine" narrative lean. Plan approved.
|
||||
|
||||
**Stage 0 — region-relevancy world split (the netcode crux; DONE + validated).**
|
||||
- `RegionTag{byte Region}` + `RegionId`/`RegionMath` (`Simulation/World/RegionComponents.cs`); transit RPC
|
||||
(`RegionTransitRequest.cs`).
|
||||
- `RegionRelevancySystem` (Server, `GhostSimulationSystemGroup`): per-connection `GhostRelevancyMode.SetIsIrrelevant`,
|
||||
hides cross-region ghosts each tick (global/untagged ghosts stay visible for free). API verified on Netcode
|
||||
1.13.2 via `unity_reflect`.
|
||||
- `RegionTransitSystem` (Server): RPC → resolve player via `SourceConnection`→`NetworkId`→`GhostOwner`, flip
|
||||
`RegionTag`, teleport to region origin (expedition = base + (1000,0,0)).
|
||||
- Tagged players (`GoInGameServerSystem`), storage (`SharedStorageSpawnSystem`), Husks (`WaveSystem`) → Base.
|
||||
- **Validated headless:** transit→Expedition teleports the player to X=1000 and **despawns the base storage
|
||||
ghost on the client** (relevancy); transit→Base **re-grants** it; server==client, console clean.
|
||||
|
||||
**Stage 1 — macro phase director + wave gating (DONE + validated).**
|
||||
- `CycleState` ([GhostField] Phase/CycleNumber/PhaseEndTick — pre-annotated for the future CycleDirector ghost)
|
||||
+ `CyclePhase` consts + `CycleRuntime` (server-only) in `Simulation/World/CycleComponents.cs`.
|
||||
- `CyclePhaseSystem` (Server, `[UpdateBefore(WaveSystem)]`): Expedition (timed) → Defend (wave-driven) → Build
|
||||
(timed) → next cycle, all wrap-safe `NetworkTick` math. Gates `WaveSystem` via a one-line `CycleState` check.
|
||||
- **Validated headless:** full **Expedition→Defend→Build→Expedition** auto-cycle; **CycleNumber 1→2**; wave
|
||||
spawns **only** in Defend (husks=0 in Expedition); **escalation across cycles 4→6 Husks**; Husks tagged Base.
|
||||
|
||||
**Stage 2 — resources + harvest + cycle replication/HUD (DONE + validated).** Adversarially design-reviewed
|
||||
first via a 3-critic + synthesis workflow (it caught real bugs pre-code: the base-storage-ledger relevancy
|
||||
trap, a `GetSingleton<StorageEntry>` "multiple instances" collision, the harvest variable-frame dt-trap, a
|
||||
node double-destroy, the CycleState lazy-create hazard). Split into **2a** (CycleDirector global ghost +
|
||||
migrate `CycleState` onto it + HUD phase readout) and **2b** (resources/nodes/harvest/ledger + HUD counts).
|
||||
- **2a:** `CycleDirector.prefab` (dup UpgradePickup, mesh stripped, no RegionTag → global) carries `CycleState`
|
||||
+ a `StorageEntry` ledger buffer + `ResourceLedger` tag; `CycleDirectorSpawnSystem` (one-shot) spawns it;
|
||||
`CyclePhaseSystem` refactored atomically (`RequireForUpdate<CycleState>`, lazy-create deleted). **Validated:**
|
||||
exactly one `CycleState`, replicates to client, HUD shows `"DEFEND CYCLE 1"`; the global director stays
|
||||
relevant to an expedition player while the base storage despawns (the global-ledger thesis proven).
|
||||
- **2b:** `ResourceId` + `ResourceNode` ghost (`ResourceNode.prefab`, RegionTag{Expedition}); `ExpeditionFieldSystem`
|
||||
edge-spawns/despawns a seeded field per cycle; `ResourceHarvestSystem` (plain group after the predicted group)
|
||||
sweeps projectiles via the new `Projectile.LastStep` (written by `ProjectileMoveSystem`) → deposits to the
|
||||
global ledger (`StorageMath` reused). **Validated headless:** 8 nodes seed in expedition (round-robin A/O/B,
|
||||
invisible to the base player by relevancy); a hit deposits 5 Aether + decrements the node; full depletion
|
||||
despawns the node; 5 same-tick hits deposit all + destroy once (double-destroy safe); **tunnelling sweep**
|
||||
catches a node 3u past the projectile (point test would miss); field despawns on leaving Expedition; HUD reads
|
||||
`"AETHER 30 ORE 5 BIO 0"`. Burst ON, console clean.
|
||||
|
||||
**Visibility + playability pass (DONE + validated, operator-requested).** The systems worked but were invisible/
|
||||
unplayable in a real session (no in-world travel, void expedition, timer-only phases). Added: **walk-in gates**
|
||||
(`ExpeditionGate` baked entity + `ExpeditionGateSystem` server proximity transit — a glowing gate at the base
|
||||
deploys you to the expedition, a return gate brings you back) with **timer cap + early return** pacing
|
||||
(Expedition cap lengthened to ~60s; returning to base via the gate expires the timer → Defend); a **visible
|
||||
expedition place** (indigo ground plane + dark pillars at the expedition region in SampleScene); **bright glowing
|
||||
resource nodes** (M_Projectile material); and a **HUD clarity pass** (color-coded phase + countdown, BASE/ON-
|
||||
EXPEDITION location + gate hint, resource counts). **Validated via screenshots + headless:** stepping into the
|
||||
base gate deploys to the expedition (camera follows, 8 glowing nodes become visible by relevancy); stepping into
|
||||
the return gate comes back to base AND starts Defend early; HUD reads phase/countdown/cycle/resources/location.
|
||||
*Tooling gotcha:* `manage_gameobject create` `component_properties` silently dropped the enum/Vector3 fields
|
||||
(both gates baked with authoring defaults — the BaseGate worked only by coincidence); fixed with
|
||||
`manage_components set_property` + verified via the `mcpforunity://scene/gameobject/{id}/component/...` resource.
|
||||
|
||||
See [[DR-013_M6_Aether_Cycle_Region_Split]] for the full architecture + validated evidence.
|
||||
|
||||
## Decisions
|
||||
|
||||
- [[DR-013_M6_Aether_Cycle_Region_Split]] — M6 = "The Aether Cycle"; base/expedition split via coordinate-region
|
||||
+ `GhostRelevancy` (supersedes DR-008's streaming framing); server-authoritative phase director gating the
|
||||
Husk wave. Region-relevancy + phase-machine implemented and runtime-validated.
|
||||
|
||||
## Open / deferred (the Stage 3–4 continuation)
|
||||
|
||||
- ✅ **Stages 2a/2b done** (see above): client HUD + `CycleState` replication on the global CycleDirector ghost;
|
||||
multi-type resources + procedural harvest field + global ledger + HUD resource counts. Optional hardening:
|
||||
extract the harvest sweep into a pure `HarvestMath` + EditMode tunnelling/reproducibility tests (currently
|
||||
runtime-validated, consistent with how `ProjectileDamageSystem`'s sweep is covered).
|
||||
- **Stage 3 — Build placement + turret + ability tiers:** `BuildPlaceRequest` RPC + occupancy + `BuildPlacementMath`
|
||||
(unit-tested) + grid-snap preview; `Turret`+`TurretFireSystem` (auto-defend, reuse projectile path);
|
||||
`AbilityUpgradeRequest` spending the ledger. (The original M6 build slice.)
|
||||
- **Stage 4 — Persistence + goal meter:** host JSON save/restore (replayed through build/upgrade paths — new
|
||||
scope vs. DR-008); `GoalProgress` ghost ticked per cycle.
|
||||
- **Fiction reconciliation:** adopt Aether/Awakening-Engine naming into [[Identity]] (operator sign-off).
|
||||
- **Burst re-enable + editor restart:** Burst is currently **disabled** for the session (workaround for a stale
|
||||
Burst-cache exception when editing `WaveSystem` on an unfocused editor — see DR-013 gotcha). Restart Unity to
|
||||
clear the cache, then re-enable `Jobs ▸ Burst ▸ Enable Compilation` to restore full performance.
|
||||
|
||||
## Next
|
||||
|
||||
Checkpoint for operator feedback on the working core-loop skeleton, then continue Stage 2 (resources + harvest)
|
||||
— the gather half of the economy — followed by build placement (Stage 3) and persistence/goal (Stage 4).
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
id: DR-013
|
||||
title: M6 — "The Aether Cycle" core loop + base/expedition region split via GhostRelevancy
|
||||
status: accepted
|
||||
date: 2026-06-03
|
||||
tags:
|
||||
- decision
|
||||
- netcode
|
||||
- ghost-relevancy
|
||||
- world-architecture
|
||||
- core-loop
|
||||
- m6
|
||||
permalink: gamevault/07-sessions/decisions/dr-013-m6-aether-cycle-region-split
|
||||
---
|
||||
|
||||
# DR-013 — M6 "The Aether Cycle" core loop + base/expedition region split
|
||||
|
||||
## Context
|
||||
|
||||
M1–M5.5 + polish delivered a systems-complete prototype (predicted player, data-driven combat, co-op,
|
||||
physics/CC, home-base grid + shared storage, escalating Husk `WaveSystem`, death/respawn, VFX/HUD, Synty
|
||||
world, KBM/gamepad aim) — but with **no game loop**. The roadmap scoped M6 narrowly as "server-authoritative
|
||||
grid build placement via RPC."
|
||||
|
||||
A `/dots-dev` research + design pass (Dome Keeper, Deep Rock, Hades, Risk of Rain, Vampire Survivors, Noita,
|
||||
Warframe/Control/Bastion) reframed M6 as **the first vertical slice of the actual core game loop** — *The
|
||||
Aether Cycle* — reusing the ~70% of loop systems that already exist. The operator chose four directions:
|
||||
**persistent base + procedural sorties** (death = respawn, not wipe); **separate base/expedition scenes**;
|
||||
**multiple resource types** (Aether/ore/biomass); narrative lean **"The Awakening Engine"** (base = recharging
|
||||
hibernation-pod, Aether restores memory+power, a guiding voice toward "THEM"). Full plan: see
|
||||
[[2026-06-03_M6_Aether_Cycle_CoreLoop]].
|
||||
|
||||
The core loop is a **Dome-Keeper two-phase rhythm**: **Expedition** (gather resources from a procedural field,
|
||||
soft incursion timer) → **Defend** (a wave assaults the base; you + built structures hold) → **Build/Charge**
|
||||
(spend resources to place/upgrade structures *and* ability tiers — one shared economy) → repeat, escalating,
|
||||
with a long-arc goal meter toward THEM. Mechanically this is pure fulfilment of the locked [[Pillars]] (action
|
||||
ARPG + co-op base + automation + *persistent base + instanced/procedural expeditions*) — **no pillar changes**;
|
||||
only the fiction skin evolves (Aether/Awakening-Engine vs. [[Identity]]'s industrial-colony/Blight framing),
|
||||
which is reconcilable and tracked as a follow-up, not a mechanical rework.
|
||||
|
||||
## Decision
|
||||
|
||||
**The milestone is staged to de-risk the netcode world-split FIRST. Stages 0–1 are implemented + runtime-validated; Stages 2–4 are scoped for the continuation.**
|
||||
|
||||
1. **Base/expedition split = ONE server world, two spatial REGIONS at a coordinate offset, scoped per-connection by `GhostRelevancy` — NOT `SceneSystem` streaming.** This **supersedes DR-008's framing** that the split requires Option-C async subscene streaming (which DR-008 deferred). `RegionTag { byte Region }` (server-only, NOT a `[GhostField]`; `RegionId.Base=0` / `Expedition=1`); `RegionMath.RegionOrigin` puts the expedition at `baseCenter + (1000,0,0)`. A server `RegionRelevancySystem` (in `GhostSimulationSystemGroup`, before `GhostSendSystem`) sets `GhostRelevancyMode.SetIsIrrelevant` and, each tick, marks every region-tagged ghost **irrelevant to each connection whose player is in a different region**. `SetIsIrrelevant` (not `SetIsRelevant`) was chosen so **untagged/global ghosts stay relevant to everyone for free** — only cross-region ghosts are hidden. Verified API shape on the installed Netcode **1.13.2** via `unity_reflect` (the 1.10 published docs were closest): `GhostRelevancy` singleton with `GhostRelevancyMode` + `NativeParallelHashMap<RelevantGhostForConnection,int> GhostRelevancySet`; `RelevantGhostForConnection { int Connection; int Ghost }` (`Connection` = `NetworkId.Value`, `Ghost` = `GhostInstance.ghostId`).
|
||||
|
||||
2. **Region transit = `RegionTransitRequest { byte TargetRegion }` `IRpcCommand`** (mirrors the `StorageOpRequest` recipe — byte code, plain blittable, applied in the plain server `SimulationSystemGroup`, NOT the predicted loop). `RegionTransitSystem` resolves the sender's player via `ReceiveRpcCommandRequest.SourceConnection` → `NetworkId` → `GhostOwner`, flips its `RegionTag`, and teleports its `LocalTransform.Position` to the region origin. Players are tagged `RegionTag{Base}` on spawn (`GoInGameServerSystem`); the shared storage ghost (`SharedStorageSpawnSystem`) and Husks (`WaveSystem`) are tagged `RegionTag{Base}`.
|
||||
|
||||
3. **Macro-loop director = a server-authoritative state machine.** `CycleState { [GhostField] byte Phase; [GhostField] int CycleNumber; [GhostField] uint PhaseEndTick }` (+ `CyclePhase` byte consts Expedition/Defend/Build) — currently a **server-only singleton**, pre-annotated with `[GhostField]`s so it drops onto the runtime-spawned CycleDirector ghost unchanged when the client HUD is wired. `CyclePhaseSystem` (plain server `SimulationSystemGroup`, `[UpdateBefore(WaveSystem)]`) advances **Expedition (timed) → Defend (wave-driven) → Build (timed) → next cycle**, all on wrap-safe `NetworkTick` math (`TickUtil.NonZero` + `IsNewerThan`, never raw `uint <`). It **gates `WaveSystem`**: a one-line early-out (`if (TryGetSingleton<CycleState>(out var c) && c.Phase != CyclePhase.Defend) return;`) so the base-defense wave only spawns during Defend. Defend ends when the wave has run for this phase (`WaveState.WaveNumber > DefendStartWave`), is fully spawned, and no Husks remain (`CycleRuntime.DefendStartWave` is server-only bookkeeping, kept off the replicated struct).
|
||||
|
||||
4. **No new asmdefs.** New code under `…/World/` in Simulation (`RegionComponents`, `RegionTransitRequest`, `CycleComponents`) and Server (`RegionRelevancySystem`, `RegionTransitSystem`, `CyclePhaseSystem`); two one-line edits each to `GoInGameServerSystem` (tag player Base) and `WaveSystem` (Defend gate + tag Husk Base). The Server asmdef already references `Unity.NetCode` → `GhostRelevancy`/`GhostInstance` reachable with no asmdef edit.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Validated at runtime on 6.4.7 (single in-editor client), headless via `execute_code`:**
|
||||
- **Stage 0 (relevancy):** player spawns `RegionTag{Base}`; sends `RegionTransitRequest{Expedition}` → server player teleports to X=1000, region flips, **the base-region storage ghost despawns on the client** (`clientStorageGhosts` 1→0); transit back → storage **re-granted** (0→1), server==client position (no desync). Clean console (only the known tick-batching warning).
|
||||
- **Stage 1 (cycle):** full loop **Expedition → Defend → Build → Expedition** auto-advances on timers; **CycleNumber increments 1→2**; the wave spawns **only** in Defend (husks=0 in Expedition); **escalation persists across cycles** (wave 1 = 4 Husks → wave 2 = 6); Husks carry `RegionTag{Base}`.
|
||||
- **Delivers the "instanced/procedural expeditions" pillar without the streaming machinery DR-008 deferred** — region-relevancy reuses the existing runtime-ghost spawn path verbatim (no baked/prespawned ghosts → no prespawn section-ack/CRC handshake), and relevancy is a per-tick server-only write with no async-load race. Co-op drop-in is trivial (a connection without a spawned player is simply absent from the map and sees everything).
|
||||
- **Foundation for Stages 2–4** (the continuation): resources + harvest (multi-type, into the generalised `StorageEntry` ledger), build placement + turret + ability tiers (the original M6 RPC), persistence (host JSON, new scope vs. DR-008), goal meter — plus wiring the deferred **client HUD + cycle-state replication** (move `CycleState` onto a runtime-spawned CycleDirector ghost; the `[GhostField]`s are already in place).
|
||||
|
||||
## Open / deferred
|
||||
|
||||
- **Client HUD + `CycleState` replication** — deferred from Stage 1; bundle with the Stage 2 HUD (resource ledger readout). Needs the CycleDirector ghost prefab (duplicate `UpgradePickup.prefab`) + a baked spawner.
|
||||
- **Stages 2–4** — resources/harvest, build placement/turret/ability-tiers, persistence/goal. See the plan in the session log.
|
||||
- **Fiction reconciliation** — adopt the Awakening-Engine/Aether naming into [[Identity]] (currently industrial-colony/Blight); operator sign-off before rewriting the Identity doc. Light-touch in M6.
|
||||
- **Disk persistence** — the persistent-base run model now requires the save/load DR-008 deferred (Stage 4 = new scope).
|
||||
- **Teleport fidelity** — transit lands the CC character near (not exactly on) the region origin (collide-and-slide settling); the real game will land players on a designated pad/ring slot.
|
||||
|
||||
## Build gotcha recorded this session
|
||||
|
||||
- **Editing an existing `[BurstCompile]` ISystem's SystemAPI query set on an UNFOCUSED editor can leave a stale Burst binary** while the managed assembly recompiles with shifted source-gen query indices → a runtime `InvalidOperationException: "… required component type was not declared in the EntityQuery"` from an *unrelated* `GetSingleton<T>` in that system (the Burst stack reports the *old* line number — the tell). Root cause: Burst's async recompile doesn't complete on an unfocused editor (same family as the [[CLAUDE]] M2 Burst-cache gotcha). **Workaround used:** disable Burst for the session (`Jobs ▸ Burst ▸ Enable Compilation`; verified `BurstCompiler.Options.EnableBurstCompilation == false`) so every system runs the fresh managed source-gen. **Permanent fix = restart Unity** (clears the Burst cache) then re-enable Burst. Prefer a **focused** editor for Burst-affecting edits.
|
||||
|
||||
Mirrors the server-authoritative + deterministic + co-op pillars from [[Pillars]]; supersedes the streaming framing in [[DR-008_M5_HomeBase_BaseLayer_Storage]]; reuses the byte-RPC + runtime-ghost + tick-safe patterns from [[DR-008_M5_HomeBase_BaseLayer_Storage]] / [[DR-009_GameFeel_Identity_FirstBlood]].
|
||||
Reference in New Issue
Block a user