Compare commits

..

2 commits

Author SHA1 Message Date
3febdcf021 remove old building effect vestiges 2026-07-03 14:18:01 -07:00
362b76ae75 towers fall from the sky - neat! 2026-07-03 14:05:08 -07:00
21 changed files with 401 additions and 738 deletions

View file

@ -34,11 +34,6 @@ MonoBehaviour:
SourcePrefabToOverride: {fileID: 0} SourcePrefabToOverride: {fileID: 0}
SourceHashToOverride: 0 SourceHashToOverride: 0
OverridingTargetPrefab: {fileID: 0} OverridingTargetPrefab: {fileID: 0}
- Override: 0
Prefab: {fileID: 7720770984308489338, guid: dff852699e2897b4494fcbc7f7e547d6, type: 3}
SourcePrefabToOverride: {fileID: 0}
SourceHashToOverride: 0
OverridingTargetPrefab: {fileID: 0}
- Override: 0 - Override: 0
Prefab: {fileID: 2664719039363295382, guid: dc2e4a4108e03874a8b2dab88dcc8fba, type: 3} Prefab: {fileID: 2664719039363295382, guid: dc2e4a4108e03874a8b2dab88dcc8fba, type: 3}
SourcePrefabToOverride: {fileID: 0} SourcePrefabToOverride: {fileID: 0}
@ -99,3 +94,8 @@ MonoBehaviour:
SourcePrefabToOverride: {fileID: 0} SourcePrefabToOverride: {fileID: 0}
SourceHashToOverride: 0 SourceHashToOverride: 0
OverridingTargetPrefab: {fileID: 0} OverridingTargetPrefab: {fileID: 0}
- Override: 0
Prefab: {fileID: 7720770984308489338, guid: dff852699e2897b4494fcbc7f7e547d6, type: 3}
SourcePrefabToOverride: {fileID: 0}
SourceHashToOverride: 0
OverridingTargetPrefab: {fileID: 0}

View file

@ -1,117 +1,5 @@
%YAML 1.1 %YAML 1.1
%TAG !u! tag:unity3d.com,2011: %TAG !u! tag:unity3d.com,2011:
--- !u!1 &2381228775386571742
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2082013748313019226}
- component: {fileID: 7119837420184958461}
- component: {fileID: 8833246162440246558}
- component: {fileID: 1909402251021719060}
m_Layer: 0
m_Name: Scale Target
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2082013748313019226
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2381228775386571742}
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: 1531733800731892084}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &7119837420184958461
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2381228775386571742}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &8833246162440246558
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2381228775386571742}
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: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!65 &1909402251021719060
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2381228775386571742}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}
--- !u!1 &4243270647055755528 --- !u!1 &4243270647055755528
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -250,7 +138,6 @@ Transform:
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: m_Children:
- {fileID: 2082013748313019226}
- {fileID: 1059634071631389929} - {fileID: 1059634071631389929}
- {fileID: 3705223059275757242} - {fileID: 3705223059275757242}
m_Father: {fileID: 0} m_Father: {fileID: 0}
@ -294,14 +181,10 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.BuildSiteVisual m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.BuildSiteVisual
ShowTopMostFoldoutHeaderGroup: 1 ShowTopMostFoldoutHeaderGroup: 1
tintedRenderers: [] tintedRenderers: []
scaleTarget: {fileID: 2082013748313019226}
queuedMaterial: {fileID: 2100000, guid: f8d951a6841d3f74098bb31255379774, type: 2} queuedMaterial: {fileID: 2100000, guid: f8d951a6841d3f74098bb31255379774, type: 2}
constructingMaterial: {fileID: 2100000, guid: 88f1dd7b174716645953857b38fb6948, type: 2} constructingMaterial: {fileID: 2100000, guid: 88f1dd7b174716645953857b38fb6948, type: 2}
pausedMaterial: {fileID: 0} pausedMaterial: {fileID: 0}
defaultPhaseSet: {fileID: 11400000, guid: ed951755c09d6d0479b4fd924b6915da, type: 2}
dropReticlePrefab: {fileID: 4110617398237858862, guid: f2a37509f5f2e4d4e86cdaf12f99c6c4, type: 3} dropReticlePrefab: {fileID: 4110617398237858862, guid: f2a37509f5f2e4d4e86cdaf12f99c6c4, type: 3}
stageCount: 4
queuedYScale: 0.15
--- !u!65 &6259437393614329336 --- !u!65 &6259437393614329336
BoxCollider: BoxCollider:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 82e418e7a5a804747a9a4865648b7793
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,18 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
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: c8f2a4e1b9d6437052a1e8c4f7b3d069, type: 3}
m_Name: DefaultConstructionSet
m_EditorClassIdentifier: Assembly-CSharp::TD.Towers.ConstructionPhaseSet
phases:
- {fileID: 562891720401665892, guid: e7be86c29087e99439507b508425b612, type: 3}
- {fileID: 3284400382278582069, guid: c6dabc822d0afb746adc08fb8602c2b4, type: 3}
- {fileID: 2161157853692362223, guid: d00c71c0cfc7e8744b9f8c1d48c2d256, type: 3}

View file

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: ed951755c09d6d0479b4fd924b6915da
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,114 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &562891720401665892
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5433206400477731590}
- component: {fileID: 3041202948021905482}
- component: {fileID: 2318454595080774531}
- component: {fileID: 2971339723339766249}
m_Layer: 0
m_Name: DefaultConstruction_Phase_1
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &5433206400477731590
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 562891720401665892}
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: 0.1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3041202948021905482
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 562891720401665892}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &2318454595080774531
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 562891720401665892}
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: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!65 &2971339723339766249
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 562891720401665892}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: e7be86c29087e99439507b508425b612
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,114 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3284400382278582069
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3820746952356246079}
- component: {fileID: 3350984935134180217}
- component: {fileID: 6336690892943026752}
- component: {fileID: 2003287843732108245}
m_Layer: 0
m_Name: DefaultConstruction_Phase_2
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3820746952356246079
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3284400382278582069}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1.5, y: 0.5, z: 1.5}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3350984935134180217
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3284400382278582069}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &6336690892943026752
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3284400382278582069}
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: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!65 &2003287843732108245
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3284400382278582069}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: c6dabc822d0afb746adc08fb8602c2b4
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,114 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &2161157853692362223
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6591719589271492770}
- component: {fileID: 3693505870455731207}
- component: {fileID: 2366594505425509977}
- component: {fileID: 5474707419433840976}
m_Layer: 0
m_Name: DefaultConstruction_Phase_3
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &6591719589271492770
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2161157853692362223}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1.75, y: 1, z: 1.75}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3693505870455731207
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2161157853692362223}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &2366594505425509977
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2161157853692362223}
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: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!65 &5474707419433840976
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2161157853692362223}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}

View file

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: d00c71c0cfc7e8744b9f8c1d48c2d256
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -70,6 +70,8 @@ GameObject:
- component: {fileID: 805962841523123163} - component: {fileID: 805962841523123163}
- component: {fileID: 8853488620519990682} - component: {fileID: 8853488620519990682}
- component: {fileID: 5360241685303413177} - component: {fileID: 5360241685303413177}
- component: {fileID: 5793816278935258120}
- component: {fileID: 3412005213656160288}
m_Layer: 0 m_Layer: 0
m_Name: Tower_BasicArrow m_Name: Tower_BasicArrow
m_TagString: Untagged m_TagString: Untagged
@ -212,6 +214,8 @@ MonoBehaviour:
ShowTopMostFoldoutHeaderGroup: 1 ShowTopMostFoldoutHeaderGroup: 1
tintedRenderers: tintedRenderers:
- {fileID: 4028055828417179692} - {fileID: 4028055828417179692}
dropHeight: 15
landingDuration: 0.4
--- !u!114 &805962841523123163 --- !u!114 &805962841523123163
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -258,6 +262,61 @@ MonoBehaviour:
radius: 0.7071068 radius: 0.7071068
height: 1.5 height: 1.5
angularOffsetDegrees: 45 angularOffsetDegrees: 45
--- !u!114 &5793816278935258120
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6482414459531823157}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 22d67b2aa780d786a93acfe06d1909b0, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Combat.TowerLandedSound
landedSound:
clip: {fileID: 0}
volume: 0
minPitch: 0
maxPitch: 0
--- !u!114 &3412005213656160288
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6482414459531823157}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bf337521317d9e297a652e97c666376f, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Combat.TowerLandingVisual
fallCurve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
- serializedVersion: 3
time: 1
value: 1
inSlope: 0
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
impactVfxPrefab: {fileID: 0}
impactVfxLifetime: 3
--- !u!1 &7580197837852108944 --- !u!1 &7580197837852108944
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View file

@ -69,6 +69,8 @@ GameObject:
- component: {fileID: 9137031893466587143} - component: {fileID: 9137031893466587143}
- component: {fileID: 805962841523123163} - component: {fileID: 805962841523123163}
- component: {fileID: 8853488620519990682} - component: {fileID: 8853488620519990682}
- component: {fileID: 3021373172845454683}
- component: {fileID: 4003901452716391977}
m_Layer: 0 m_Layer: 0
m_Name: Tower_SiegeCannon m_Name: Tower_SiegeCannon
m_TagString: Untagged m_TagString: Untagged
@ -183,7 +185,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
GlobalObjectIdHash: 3344551122 GlobalObjectIdHash: 2153184693
InScenePlacedSourceGlobalObjectIdHash: 0 InScenePlacedSourceGlobalObjectIdHash: 0
DeferredDespawnTick: 0 DeferredDespawnTick: 0
Ownership: 1 Ownership: 1
@ -211,6 +213,8 @@ MonoBehaviour:
ShowTopMostFoldoutHeaderGroup: 1 ShowTopMostFoldoutHeaderGroup: 1
tintedRenderers: tintedRenderers:
- {fileID: 4028055828417179692} - {fileID: 4028055828417179692}
dropHeight: 15
landingDuration: 0.4
--- !u!114 &805962841523123163 --- !u!114 &805962841523123163
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -241,6 +245,61 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::TD.Combat.TowerRangeIndicator m_EditorClassIdentifier: Assembly-CSharp::TD.Combat.TowerRangeIndicator
rangeProjector: {fileID: 8255517343120954594} rangeProjector: {fileID: 8255517343120954594}
projectionDepth: 50 projectionDepth: 50
--- !u!114 &3021373172845454683
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6482414459531823157}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 22d67b2aa780d786a93acfe06d1909b0, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Combat.TowerLandedSound
landedSound:
clip: {fileID: 0}
volume: 0
minPitch: 0
maxPitch: 0
--- !u!114 &4003901452716391977
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6482414459531823157}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bf337521317d9e297a652e97c666376f, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Combat.TowerLandingVisual
fallCurve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
- serializedVersion: 3
time: 1
value: 1
inSlope: 0
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
impactVfxPrefab: {fileID: 0}
impactVfxLifetime: 3
--- !u!1 &7580197837852108944 --- !u!1 &7580197837852108944
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View file

@ -101,6 +101,8 @@ GameObject:
- component: {fileID: 4524938955752569428} - component: {fileID: 4524938955752569428}
- component: {fileID: -2590971686077483651} - component: {fileID: -2590971686077483651}
- component: {fileID: 1288485127287735786} - component: {fileID: 1288485127287735786}
- component: {fileID: 1829137298732744432}
- component: {fileID: 8125712573731754244}
m_Layer: 0 m_Layer: 0
m_Name: Tower_TeslaCoil m_Name: Tower_TeslaCoil
m_TagString: Untagged m_TagString: Untagged
@ -147,7 +149,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
GlobalObjectIdHash: 280718094 GlobalObjectIdHash: 1363936954
InScenePlacedSourceGlobalObjectIdHash: 0 InScenePlacedSourceGlobalObjectIdHash: 0
DeferredDespawnTick: 0 DeferredDespawnTick: 0
Ownership: 1 Ownership: 1
@ -175,6 +177,8 @@ MonoBehaviour:
ShowTopMostFoldoutHeaderGroup: 1 ShowTopMostFoldoutHeaderGroup: 1
tintedRenderers: tintedRenderers:
- {fileID: 0} - {fileID: 0}
dropHeight: 15
landingDuration: 0.4
--- !u!114 &805962841523123163 --- !u!114 &805962841523123163
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -268,6 +272,61 @@ MonoBehaviour:
volume: 0.496 volume: 0.496
minPitch: 1 minPitch: 1
maxPitch: 1 maxPitch: 1
--- !u!114 &1829137298732744432
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6482414459531823157}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 22d67b2aa780d786a93acfe06d1909b0, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Combat.TowerLandedSound
landedSound:
clip: {fileID: 0}
volume: 0
minPitch: 0
maxPitch: 0
--- !u!114 &8125712573731754244
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6482414459531823157}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bf337521317d9e297a652e97c666376f, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Combat.TowerLandingVisual
fallCurve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
- serializedVersion: 3
time: 1
value: 1
inSlope: 0
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
impactVfxPrefab: {fileID: 0}
impactVfxLifetime: 3
--- !u!1 &7101790219931971190 --- !u!1 &7101790219931971190
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View file

@ -58,6 +58,12 @@ namespace TD.Combat
// is in range, the tower fires and the timer resets to 1 / AttacksPerSecond. // is in range, the tower fires and the timer resets to 1 / AttacksPerSecond.
private float attackCooldown; private float attackCooldown;
// Counts down from towerInstance.LandingDuration, set in OnNetworkSpawn. Combat is
// fully inert until it reaches zero, so a tower can't target/fire while its mesh is
// still visually falling (TowerLandingVisual). Server-only; independent of whatever
// the client-side fall visual is doing to the (possibly shared, on a host) transform.
private float landingCooldown;
// Cached on OnNetworkSpawn — avoids GetComponent every Update. // Cached on OnNetworkSpawn — avoids GetComponent every Update.
private TowerInstance towerInstance; private TowerInstance towerInstance;
@ -102,6 +108,7 @@ namespace TD.Combat
public override void OnNetworkSpawn() public override void OnNetworkSpawn()
{ {
towerInstance = GetComponent<TowerInstance>(); towerInstance = GetComponent<TowerInstance>();
landingCooldown = towerInstance.LandingDuration;
replicatedTarget.OnValueChanged += HandleReplicatedTargetChanged; replicatedTarget.OnValueChanged += HandleReplicatedTargetChanged;
// Late-joining clients receive the NV's current value in the initial // Late-joining clients receive the NV's current value in the initial
@ -128,6 +135,12 @@ namespace TD.Combat
{ {
if (!IsServer) return; if (!IsServer) return;
if (landingCooldown > 0f)
{
landingCooldown -= Time.deltaTime;
return;
}
TowerDefinition def = towerInstance?.Definition; TowerDefinition def = towerInstance?.Definition;
if (def == null || def.Range <= 0f || def.AttacksPerSecond <= 0f) return; if (def == null || def.Range <= 0f || def.AttacksPerSecond <= 0f) return;

View file

@ -0,0 +1,41 @@
// Assets/_Project/Scripts/Combat/TowerLandedSound.cs
using TD.Audio;
using TD.Gameplay;
using UnityEngine;
namespace TD.Combat
{
/// <summary>
/// Local-only audio for the tower's landing impact. Subscribes to
/// <see cref="TowerInstance.OnLandedClient"/> and plays a sound on every peer when the
/// tower's fall animation (<see cref="TowerLandingVisual"/>) finishes.
/// Attach to any tower prefab and assign its clip in the Inspector.
/// </summary>
[RequireComponent(typeof(TowerInstance))]
public class TowerLandedSound : MonoBehaviour
{
[SerializeField] private SoundConfig landedSound;
private TowerInstance towerInstance;
private void Awake()
{
towerInstance = GetComponent<TowerInstance>();
}
private void OnEnable()
{
if (towerInstance != null) towerInstance.OnLandedClient += HandleLanded;
}
private void OnDisable()
{
if (towerInstance != null) towerInstance.OnLandedClient -= HandleLanded;
}
private void HandleLanded()
{
AudioManager.Instance?.Play(landedSound.clip, AudioCategory.UI, landedSound.RandomPitch(), landedSound.volume);
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 22d67b2aa780d786a93acfe06d1909b0

View file

@ -0,0 +1,104 @@
// Assets/_Project/Scripts/Combat/TowerLandingVisual.cs
using System.Collections;
using TD.Gameplay;
using UnityEngine;
namespace TD.Combat
{
/// <summary>
/// Local-only "falls from the sky and lands" spawn visual. Subscribes to
/// <see cref="TowerInstance.OnBuiltClient"/> and, on every client (never on a
/// dedicated server, which has no camera to show it to), animates the tower's
/// transform down from <see cref="TowerInstance.DropHeight"/> above its already-correct
/// resting position over <see cref="TowerInstance.LandingDuration"/> seconds.
/// </summary>
/// <remarks>
/// <para><b>No NetworkTransform involved.</b> Tower prefabs have no NetworkTransform —
/// position is a one-shot snapshot sent at spawn. This animation is purely local per
/// peer (same pattern as <see cref="TowerBuiltSound"/>'s audio cue); each peer computes
/// an identical animation independently from the same replicated landing position and
/// the same authored <see cref="TowerInstance.DropHeight"/>/<see cref="TowerInstance.LandingDuration"/>
/// values, so no networking is required.</para>
///
/// <para><b>Safe on a host despite sharing one Transform with server combat logic.</b>
/// <see cref="TowerCombat"/> withholds all targeting/attack logic for
/// <see cref="TowerInstance.LandingDuration"/> seconds after spawn (see its
/// <c>landingCooldown</c> field), so it never reads <c>transform.position</c> while this
/// animation is moving it. The two timers start from the same value in the same call
/// (<see cref="TowerInstance.OnBuiltClient"/> fires from <c>OnNetworkSpawn</c>), so they
/// finish within a frame of each other.</para>
///
/// <para><b>Root transform, not a child wrapper.</b> Tower prefabs render their mesh
/// directly on the root GameObject (no separate "Model" child exists to offset
/// instead), so this animates <c>transform.position</c> directly. Footprint/pathing is
/// unaffected — <see cref="TowerInstance"/> stamps walkability from its replicated
/// anchor tile (grid data), not from <c>transform.position</c>.</para>
/// </remarks>
[RequireComponent(typeof(TowerInstance))]
public class TowerLandingVisual : MonoBehaviour
{
[Tooltip("Eases the fall over LandingDuration. X = normalized time, Y = normalized " +
"fraction of DropHeight already descended (0 = still at drop height, 1 = landed).")]
[SerializeField] private AnimationCurve fallCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
[Tooltip("Optional impact VFX (dust burst, etc.) instantiated at the landing point when " +
"the fall finishes. Leave empty for no VFX.")]
[SerializeField] private GameObject impactVfxPrefab;
[Tooltip("Seconds before the instantiated impact VFX is destroyed. Ignored if " +
"impactVfxPrefab is unassigned.")]
[SerializeField] private float impactVfxLifetime = 3f;
private TowerInstance towerInstance;
private void Awake()
{
towerInstance = GetComponent<TowerInstance>();
}
private void OnEnable()
{
if (towerInstance != null) towerInstance.OnBuiltClient += HandleBuilt;
}
private void OnDisable()
{
if (towerInstance != null) towerInstance.OnBuiltClient -= HandleBuilt;
}
private void HandleBuilt()
{
// Dedicated (non-host) servers have no camera/visuals and must never move the
// shared transform. TowerCombat's landing gate is fully independent of this
// animation, so skipping it here on a pure server is a harmless no-op.
if (!towerInstance.IsClient) return;
StartCoroutine(AnimateDrop());
}
private IEnumerator AnimateDrop()
{
Vector3 landedPosition = transform.position; // already correct (SeatOnGround, pre-spawn)
float dropHeight = towerInstance.DropHeight;
float duration = towerInstance.LandingDuration;
transform.position = landedPosition + Vector3.up * dropHeight;
float t = 0f;
while (t < duration)
{
t += Time.deltaTime;
float u = fallCurve.Evaluate(Mathf.Clamp01(t / duration));
transform.position = landedPosition + Vector3.up * dropHeight * (1f - u);
yield return null;
}
transform.position = landedPosition; // snap exactly, avoid float drift
if (impactVfxPrefab != null)
Destroy(Instantiate(impactVfxPrefab, landedPosition, Quaternion.identity), impactVfxLifetime);
towerInstance.ClientNotifyLanded();
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bf337521317d9e297a652e97c666376f

View file

@ -8,15 +8,16 @@ using TD.VFX;
namespace TD.Gameplay namespace TD.Gameplay
{ {
/// <summary> /// <summary>
/// Visual representation of an in-flight <see cref="BuildJob"/>: the green /// Visual representation of an in-flight <see cref="BuildJob"/>: a static, non-growing
/// queued ghost, and the staged construction animation (4 stages of growing /// site marker (color-tinted by stage) plus the ground targeting reticle
/// height for the testing cube). One NetworkObject per active job. Despawned /// (<see cref="TowerDropReticle"/>) that shrinks as construction progresses. One
/// when the job is cancelled or when the real <see cref="TowerInstance"/> /// NetworkObject per active job. Despawned when the job is cancelled or when the real
/// takes its place at construction-complete. /// <see cref="TowerInstance"/> takes its place at construction-complete, at which point
/// <c>TowerLandingVisual</c> plays the tower's fall-from-sky arrival.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para><b>Why a separate prefab from TowerInstance.</b> The build-site visual /// <para><b>Why a separate prefab from TowerInstance.</b> The build-site visual
/// has different rendering (transparent green or partial-height cube), no combat, /// has different rendering (translucent green site marker), no combat,
/// no grid-stamping (the Builder owns those state transitions), and a much /// no grid-stamping (the Builder owns those state transitions), and a much
/// shorter lifecycle. Sharing a prefab with TowerInstance would mean adding /// shorter lifecycle. Sharing a prefab with TowerInstance would mean adding
/// "am I real or a ghost" branching to every TowerInstance code path. Two /// "am I real or a ghost" branching to every TowerInstance code path. Two
@ -27,10 +28,9 @@ namespace TD.Gameplay
/// from <c>NetworkManager.ServerTime.TimeAsFloat</c>. Only the server writes; /// from <c>NetworkManager.ServerTime.TimeAsFloat</c>. Only the server writes;
/// clients read.</para> /// clients read.</para>
/// ///
/// <para><b>Visual model.</b> The prefab inspector points at a re-tinted copy /// <para><b>Visual model.</b> The site marker's material swaps by stage (queued/
/// of the tower's mesh (the same testing cube). At Stage = Queued, the visual /// constructing/paused) but never grows or changes shape — construction progress is
/// is a translucent green cube at full footprint scale but reduced Y. At /// communicated entirely by the reticle shrinking, not by the marker itself.</para>
/// Stage = Constructing, the cube grows in 4 sub-stages over BuildTime.</para>
/// ///
/// <para><b>No grid stamping here.</b> Walkability and occupancy stamps are /// <para><b>No grid stamping here.</b> Walkability and occupancy stamps are
/// driven by the Builder (queue-time stamps occupied=true / walkable=true, /// driven by the Builder (queue-time stamps occupied=true / walkable=true,
@ -48,10 +48,6 @@ namespace TD.Gameplay
"MeshRenderer on the prefab. Auto-populated from children if empty.")] "MeshRenderer on the prefab. Auto-populated from children if empty.")]
[SerializeField] private MeshRenderer[] tintedRenderers; [SerializeField] private MeshRenderer[] tintedRenderers;
[Tooltip("Transform that gets Y-scaled to represent construction progress. " +
"Typically the visual mesh's transform. The growth axis is local Y.")]
[SerializeField] private Transform scaleTarget;
[Tooltip("Material applied while the job is Queued (translucent green).")] [Tooltip("Material applied while the job is Queued (translucent green).")]
[SerializeField] private Material queuedMaterial; [SerializeField] private Material queuedMaterial;
@ -63,13 +59,6 @@ namespace TD.Gameplay
"Suggested: muted/grey-tinted variant of the constructing material.")] "Suggested: muted/grey-tinted variant of the constructing material.")]
[SerializeField] private Material pausedMaterial; [SerializeField] private Material pausedMaterial;
[Header("Construction phases")]
[Tooltip("Project-default construction-stage visuals used when the tower being built " +
"doesn't define its own ConstructionPhases. When this (or the tower's set) " +
"has phases, the build site swaps between those phase prefabs as it builds. " +
"Leave empty to fall back to the legacy cube Y-scale growth below.")]
[SerializeField] private ConstructionPhaseSet defaultPhaseSet;
[Header("Drop reticle FX")] [Header("Drop reticle FX")]
[Tooltip("Optional targeting-reticle effect (ground decal + beam + motes) shown for the " + [Tooltip("Optional targeting-reticle effect (ground decal + beam + motes) shown for the " +
"life of this build. Spawned as a child on every peer and fed construction " + "life of this build. Spawned as a child on every peer and fed construction " +
@ -77,16 +66,6 @@ namespace TD.Gameplay
"this visual at completion/cancel. Leave empty to build with no reticle.")] "this visual at completion/cancel. Leave empty to build with no reticle.")]
[SerializeField] private TowerDropReticle dropReticlePrefab; [SerializeField] private TowerDropReticle dropReticlePrefab;
[Header("Construction stages (cube fallback)")]
[Tooltip("FALLBACK ONLY (no phase set assigned): number of discrete growth stages " +
"while constructing. 4 = 1/4 → 2/4 → 3/4 → 4/4 height.")]
[SerializeField] private int stageCount = 4;
[Tooltip("FALLBACK ONLY: Y-scale applied to scaleTarget when Stage == Queued. " +
"Visually distinct from any constructing height so the queued ghost reads " +
"as 'intent, not progress'.")]
[SerializeField] private float queuedYScale = 0.15f;
// ----- Networked state -------------------------------------------- // ----- Networked state --------------------------------------------
// Replicated owner slot for color tinting. Mirrors TowerInstance. // Replicated owner slot for color tinting. Mirrors TowerInstance.
@ -133,7 +112,7 @@ namespace TD.Gameplay
readPerm: NetworkVariableReadPermission.Everyone, readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server); writePerm: NetworkVariableWritePermission.Server);
// Current stage. Drives material swap and Y-scale animation. // Current stage. Drives the site marker's material/tint swap.
private readonly NetworkVariable<BuildStage> currentStage = private readonly NetworkVariable<BuildStage> currentStage =
new NetworkVariable<BuildStage>( new NetworkVariable<BuildStage>(
BuildStage.Queued, BuildStage.Queued,
@ -244,17 +223,6 @@ namespace TD.Gameplay
private int pendingGoldSpent; private int pendingGoldSpent;
private bool hasPendingInit; private bool hasPendingInit;
// ----- Construction-phase runtime (local, all peers) --------------
// Resolved on spawn from the tower's ConstructionPhases or the defaultPhaseSet.
// When non-null with PhaseCount > 0, the build site swaps between these
// instantiated phase prefabs instead of Y-scaling the fallback cube.
private ConstructionPhaseSet effectivePhaseSet;
private GameObject[] phaseInstances; // one instantiated child per phase
private MeshRenderer[][] phaseRenderers; // cached renderers per phase, for tinting
private int activePhaseIndex = -1;
private bool usePhases;
// Spawned child targeting-reticle effect (local, all peers). Fed progress in Update; // Spawned child targeting-reticle effect (local, all peers). Fed progress in Update;
// destroyed automatically with this NetworkObject at completion/cancel. // destroyed automatically with this NetworkObject at completion/cancel.
private TowerDropReticle dropReticleInstance; private TowerDropReticle dropReticleInstance;
@ -307,10 +275,6 @@ namespace TD.Gameplay
hasPendingInit = false; hasPendingInit = false;
} }
// Resolve and instantiate the construction-phase visuals (or fall back to the
// cube). Depends on towerTypeId, which is replicated by now on every peer.
ResolvePhaseSet();
// Spawn the targeting-reticle FX as a local child. It reads this build site's // Spawn the targeting-reticle FX as a local child. It reads this build site's
// replicated progress (fed in Update) and dies with us at completion/cancel. // replicated progress (fed in Update) and dies with us at completion/cancel.
if (dropReticlePrefab != null) if (dropReticlePrefab != null)
@ -324,7 +288,7 @@ namespace TD.Gameplay
currentStage.OnValueChanged += HandleStageChanged; currentStage.OnValueChanged += HandleStageChanged;
// Apply initial visual state based on the (now-replicated) values. // Apply initial visual state based on the (now-replicated) values.
ApplyStageVisual(currentStage.Value); ApplyStageMaterialAndTint(currentStage.Value);
} }
public override void OnNetworkDespawn() public override void OnNetworkDespawn()
@ -376,30 +340,10 @@ namespace TD.Gameplay
{ {
// Drive the targeting reticle on every peer, every stage: 0 while Queued // Drive the targeting reticle on every peer, every stage: 0 while Queued
// (full-size reticle, awaiting a builder), shrinking while Constructing, frozen // (full-size reticle, awaiting a builder), shrinking while Constructing, frozen
// while Paused. Kept before the Constructing early-return so it also animates // while Paused. This is the only per-frame visual effect on the build site —
// the queued/paused states. // nothing about the site's mesh grows or changes shape during construction.
if (dropReticleInstance != null) if (dropReticleInstance != null)
dropReticleInstance.SetProgress(ComputeProgressNormalized()); dropReticleInstance.SetProgress(ComputeProgressNormalized());
// While constructing, advance the visual based on server time. Runs on every
// peer so visuals stay synchronized. Paused does NOT update — the visual is
// frozen at the pause point.
if (currentStage.Value != BuildStage.Constructing) return;
if (usePhases)
{
int index = PhaseIndexFromProgress();
if (index != activePhaseIndex)
{
ShowPhase(index);
// Renderers changed with the swap — re-apply material + owner tint.
ApplyStageMaterialAndTint(BuildStage.Constructing);
}
return;
}
// Fallback: smoothly grow the cube's Y-scale through the stages.
ApplyYScale(ComputeConstructingYScale());
} }
// ----- Server API ------------------------------------------------- // ----- Server API -------------------------------------------------
@ -522,9 +466,9 @@ namespace TD.Gameplay
/// <summary> /// <summary>
/// Server-only: transitions Constructing → Paused AND writes the new /// Server-only: transitions Constructing → Paused AND writes the new
/// accumulated construction time. Y-scale freezes at the level matching /// accumulated construction time (freezing the reticle's shrink progress at that
/// the accumulated time. After this returns, the visual is fully self- /// point). After this returns, the visual is fully self-describing — it carries
/// describing — it carries enough state to be shelved and later resumed. /// enough state to be shelved and later resumed.
/// </summary> /// </summary>
public void ServerPauseAndPersistAccumulated(float totalAccumulated) public void ServerPauseAndPersistAccumulated(float totalAccumulated)
{ {
@ -542,37 +486,11 @@ namespace TD.Gameplay
private void HandleStageChanged(BuildStage previous, BuildStage current) private void HandleStageChanged(BuildStage previous, BuildStage current)
{ {
ApplyStageVisual(current); ApplyStageMaterialAndTint(current);
} }
private void ApplyStageVisual(BuildStage stage) // Applies the stage's material + owner tint to the site marker's renderers.
{ // Paused falls back to the constructing material if no paused material is assigned.
if (usePhases)
{
// Queued shows the first phase; constructing/paused show the phase
// matching current progress. ShowPhase repoints tintedRenderers at the
// active phase instance, so material + tint are applied AFTER it.
int index = stage == BuildStage.Queued ? 0 : PhaseIndexFromProgress();
ShowPhase(index);
ApplyStageMaterialAndTint(stage);
return;
}
// Fallback: legacy cube Y-scale growth (no phase set wired).
ApplyStageMaterialAndTint(stage);
switch (stage)
{
case BuildStage.Queued: ApplyYScale(queuedYScale); break;
case BuildStage.Constructing: ApplyYScale(ComputeConstructingYScale()); break;
// ComputePausedYScale uses accumulatedConstructionTime alone when
// constructionStartServerTime is -1 (the pause sentinel).
case BuildStage.Paused: ApplyYScale(ComputePausedYScale()); break;
}
}
// Applies the stage's material + owner tint to whatever renderers are currently
// active — the cube fallback, or the active phase instance's renderers. Paused
// falls back to the constructing material if no paused material is assigned.
private void ApplyStageMaterialAndTint(BuildStage stage) private void ApplyStageMaterialAndTint(BuildStage stage)
{ {
switch (stage) switch (stage)
@ -591,115 +509,6 @@ namespace TD.Gameplay
} }
} }
// ----- Construction phases ----------------------------------------
// Resolves the effective phase set (tower's own, else the project default) and
// instantiates one inactive child per phase. Sets usePhases=false (cube fallback)
// if neither set has any phases. Runs on every peer in OnNetworkSpawn.
private void ResolvePhaseSet()
{
var def = TowerPlacementManager.GetDefinition(towerTypeId.Value);
effectivePhaseSet =
(def != null && def.ConstructionPhases != null && def.ConstructionPhases.PhaseCount > 0)
? def.ConstructionPhases
: defaultPhaseSet;
int n = effectivePhaseSet != null ? effectivePhaseSet.PhaseCount : 0;
if (n <= 0)
{
usePhases = false;
return;
}
usePhases = true;
phaseInstances = new GameObject[n];
phaseRenderers = new MeshRenderer[n][];
// Hide the legacy fallback cube — the instantiated phase prefabs replace it.
if (scaleTarget != null) scaleTarget.gameObject.SetActive(false);
for (int i = 0; i < n; i++)
{
var prefab = effectivePhaseSet.GetPhase(i);
if (prefab == null) continue;
var go = Instantiate(prefab, transform);
go.transform.localPosition = Vector3.zero;
go.transform.localRotation = Quaternion.identity;
go.SetActive(false);
phaseInstances[i] = go;
phaseRenderers[i] = go.GetComponentsInChildren<MeshRenderer>(true);
}
}
// Activates only phase <paramref name="index"/> and points tintedRenderers at its
// renderers so the existing material/tint helpers drive the active phase.
private void ShowPhase(int index)
{
if (phaseInstances == null) return;
index = Mathf.Clamp(index, 0, phaseInstances.Length - 1);
for (int i = 0; i < phaseInstances.Length; i++)
if (phaseInstances[i] != null)
phaseInstances[i].SetActive(i == index);
activePhaseIndex = index;
tintedRenderers = phaseRenderers[index];
}
// Maps current normalized progress to a phase index (0..PhaseCount-1).
private int PhaseIndexFromProgress()
{
int n = effectivePhaseSet != null ? effectivePhaseSet.PhaseCount : 1;
if (n <= 1) return 0;
float p = ComputeProgressNormalized();
return Mathf.Clamp(Mathf.FloorToInt(p * n), 0, n - 1);
}
// Stage index 0..stageCount-1 based on elapsed server time PLUS any accumulated
// time from previous Constructing runs (resume support).
// Returned Y-scale is (stageIndex + 1) / stageCount, so stage 0 = 1/4,
// stage 1 = 2/4, ..., stage stageCount-1 = 4/4 = full height.
private float ComputeConstructingYScale()
{
float bt = buildTime.Value;
if (bt <= 0f || stageCount <= 0) return 1f;
float currentRunElapsed = (float)NetworkManager.Singleton.ServerTime.Time
- constructionStartServerTime.Value;
float total = currentRunElapsed + accumulatedConstructionTime.Value;
float perStage = bt / stageCount;
int stageIndex = Mathf.Clamp(
Mathf.FloorToInt(total / perStage),
0, stageCount - 1);
return (stageIndex + 1f) / stageCount;
}
// While Paused, only the accumulated time matters (no current run is in flight).
private float ComputePausedYScale()
{
float bt = buildTime.Value;
if (bt <= 0f || stageCount <= 0) return 1f;
float total = accumulatedConstructionTime.Value;
float perStage = bt / stageCount;
int stageIndex = Mathf.Clamp(
Mathf.FloorToInt(total / perStage),
0, stageCount - 1);
return (stageIndex + 1f) / stageCount;
}
private void ApplyYScale(float y)
{
if (scaleTarget == null) return;
Vector3 s = scaleTarget.localScale;
scaleTarget.localScale = new Vector3(s.x, y, s.z);
}
// ----- Material handling ------------------------------------------ // ----- Material handling ------------------------------------------
private void SwapMaterial(Material mat) private void SwapMaterial(Material mat)

View file

@ -55,6 +55,18 @@ namespace TD.Gameplay
"to exclude specific children.")] "to exclude specific children.")]
[SerializeField] private MeshRenderer[] tintedRenderers; [SerializeField] private MeshRenderer[] tintedRenderers;
[Header("Landing (post-construction drop)")]
[Tooltip("World-unit height the tower visually drops from when construction completes. " +
"Consumed only by the client-side fall visual (TowerLandingVisual).")]
[SerializeField] private float dropHeight = 15f;
[Tooltip("Seconds the fall-and-land animation takes. Single source of truth: " +
"TowerCombat withholds targeting/attacks for exactly this long " +
"(server-authoritative), and TowerLandingVisual uses the same value to time " +
"its client-side animation, so the two stay in lockstep without duplicating " +
"the number.")]
[SerializeField] private float landingDuration = 0.4f;
// ----- Networked state ------------------------------------------------ // ----- Networked state ------------------------------------------------
// The TowerTypeId (index into TowerPlacementManager's catalog) for this tower. // The TowerTypeId (index into TowerPlacementManager's catalog) for this tower.
@ -132,6 +144,13 @@ namespace TD.Gameplay
/// <summary>The footprint anchor tile (SW corner, world-tile coords).</summary> /// <summary>The footprint anchor tile (SW corner, world-tile coords).</summary>
public Vector2Int AnchorTile => anchorTile.Value; public Vector2Int AnchorTile => anchorTile.Value;
/// <summary>World-unit height the post-construction drop animation falls from.</summary>
public float DropHeight => dropHeight;
/// <summary>Seconds the drop/land animation takes. Also how long TowerCombat withholds
/// targeting/attacks after construction completes.</summary>
public float LandingDuration => landingDuration;
// ----- ISelectable ---------------------------------------------------- // ----- ISelectable ----------------------------------------------------
// Absolute world-unit margin that the selection ring extends beyond the // Absolute world-unit margin that the selection ring extends beyond the
@ -171,6 +190,16 @@ namespace TD.Gameplay
/// <summary>Fired on ALL peers when this tower finishes construction and spawns.</summary> /// <summary>Fired on ALL peers when this tower finishes construction and spawns.</summary>
public event System.Action OnBuiltClient; public event System.Action OnBuiltClient;
/// <summary>Fired locally on this peer when the client-side fall/land animation
/// finishes (see <see cref="TD.Combat.TowerLandingVisual"/>). Analogous to
/// <see cref="OnBuiltClient"/>, but delayed by <see cref="LandingDuration"/>.</summary>
public event System.Action OnLandedClient;
/// <summary>Called by <see cref="TD.Combat.TowerLandingVisual"/> when its local
/// fall/land animation finishes. Purely local — never invoked on a dedicated
/// (non-client) server, which never runs that animation.</summary>
public void ClientNotifyLanded() => OnLandedClient?.Invoke();
// ----- Server-only initialization ------------------------------------- // ----- Server-only initialization -------------------------------------
/// <summary> /// <summary>