Compare commits

..

6 commits

Author SHA1 Message Date
819741ba6a Merge pull request 'feature/tesla-coil-tower' (#8) from feature/tesla-coil-tower into main
Reviewed-on: #8
2026-06-25 15:57:54 -07:00
b0cf9f619e Wiring 9Player scene to register new tower and construction set. 2026-06-25 11:36:55 -07:00
1afc78d64f Code fix to make sure towers are flush with the ground
This may need to be changed later if we have any towers that have visuals that go underneath the ground. Right now that won't work, but we also don't have anything like that.
2026-06-25 11:36:31 -07:00
699ea34268 Adding Tesla Coil Tower and Arc VFX
New tower!
2026-06-25 11:35:23 -07:00
7b93137216 Feature: per-tower construction-phase build bisuals
Adding the correct pipeline for creating the different building phases for towers. Adding a default set with primitive cubes.
2026-06-25 11:33:23 -07:00
a87b221d87 Refactor: Remove TowerRegistry, replicate towers by TypeID
Removing redundant workflow and making sure there's just once source of truth for the list of available towers.
2026-06-25 11:21:43 -07:00
44 changed files with 1978 additions and 250 deletions

View file

@ -94,3 +94,8 @@ MonoBehaviour:
SourcePrefabToOverride: {fileID: 0} SourcePrefabToOverride: {fileID: 0}
SourceHashToOverride: 0 SourceHashToOverride: 0
OverridingTargetPrefab: {fileID: 0} OverridingTargetPrefab: {fileID: 0}
- Override: 0
Prefab: {fileID: 6482414459531823157, guid: 5a9e31f20d023e44dbd1c6d7dcdf4f71, type: 3}
SourcePrefabToOverride: {fileID: 0}
SourceHashToOverride: 0
OverridingTargetPrefab: {fileID: 0}

View file

@ -0,0 +1,143 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &-3745127967447924279
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
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: M_Electrical_Glow
m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _ALPHAPREMULTIPLY_ON
- _EMISSION
- _SPECULAR_SETUP
- _SURFACE_TYPE_TRANSPARENT
m_InvalidKeywords: []
m_LightmapFlags: 2
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: 3000
stringTagMap:
RenderType: Transparent
disabledShaderPasses:
- MOTIONVECTORS
- DepthOnly
- SHADOWCASTER
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: 10
- _DstBlendAlpha: 10
- _EnvironmentReflections: 1
- _GlossMapScale: 0
- _Glossiness: 0
- _GlossyReflections: 0
- _Metallic: 1
- _OcclusionStrength: 1
- _Parallax: 0.005
- _QueueOffset: 0
- _ReceiveShadows: 1
- _Smoothness: 1
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _SrcBlendAlpha: 1
- _Surface: 1
- _WorkflowMode: 0
- _XRMotionVectorsPass: 1
- _ZWrite: 0
m_Colors:
- _BaseColor: {r: 1, g: 1, b: 1, a: 1}
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0.36320752, g: 0.43061623, b: 1, a: 1}
- _SpecColor: {r: 0.1764706, g: 0.1764706, b: 0.1764706, a: 1}
m_BuildTextureStacks: []
m_AllowLocking: 1

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9ba610475fb3d4c4fa1245e15a72aee5
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,137 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &-935996532353526960
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
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: M_Metal_Bronze
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: 1
- _OcclusionStrength: 1
- _Parallax: 0.005
- _QueueOffset: 0
- _ReceiveShadows: 1
- _Smoothness: 0.677
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _SrcBlendAlpha: 1
- _Surface: 0
- _WorkflowMode: 1
- _XRMotionVectorsPass: 1
- _ZWrite: 1
m_Colors:
- _BaseColor: {r: 0.6132076, g: 0.26755944, b: 0, a: 1}
- _Color: {r: 0.6132076, g: 0.2675594, b: 0, 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

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 48c188cfe6c7b8f41865979202b09f66
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,137 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &-3766760622661600496
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
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: M_Metal_Iron
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: 1
- _OcclusionStrength: 1
- _Parallax: 0.005
- _QueueOffset: 0
- _ReceiveShadows: 1
- _Smoothness: 0.677
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _SrcBlendAlpha: 1
- _Surface: 0
- _WorkflowMode: 1
- _XRMotionVectorsPass: 1
- _ZWrite: 1
m_Colors:
- _BaseColor: {r: 1, g: 1, b: 1, a: 1}
- _Color: {r: 1, g: 1, b: 1, 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

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 43a7de81dc971be4bab78e201f3844ca
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

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

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8779a537cbf0e93409b3b547fc612f7c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 4300000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,20 @@
%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: 8c2d6e4f1a9b4c73d05e8f1a2b6c9d40, type: 3}
m_Name: Draft_NewTower_TeslaCoil
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.Draft.NewTowerDraftOption
DisplayName: Tesla Coil
Description: Add the Tesla Coil to your arsenal. Zaps all enemies in a short range
at once.
Icon: {fileID: 0}
Weight: 1
Tower: {fileID: 11400000, guid: e3f1a9c2b5d84e76a0c3f1e8d27b4905, type: 2}

View file

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

View file

@ -0,0 +1,39 @@
%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: 7b353a757b6e6774d97e6fb8ba138fcc, type: 3}
m_Name: TeslaCoilTower
m_EditorClassIdentifier: Assembly-CSharp::TD.Towers.TowerDefinition
DisplayName: Tesla Coil Tower
Description: Zaps every enemy within its short range at once with electric arcs.
Rapid, low per-hit damage; targets ground and air.
FootprintSize: {x: 2, y: 2}
GoldCost: 40
BuildTime: 0.5
TowerPrefab: {fileID: 6482414459531823157, guid: 5a9e31f20d023e44dbd1c6d7dcdf4f71, type: 3}
ConstructionPhases: {fileID: 0}
Icon: {fileID: 0}
DamageType: 6
TargetPriority: 0
TargetType: 3
GroundedOnly: 0
Damage: 5
Range: 10
FireRate: 2
SplashRadius: 0
ChainCount: 0
ChainRange: 0
SlowFactor: 0
DotDamagePerSecond: 0
EffectDuration: 0
ProjectilePrefab: {fileID: 0}
ProjectileSpeed: 0
UpgradePaths: []

View file

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

View file

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

View file

@ -298,6 +298,7 @@ MonoBehaviour:
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}
stageCount: 4 stageCount: 4
queuedYScale: 0.15 queuedYScale: 0.15
--- !u!65 &6259437393614329336 --- !u!65 &6259437393614329336

View file

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

View file

@ -0,0 +1,18 @@
%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

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

View file

@ -0,0 +1,114 @@
%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

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

View file

@ -0,0 +1,114 @@
%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

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

View file

@ -0,0 +1,114 @@
%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

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

View file

@ -0,0 +1,398 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &39352475684176306
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5753294230586596248}
- component: {fileID: 5360241685303413176}
m_Layer: 8
m_Name: SelectionVolume
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &5753294230586596248
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 39352475684176306}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0.5, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1283036264165444500}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!65 &5360241685303413176
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 39352475684176306}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 1
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 2, y: 1, z: 2}
m_Center: {x: 0, y: 0, z: 0}
--- !u!1 &5555487747732705634
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1088221722203278382}
m_Layer: 0
m_Name: EmitterLocation
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1088221722203278382
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5555487747732705634}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 1.3, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1283036264165444500}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &6482414459531823157
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1283036264165444500}
- component: {fileID: 6869333096494165105}
- component: {fileID: 7630870068340451557}
- component: {fileID: 9137031893466587143}
- component: {fileID: 805962841523123163}
- component: {fileID: 8853488620519990682}
- component: {fileID: 4524938955752569428}
m_Layer: 0
m_Name: Tower_TeslaCoil
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1283036264165444500
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6482414459531823157}
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:
- {fileID: 5753294230586596248}
- {fileID: 7547850274173754846}
- {fileID: 7158970167016586489}
- {fileID: 1088221722203278382}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &6869333096494165105
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6482414459531823157}
m_Mesh: {fileID: 4300000, guid: 8779a537cbf0e93409b3b547fc612f7c, type: 2}
--- !u!114 &7630870068340451557
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: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
GlobalObjectIdHash: 1363936954
InScenePlacedSourceGlobalObjectIdHash: 0
DeferredDespawnTick: 0
Ownership: 1
AlwaysReplicateAsRoot: 0
SynchronizeTransform: 1
ActiveSceneSynchronization: 0
SceneMigrationSynchronization: 0
SpawnWithObservers: 1
DontDestroyWithOwner: 0
AutoObjectParentSync: 1
SyncOwnerTransformWhenParented: 1
AllowOwnerToParent: 0
--- !u!114 &9137031893466587143
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: fb111fc88b3d6a340a3abde5a1502af3, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.TowerInstance
ShowTopMostFoldoutHeaderGroup: 1
tintedRenderers:
- {fileID: 0}
--- !u!114 &805962841523123163
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: 7eb6cce6cd96b23478b7d2173cebf74d, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Combat.TowerCombat
ShowTopMostFoldoutHeaderGroup: 1
enemyLayerMask:
serializedVersion: 2
m_Bits: 1024
--- !u!114 &8853488620519990682
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: c0edb0c5206ca454bbd7c300c6cc7574, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Combat.TowerRangeIndicator
rangeProjector: {fileID: 8255517343120954594}
projectionDepth: 50
--- !u!114 &4524938955752569428
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: a7c3e1f95b2d4068b1a9c4e7f2d50b83, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Combat.TeslaArcVisual
emitter: {fileID: 1088221722203278382}
arcMaterial: {fileID: 2100000, guid: 9ba610475fb3d4c4fa1245e15a72aee5, type: 2}
arcColor: {r: 0.6, g: 0.85, b: 1, a: 1}
arcWidth: 0.06
arcDuration: 0.12
segmentsPerArc: 6
jitter: 0.2
--- !u!1 &7101790219931971190
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7158970167016586489}
- component: {fileID: 8483068905520245286}
- component: {fileID: 4894823604936447762}
- component: {fileID: 3197617762217759550}
m_Layer: 0
m_Name: Mesh_TeslaTower
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7158970167016586489
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7101790219931971190}
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: 1283036264165444500}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &8483068905520245286
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7101790219931971190}
m_Mesh: {fileID: 4300000, guid: 8779a537cbf0e93409b3b547fc612f7c, type: 2}
--- !u!23 &4894823604936447762
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7101790219931971190}
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: 43a7de81dc971be4bab78e201f3844ca, type: 2}
- {fileID: 2100000, guid: 48c188cfe6c7b8f41865979202b09f66, 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 &3197617762217759550
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7101790219931971190}
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: 2, y: 1.625, z: 2}
m_Center: {x: 0, y: 0.8125, z: 0}
--- !u!1 &7580197837852108944
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7547850274173754846}
- component: {fileID: 8255517343120954594}
m_Layer: 0
m_Name: RangeIndicator
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7547850274173754846
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7580197837852108944}
serializedVersion: 2
m_LocalRotation: {x: 0.7071068, y: -0, z: -0, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1283036264165444500}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &8255517343120954594
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7580197837852108944}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0777d029ed3dffa4692f417d4aba19ca, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.DecalProjector
m_Material: {fileID: 2100000, guid: f99227cbde481ce47a2527e6bca709d2, type: 2}
m_DrawDistance: 1000
m_FadeScale: 0.9
m_StartAngleFade: 180
m_EndAngleFade: 180
m_UVScale: {x: 1, y: 1}
m_UVBias: {x: 0, y: 0}
m_RenderingLayerMask:
serializedVersion: 0
m_Bits: 1
m_ScaleMode: 0
m_Offset: {x: 0, y: 0, z: 0.5}
m_Size: {x: 1, y: 1, z: 1}
m_FadeFactor: 1
m_VisibleInScene: 1
version: 1
m_DecalLayerMask: 1

View file

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

View file

@ -19774,7 +19774,9 @@ MonoBehaviour:
- {fileID: 11400000, guid: d5b2e8fa3c7e9b5f4a1d0c2e7f6b3a91, type: 2} - {fileID: 11400000, guid: d5b2e8fa3c7e9b5f4a1d0c2e7f6b3a91, type: 2}
- {fileID: 11400000, guid: 0f693e29ca953e1439e10cb8f12e4b30, type: 2} - {fileID: 11400000, guid: 0f693e29ca953e1439e10cb8f12e4b30, type: 2}
- {fileID: 11400000, guid: 04219895bbea4fb9a94fd62fef35bf2a, type: 2} - {fileID: 11400000, guid: 04219895bbea4fb9a94fd62fef35bf2a, type: 2}
- {fileID: 11400000, guid: e3f1a9c2b5d84e76a0c3f1e8d27b4905, type: 2}
startingDeck: startingDeck:
- {fileID: 11400000, guid: d5b2e8fa3c7e9b5f4a1d0c2e7f6b3a91, type: 2}
- {fileID: 11400000, guid: 0f693e29ca953e1439e10cb8f12e4b30, type: 2} - {fileID: 11400000, guid: 0f693e29ca953e1439e10cb8f12e4b30, type: 2}
--- !u!4 &1507514109 --- !u!4 &1507514109
Transform: Transform:
@ -19861,54 +19863,6 @@ BoxCollider:
serializedVersion: 3 serializedVersion: 3
m_Size: {x: 33, y: 1, z: 17} m_Size: {x: 33, y: 1, z: 17}
m_Center: {x: 0, y: 0, z: 0} m_Center: {x: 0, y: 0, z: 0}
--- !u!1 &1538763653
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1538763654}
- component: {fileID: 1538763655}
m_Layer: 0
m_Name: TowerRegistry
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1538763654
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1538763653}
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!114 &1538763655
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1538763653}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a9dc0fbbe4422bc479ab8db7658c082b, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.TowerRegistry
definitions:
- {fileID: 11400000, guid: d5b2e8fa3c7e9b5f4a1d0c2e7f6b3a91, type: 2}
- {fileID: 11400000, guid: 0f693e29ca953e1439e10cb8f12e4b30, type: 2}
- {fileID: 11400000, guid: 04219895bbea4fb9a94fd62fef35bf2a, type: 2}
--- !u!1 &1547583984 --- !u!1 &1547583984
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -28414,7 +28368,7 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.Draft.DraftPool m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.Draft.DraftPool
options: options:
- {fileID: 11400000, guid: 6b1d3f8a2c5e4097b8a1d0c3e6f9b240, type: 2} - {fileID: 11400000, guid: 6b1d3f8a2c5e4097b8a1d0c3e6f9b240, type: 2}
- {fileID: 11400000, guid: 7c2e4a9b3d6f51a80c9b2e1d4f7a3b51, type: 2} - {fileID: 11400000, guid: f4a2b8d1c6e93f05a7b2d4c8e1f6a930, type: 2}
--- !u!4 &2139601601 --- !u!4 &2139601601
Transform: Transform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -28795,7 +28749,6 @@ SceneRoots:
- {fileID: 441239881} - {fileID: 441239881}
- {fileID: 167151709} - {fileID: 167151709}
- {fileID: 1507514109} - {fileID: 1507514109}
- {fileID: 1538763654}
- {fileID: 1597884409} - {fileID: 1597884409}
- {fileID: 1239994224} - {fileID: 1239994224}
- {fileID: 1222526238} - {fileID: 1222526238}

View file

@ -0,0 +1,118 @@
// Assets/_Project/Scripts/Combat/TeslaArcVisual.cs
using System.Collections.Generic;
using UnityEngine;
namespace TD.Combat
{
/// <summary>
/// Local-only electrical-arc visual for <see cref="TD.Core.TargetType.AllInRange"/>
/// towers (Tesla Coil). Subscribes to <see cref="TowerCombat.OnAreaFired"/> and draws a
/// jagged line from the tower's emitter point to every enemy hit, flashed for a moment.
/// </summary>
/// <remarks>
/// Pure visualization — runs on every peer from the replicated fire broadcast; nothing
/// here is networked. Arcs are pooled <see cref="LineRenderer"/>s reused across ticks.
/// </remarks>
[RequireComponent(typeof(TowerCombat))]
public class TeslaArcVisual : MonoBehaviour
{
[Tooltip("Point the arcs originate from — the central top of the coil. " +
"If unset, falls back to one unit above the tower root.")]
[SerializeField] private Transform emitter;
[Tooltip("Material for the arc lines. Assign an unlit/additive material for a glow. " +
"If unset, a fallback Sprites/Default material is created at runtime.")]
[SerializeField] private Material arcMaterial;
[SerializeField] private Color arcColor = new Color(0.6f, 0.85f, 1f, 1f);
[SerializeField] private float arcWidth = 0.06f;
[Tooltip("How long each arc flash stays visible, in seconds.")]
[SerializeField] private float arcDuration = 0.12f;
[Tooltip("Number of points per arc. More = more jagged detail.")]
[SerializeField] private int segmentsPerArc = 6;
[Tooltip("Random offset applied to interior arc points for the lightning look.")]
[SerializeField] private float jitter = 0.2f;
private TowerCombat combat;
private readonly List<LineRenderer> pool = new List<LineRenderer>();
private float hideAt = -1f;
private void Awake() => combat = GetComponent<TowerCombat>();
private void OnEnable()
{
if (combat != null) combat.OnAreaFired += HandleAreaFired;
}
private void OnDisable()
{
if (combat != null) combat.OnAreaFired -= HandleAreaFired;
}
private void Update()
{
if (hideAt >= 0f && Time.time >= hideAt)
{
for (int i = 0; i < pool.Count; i++) pool[i].enabled = false;
hideAt = -1f;
}
}
private void HandleAreaFired(Vector3[] positions)
{
if (positions == null) return;
Vector3 origin = emitter != null ? emitter.position : transform.position + Vector3.up;
for (int i = 0; i < positions.Length; i++)
{
var lr = GetLine(i);
DrawArc(lr, origin, positions[i]);
lr.enabled = true;
}
// Disable any leftover lines from a previous, larger tick.
for (int i = positions.Length; i < pool.Count; i++)
pool[i].enabled = false;
hideAt = Time.time + arcDuration;
}
private LineRenderer GetLine(int index)
{
while (pool.Count <= index)
{
var go = new GameObject($"Arc_{pool.Count}");
go.transform.SetParent(transform, worldPositionStays: false);
var lr = go.AddComponent<LineRenderer>();
lr.useWorldSpace = true;
lr.widthMultiplier = arcWidth;
lr.numCapVertices = 2;
lr.textureMode = LineTextureMode.Stretch;
lr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
lr.material = arcMaterial != null
? arcMaterial
: new Material(Shader.Find("Sprites/Default"));
lr.startColor = lr.endColor = arcColor;
lr.enabled = false;
pool.Add(lr);
}
return pool[index];
}
private void DrawArc(LineRenderer lr, Vector3 a, Vector3 b)
{
int n = Mathf.Max(2, segmentsPerArc);
lr.positionCount = n;
for (int s = 0; s < n; s++)
{
float t = s / (float)(n - 1);
Vector3 p = Vector3.Lerp(a, b, t);
// Jitter interior points only — endpoints stay anchored to the coil and enemy.
if (s != 0 && s != n - 1)
p += Random.insideUnitSphere * jitter;
lr.SetPosition(s, p);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a7c3e1f95b2d4068b1a9c4e7f2d50b83
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -90,6 +90,13 @@ namespace TD.Combat
/// </summary> /// </summary>
public event Action<Vector3> OnFire; public event Action<Vector3> OnFire;
/// <summary>
/// Fired locally on ALL peers when an <see cref="TargetType.AllInRange"/> tower
/// attacks, carrying the world positions of every enemy hit this tick. A visual
/// component (e.g. <c>TeslaArcVisual</c>) subscribes to draw an arc to each.
/// </summary>
public event Action<Vector3[]> OnAreaFired;
// ----- NGO lifecycle ----------------------------------------------- // ----- NGO lifecycle -----------------------------------------------
public override void OnNetworkSpawn() public override void OnNetworkSpawn()
@ -300,10 +307,14 @@ namespace TD.Combat
public readonly float ProjectileSpeed; public readonly float ProjectileSpeed;
public readonly int ChainCount; public readonly int ChainCount;
public readonly float ChainRange; public readonly float ChainRange;
// The tower's attack range. Carried here so the AllInRange path knows how far
// to sweep without re-reading the TowerDefinition.
public readonly float Range;
public CombatProfile(float damage, DamageType damageType, TargetType targetType, public CombatProfile(float damage, DamageType damageType, TargetType targetType,
float splashRadius, float slowFactor, float dotDamagePerSecond, float splashRadius, float slowFactor, float dotDamagePerSecond,
float effectDuration, float projectileSpeed, int chainCount, float chainRange) float effectDuration, float projectileSpeed, int chainCount, float chainRange,
float range)
{ {
Damage = damage; Damage = damage;
DamageType = damageType; DamageType = damageType;
@ -315,6 +326,7 @@ namespace TD.Combat
ProjectileSpeed = projectileSpeed; ProjectileSpeed = projectileSpeed;
ChainCount = chainCount; ChainCount = chainCount;
ChainRange = chainRange; ChainRange = chainRange;
Range = range;
} }
} }
@ -354,7 +366,8 @@ namespace TD.Combat
} }
return new CombatProfile(damage, damageType, targetType, splash, slow, dot, return new CombatProfile(damage, damageType, targetType, splash, slow, dot,
duration, def.ProjectileSpeed, def.ChainCount, def.ChainRange); duration, def.ProjectileSpeed, def.ChainCount, def.ChainRange,
def.Range);
} }
// ----- Damage application ------------------------------------------ // ----- Damage application ------------------------------------------
@ -377,9 +390,33 @@ namespace TD.Combat
case TargetType.Chain: case TargetType.Chain:
ApplyChain(p, primary, owner); ApplyChain(p, primary, owner);
break; break;
case TargetType.AllInRange:
ApplyAllInRange(p, owner);
break;
} }
} }
// Hits every enemy within the tower's range this tick (Tesla Coil). Broadcasts the
// hit positions so a visual component can draw an arc to each.
private void ApplyAllInRange(in CombatProfile p, PlayerSlot owner)
{
int count = Physics.OverlapSphereNonAlloc(
transform.position, p.Range, s_overlapBuffer, enemyLayerMask);
var hitPositions = new List<Vector3>(count);
for (int i = 0; i < count; i++)
{
var eh = s_overlapBuffer[i].GetComponent<EnemyHealth>();
if (eh == null || eh.IsDead) continue;
HitEnemy(p, eh, owner);
hitPositions.Add(eh.transform.position);
}
if (hitPositions.Count > 0)
AreaFiredClientRpc(hitPositions.ToArray());
}
private void ApplySplash(in CombatProfile p, EnemyHealth primary, private void ApplySplash(in CombatProfile p, EnemyHealth primary,
Vector3 origin, PlayerSlot owner) Vector3 origin, PlayerSlot owner)
{ {
@ -513,6 +550,14 @@ namespace TD.Combat
// and draw a line renderer through these world positions. // and draw a line renderer through these world positions.
} }
[ClientRpc]
private void AreaFiredClientRpc(Vector3[] hitPositions)
{
// Visual consumers (TeslaArcVisual) subscribe to OnAreaFired to draw an
// electrical arc from the tower's emitter to each hit enemy.
OnAreaFired?.Invoke(hitPositions);
}
// ----- NV callback (fires on all peers) ---------------------------- // ----- NV callback (fires on all peers) ----------------------------
private void HandleReplicatedTargetChanged( private void HandleReplicatedTargetChanged(

View file

@ -12,6 +12,7 @@ namespace TD.Core
Fire = 3, Fire = 3,
Poison = 4, Poison = 4,
Holy = 5, Holy = 5,
Electric = 6,
} }
/// <summary> /// <summary>
@ -40,6 +41,9 @@ namespace TD.Core
Splash = 1, Splash = 1,
/// <summary>Chains damage to up to ChainCount additional nearby enemies.</summary> /// <summary>Chains damage to up to ChainCount additional nearby enemies.</summary>
Chain = 2, Chain = 2,
/// <summary>Hits EVERY enemy within the tower's Range each attack (e.g. Tesla Coil).
/// Hitscan only — pairs with a null ProjectilePrefab.</summary>
AllInRange = 3,
} }

View file

@ -1,5 +1,4 @@
// Assets/_Project/Scripts/Gameplay/BuildSiteVisual.cs // Assets/_Project/Scripts/Gameplay/BuildSiteVisual.cs
using Unity.Collections;
using Unity.Netcode; using Unity.Netcode;
using UnityEngine; using UnityEngine;
using TD.Core; using TD.Core;
@ -63,27 +62,25 @@ 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 stages")] [Header("Construction phases")]
[Tooltip("Number of discrete growth stages while constructing. 4 matches the " + [Tooltip("Project-default construction-stage visuals used when the tower being built " +
"design doc (1/4 → 2/4 → 3/4 → 4/4 height).")] "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("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; [SerializeField] private int stageCount = 4;
[Tooltip("Y-scale applied to scaleTarget when Stage == Queued. Visually " + [Tooltip("FALLBACK ONLY: Y-scale applied to scaleTarget when Stage == Queued. " +
"distinct from any constructing height so the queued ghost reads as " + "Visually distinct from any constructing height so the queued ghost reads " +
"'intent, not progress'.")] "as 'intent, not progress'.")]
[SerializeField] private float queuedYScale = 0.15f; [SerializeField] private float queuedYScale = 0.15f;
// ----- Networked state -------------------------------------------- // ----- Networked state --------------------------------------------
// Replicated definition name so clients can resolve the source TowerDefinition
// (matches TowerInstance's pattern). Used for footprint size only — the visual
// prefab is the same regardless of tower type for now.
private readonly NetworkVariable<FixedString64Bytes> definitionName =
new NetworkVariable<FixedString64Bytes>(
default,
readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server);
// Replicated owner slot for color tinting. Mirrors TowerInstance. // Replicated owner slot for color tinting. Mirrors TowerInstance.
private readonly NetworkVariable<PlayerSlot> ownerSlot = private readonly NetworkVariable<PlayerSlot> ownerSlot =
new NetworkVariable<PlayerSlot>( new NetworkVariable<PlayerSlot>(
@ -232,7 +229,6 @@ namespace TD.Gameplay
// ----- Pre-spawn init data (server) ------------------------------- // ----- Pre-spawn init data (server) -------------------------------
private string pendingDefName;
private PlayerSlot pendingOwner = PlayerSlot.None; private PlayerSlot pendingOwner = PlayerSlot.None;
private float pendingBuildTime; private float pendingBuildTime;
private Vector2Int pendingAnchor; private Vector2Int pendingAnchor;
@ -240,6 +236,17 @@ 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;
// ----- Lifecycle -------------------------------------------------- // ----- Lifecycle --------------------------------------------------
/// <summary> /// <summary>
@ -261,7 +268,6 @@ namespace TD.Gameplay
return; return;
} }
pendingDefName = def != null ? def.name : string.Empty;
pendingOwner = owner; pendingOwner = owner;
pendingBuildTime = def != null ? def.BuildTime : 0f; pendingBuildTime = def != null ? def.BuildTime : 0f;
pendingAnchor = anchorTile; pendingAnchor = anchorTile;
@ -281,7 +287,6 @@ namespace TD.Gameplay
// message so clients see correct values on their first OnNetworkSpawn. // message so clients see correct values on their first OnNetworkSpawn.
if (IsServer && hasPendingInit) if (IsServer && hasPendingInit)
{ {
definitionName.Value = new FixedString64Bytes(pendingDefName ?? string.Empty);
ownerSlot.Value = pendingOwner; ownerSlot.Value = pendingOwner;
buildTime.Value = pendingBuildTime; buildTime.Value = pendingBuildTime;
anchor.Value = pendingAnchor; anchor.Value = pendingAnchor;
@ -290,6 +295,10 @@ 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();
// Subscribe to value changes so visual updates are reactive. // Subscribe to value changes so visual updates are reactive.
currentStage.OnValueChanged += HandleStageChanged; currentStage.OnValueChanged += HandleStageChanged;
@ -344,14 +353,25 @@ namespace TD.Gameplay
private void Update() private void Update()
{ {
// While constructing, smoothly interpolate Y-scale through the stages // While constructing, advance the visual based on server time. Runs on every
// based on server time. This runs on every peer (server + clients) so // peer so visuals stay synchronized. Paused does NOT update — the visual is
// visuals stay synchronized regardless of who's looking. // frozen at the pause point.
// Paused stage does NOT update — Y-scale is frozen at the pause point.
if (currentStage.Value != BuildStage.Constructing) return; if (currentStage.Value != BuildStage.Constructing) return;
float yScale = ComputeConstructingYScale(); if (usePhases)
ApplyYScale(yScale); {
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 -------------------------------------------------
@ -498,33 +518,117 @@ namespace TD.Gameplay
} }
private void ApplyStageVisual(BuildStage stage) private void ApplyStageVisual(BuildStage stage)
{
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)
{ {
switch (stage) switch (stage)
{ {
case BuildStage.Queued: case BuildStage.Queued:
SwapMaterial(queuedMaterial); SwapMaterial(queuedMaterial); // green ghost — no owner tint
ApplyYScale(queuedYScale);
break; break;
case BuildStage.Constructing: case BuildStage.Constructing:
SwapMaterial(constructingMaterial); SwapMaterial(constructingMaterial);
ApplyOwnerTint(); ApplyOwnerTint();
ApplyYScale(ComputeConstructingYScale());
break; break;
case BuildStage.Paused: case BuildStage.Paused:
// Use paused material if assigned; fall back to constructing
// material if not (still readable, just less distinct).
SwapMaterial(pausedMaterial != null ? pausedMaterial : constructingMaterial); SwapMaterial(pausedMaterial != null ? pausedMaterial : constructingMaterial);
ApplyOwnerTint(); ApplyOwnerTint();
// Freeze Y-scale at whatever the accumulated progress represents.
// ComputeConstructingYScale uses accumulatedConstructionTime alone
// when constructionStartServerTime is -1 (the pause sentinel).
ApplyYScale(ComputePausedYScale());
break; break;
} }
} }
// ----- 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 // Stage index 0..stageCount-1 based on elapsed server time PLUS any accumulated
// time from previous Constructing runs (resume support). // time from previous Constructing runs (resume support).
// Returned Y-scale is (stageIndex + 1) / stageCount, so stage 0 = 1/4, // Returned Y-scale is (stageIndex + 1) / stageCount, so stage 0 = 1/4,

View file

@ -68,8 +68,8 @@ namespace TD.Gameplay
"the prefab based on the player's RaceSelection.")] "the prefab based on the player's RaceSelection.")]
public GameObject BuilderPrefab; public GameObject BuilderPrefab;
[Tooltip("STUB (Phase 1.8): tower roster available to this race. TowerRegistry will " + [Tooltip("STUB: tower roster available to this race/builder. The draft pool or " +
"filter to this list when the active player belongs to this race.")] "starting deck will eventually filter to this list for this race.")]
public TowerDefinition[] Towers; public TowerDefinition[] Towers;
} }
} }

View file

@ -1,5 +1,4 @@
// Assets/_Project/Scripts/Gameplay/TowerInstance.cs // Assets/_Project/Scripts/Gameplay/TowerInstance.cs
using Unity.Collections;
using Unity.Netcode; using Unity.Netcode;
using UnityEngine; using UnityEngine;
using TD.Core; using TD.Core;
@ -32,11 +31,11 @@ namespace TD.Gameplay
/// <see cref="SetOccupied"/> are idempotent writes, so double-stamping is safe.</para> /// <see cref="SetOccupied"/> are idempotent writes, so double-stamping is safe.</para>
/// ///
/// <para><b>Definition reference replication.</b> TowerDefinition assets live in the /// <para><b>Definition reference replication.</b> TowerDefinition assets live in the
/// project on all clients. We replicate the asset by name via a /// project on all clients. We replicate the tower's <c>TowerTypeId</c> (its index in
/// <see cref="NetworkVariable{T}"/> holding a <c>FixedString64Bytes</c>, then look /// <see cref="TowerPlacementManager"/>'s catalog) via a <see cref="NetworkVariable{T}"/>,
/// up the asset locally. This avoids serializing the full ScriptableObject over the /// then resolve the full asset locally with <see cref="TowerPlacementManager.GetDefinition"/>.
/// network. The lookup uses a <see cref="TowerRegistry"/> singleton that must be /// This avoids serializing the ScriptableObject over the network and reuses the same
/// present in the scene. (Temporary: will be driven by RaceDefinition in Path E.)</para> /// identifier placement and the deck already use — a single source of truth.</para>
/// ///
/// <para><b>Combat.</b> No combat logic here yet. Combat fields live stubbed on /// <para><b>Combat.</b> No combat logic here yet. Combat fields live stubbed on
/// <see cref="TowerDefinition"/>; they will be consumed by a future /// <see cref="TowerDefinition"/>; they will be consumed by a future
@ -58,12 +57,13 @@ namespace TD.Gameplay
// ----- Networked state ------------------------------------------------ // ----- Networked state ------------------------------------------------
// The name of the TowerDefinition asset for this tower. Replicated so all // The TowerTypeId (index into TowerPlacementManager's catalog) for this tower.
// clients can look up the full definition locally without sending the whole // Replicated so every client resolves the full definition locally via the catalog —
// ScriptableObject over the wire. // the same identifier placement and the deck already use. 0 = unset/invalid (the
private readonly NetworkVariable<FixedString64Bytes> definitionName = // reserved catalog index). Replaces the old replicate-by-name + TowerRegistry path.
new NetworkVariable<FixedString64Bytes>( private readonly NetworkVariable<int> definitionTypeId =
default, new NetworkVariable<int>(
0,
readPerm: NetworkVariableReadPermission.Everyone, readPerm: NetworkVariableReadPermission.Everyone,
writePerm: NetworkVariableWritePermission.Server); writePerm: NetworkVariableWritePermission.Server);
@ -95,8 +95,8 @@ namespace TD.Gameplay
// ----- Local resolved state ------------------------------------------- // ----- Local resolved state -------------------------------------------
// Resolved on every client in OnNetworkSpawn from definitionName. // Resolved on every client in OnNetworkSpawn from definitionTypeId via the catalog.
// Null if the lookup fails (missing TowerRegistry or unknown name). // Null if the lookup fails (TypeId not in the catalog on this peer).
private TowerDefinition resolvedDefinition; private TowerDefinition resolvedDefinition;
// ----- Pre-spawn initialization data ---------------------------------- // ----- Pre-spawn initialization data ----------------------------------
@ -192,7 +192,7 @@ namespace TD.Gameplay
hasPendingInit = true; hasPendingInit = true;
// Cache the resolved definition on the server immediately — clients // Cache the resolved definition on the server immediately — clients
// will resolve via the registry lookup once definitionName arrives. // resolve from the replicated TowerTypeId via the catalog once it arrives.
resolvedDefinition = def; resolvedDefinition = def;
} }
@ -206,7 +206,17 @@ namespace TD.Gameplay
// every client sees correct values on its very first OnNetworkSpawn. // every client sees correct values on its very first OnNetworkSpawn.
if (IsServer && hasPendingInit) if (IsServer && hasPendingInit)
{ {
definitionName.Value = new FixedString64Bytes(pendingDefinition.name); // Resolve the catalog index for this definition and replicate that.
int typeId = 0;
var pm = TowerPlacementManager.Instance;
if (pm == null || !pm.TryGetTypeId(pendingDefinition, out typeId))
{
Debug.LogError($"[TowerInstance] Could not resolve a TowerTypeId for " +
$"'{pendingDefinition?.name}'. Ensure it is in the " +
$"TowerPlacementManager catalog (towerDefinitions).");
}
definitionTypeId.Value = typeId;
anchorTile.Value = pendingAnchor; anchorTile.Value = pendingAnchor;
ownerSlot.Value = pendingOwner; ownerSlot.Value = pendingOwner;
@ -331,27 +341,13 @@ namespace TD.Gameplay
// Already resolved (server path via InitializeServer). // Already resolved (server path via InitializeServer).
if (resolvedDefinition != null) return; if (resolvedDefinition != null) return;
string defName = definitionName.Value.ToString(); int typeId = definitionTypeId.Value;
if (string.IsNullOrEmpty(defName)) resolvedDefinition = TowerPlacementManager.GetDefinition(typeId);
{
Debug.LogError($"[TowerInstance] NetworkObject {NetworkObjectId}: " +
$"definitionName is empty. Cannot resolve TowerDefinition.");
return;
}
var registry = TowerRegistry.Instance;
if (registry == null)
{
Debug.LogError($"[TowerInstance] NetworkObject {NetworkObjectId}: " +
$"No TowerRegistry found in scene. Cannot resolve '{defName}'.");
return;
}
resolvedDefinition = registry.Get(defName);
if (resolvedDefinition == null) if (resolvedDefinition == null)
{ {
Debug.LogError($"[TowerInstance] NetworkObject {NetworkObjectId}: " + Debug.LogError($"[TowerInstance] NetworkObject {NetworkObjectId}: " +
$"TowerRegistry does not contain a definition named '{defName}'."); $"no TowerDefinition for TypeId {typeId}. Is the " +
$"TowerPlacementManager catalog populated on this peer?");
} }
} }

View file

@ -601,9 +601,9 @@ namespace TD.Gameplay
} }
Vector3 spawnPos = GridCoordinates.GetFootprintCenterWorld(anchor, def.FootprintSize); Vector3 spawnPos = GridCoordinates.GetFootprintCenterWorld(anchor, def.FootprintSize);
// Towers sit on the buildable plane (Y=0); raise slightly so the mesh base // Place on the buildable plane (Y=0); SeatOnGround below corrects the final Y
// sits flush rather than half-clipped. A cube of scale 1 needs +0.5 on Y. // from the mesh bounds so the tower rests flush regardless of its pivot.
spawnPos.y = 0.5f; spawnPos.y = 0f;
var go = Instantiate(def.TowerPrefab, spawnPos, Quaternion.identity); var go = Instantiate(def.TowerPrefab, spawnPos, Quaternion.identity);
var instance = go.GetComponent<TowerInstance>(); var instance = go.GetComponent<TowerInstance>();
@ -626,9 +626,32 @@ namespace TD.Gameplay
Destroy(go); Destroy(go);
return; return;
} }
// Seat the tower flush on the ground from its mesh bounds — BEFORE Spawn so the
// corrected transform is captured in the spawn sync. Handles any pivot.
SeatOnGround(go, groundY: 0f);
netObj.Spawn(destroyWithScene: true); netObj.Spawn(destroyWithScene: true);
} }
/// <summary>
/// Shifts <paramref name="go"/> vertically so the lowest point of its combined
/// renderer bounds rests at <paramref name="groundY"/>. Lets towers be authored with
/// any mesh pivot (center, base, etc.) and still sit flush on the ground — no
/// per-prefab Y tweaking. No-op if the prefab has no renderers.
/// </summary>
private static void SeatOnGround(GameObject go, float groundY)
{
var renderers = go.GetComponentsInChildren<Renderer>();
if (renderers.Length == 0) return;
Bounds b = renderers[0].bounds;
for (int i = 1; i < renderers.Length; i++)
b.Encapsulate(renderers[i].bounds);
go.transform.position += Vector3.up * (groundY - b.min.y);
}
// ----- Utility ---------------------------------------------------- // ----- Utility ----------------------------------------------------
private static bool TryGetDefinition(int typeId, out TowerDefinition def) private static bool TryGetDefinition(int typeId, out TowerDefinition def)

View file

@ -1,120 +0,0 @@
// Assets/_Project/Scripts/Gameplay/TowerRegistry.cs
using System.Collections.Generic;
using UnityEngine;
using TD.Towers;
namespace TD.Gameplay
{
/// <summary>
/// Scene singleton that holds every <see cref="TowerDefinition"/> available in the
/// current match and lets any code look one up by asset name.
/// </summary>
/// <remarks>
/// <para><b>Why this exists.</b> <see cref="TowerInstance"/> replicates a tower's
/// definition by name (a <c>FixedString64Bytes</c> over the network), then resolves
/// the full ScriptableObject locally on every client. TowerRegistry is the lookup
/// table that makes that resolution possible without hard-coding asset paths.</para>
///
/// <para><b>Registration.</b> Drag every <see cref="TowerDefinition"/> asset into the
/// <c>Definitions</c> list on this component in the inspector. Assets can live anywhere
/// in the project — no special folder required.</para>
///
/// <para><b>Path E upgrade path.</b> In Path E the registry will filter to only the
/// definitions belonging to the active match's <c>RaceDefinition</c> rosters. For now
/// all assigned assets are registered.</para>
///
/// <para><b>Plain MonoBehaviour.</b> Not a NetworkBehaviour — the registry is
/// identical on every peer (same assets, same names), so there is nothing to sync.</para>
/// </remarks>
public class TowerRegistry : MonoBehaviour
{
// ----- Singleton --------------------------------------------------
/// <summary>
/// The active TowerRegistry. Null before Awake or after the scene unloads.
/// Always null-check before use.
/// </summary>
public static TowerRegistry Instance { get; private set; }
// ----- Inspector --------------------------------------------------
[Tooltip("All TowerDefinition assets available in this match. " +
"Drag assets here from Assets/_Project/Data/TowerDefinitions/ " +
"(or wherever they live). Asset name is used as the registry key.")]
[SerializeField] private TowerDefinition[] definitions;
// ----- Internal lookup table --------------------------------------
// Keyed by TowerDefinition.name (the asset name, not DisplayName).
private readonly Dictionary<string, TowerDefinition> byName
= new Dictionary<string, TowerDefinition>();
// ----- Lifecycle --------------------------------------------------
private void Awake()
{
if (Instance != null && Instance != this)
{
Debug.LogError("[TowerRegistry] Multiple instances detected. " +
"Only one TowerRegistry should exist per scene.");
return;
}
Instance = this;
BuildLookupTable();
}
private void OnDestroy()
{
if (Instance == this) Instance = null;
}
// ----- Public API -------------------------------------------------
/// <summary>
/// Returns the <see cref="TowerDefinition"/> whose asset name equals
/// <paramref name="assetName"/>, or null if no match is found.
/// </summary>
public TowerDefinition Get(string assetName)
{
byName.TryGetValue(assetName, out var def);
return def;
}
/// <summary>
/// Returns all registered tower definitions. Enumerates the internal
/// dictionary values — do not modify the returned collection.
/// </summary>
public IEnumerable<TowerDefinition> All => byName.Values;
// ----- Private ----------------------------------------------------
private void BuildLookupTable()
{
byName.Clear();
if (definitions == null || definitions.Length == 0)
{
Debug.LogWarning("[TowerRegistry] No TowerDefinition assets assigned. " +
"Drag assets into the Definitions list on the TowerRegistry component.");
return;
}
foreach (var def in definitions)
{
if (def == null) continue;
if (byName.ContainsKey(def.name))
{
Debug.LogWarning($"[TowerRegistry] Duplicate asset name '{def.name}'. " +
$"Only the first entry will be used. Rename one of the assets.");
continue;
}
byName[def.name] = def;
}
Debug.Log($"[TowerRegistry] Registered {byName.Count} tower definition(s).");
}
}
}

View file

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

View file

@ -0,0 +1,32 @@
// Assets/_Project/Scripts/Towers/ConstructionPhaseSet.cs
using UnityEngine;
namespace TD.Towers
{
/// <summary>
/// An ordered set of construction-stage visuals a build site transitions between as a
/// tower is built — typically 3 assets (early → mid → late). A tower references its own
/// set via <see cref="TowerDefinition.ConstructionPhases"/>; build sites fall back to a
/// project-default set when a tower doesn't define one.
/// </summary>
/// <remarks>
/// The entries are <b>shape</b> prefabs only. <c>BuildSiteVisual</c> applies the
/// queued (green) / constructing / paused materials and the owner tint on top, so the
/// phase assets don't need their own build-state materials. Keep them simple
/// (single material) — the build site swaps the active phase's renderer material.
/// </remarks>
[CreateAssetMenu(fileName = "ConstructionPhaseSet", menuName = "TD/Construction Phase Set")]
public class ConstructionPhaseSet : ScriptableObject
{
[Tooltip("Ordered construction-stage prefabs. Shown in sequence as the build " +
"progresses — e.g. 3 entries = early / mid / late thirds of the build.")]
[SerializeField] private GameObject[] phases;
/// <summary>Number of construction phases in this set.</summary>
public int PhaseCount => phases != null ? phases.Length : 0;
/// <summary>The phase prefab at <paramref name="index"/>, or null if out of range.</summary>
public GameObject GetPhase(int index)
=> (phases != null && index >= 0 && index < phases.Length) ? phases[index] : null;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c8f2a4e1b9d6437052a1e8c4f7b3d069
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -68,6 +68,11 @@ namespace TD.Towers
"replace with a real mesh when art is available.")] "replace with a real mesh when art is available.")]
public GameObject TowerPrefab; public GameObject TowerPrefab;
[Tooltip("Optional per-tower construction-stage visuals shown while this tower is " +
"being built. Leave empty to use the project-default set assigned on the " +
"BuildSiteVisual prefab.")]
public ConstructionPhaseSet ConstructionPhases;
[Tooltip("Icon shown on this tower's tile in the HUD build menu. Auto-generated from " + [Tooltip("Icon shown on this tower's tile in the HUD build menu. Auto-generated from " +
"the TowerPrefab by the 'TD/Generate Tower Icons' menu item, or assign a " + "the TowerPrefab by the 'TD/Generate Tower Icons' menu item, or assign a " +
"Sprite manually. Null falls back to the grey placeholder icon.")] "Sprite manually. Null falls back to the grey placeholder icon.")]

View file

@ -135,6 +135,11 @@
"type": "UnityEngine.ProBuilder.RectSelectMode, Unity.ProBuilder, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "type": "UnityEngine.ProBuilder.RectSelectMode, Unity.ProBuilder, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
"key": "editor.dragSelectRectMode", "key": "editor.dragSelectRectMode",
"value": "{\"m_Value\":0}" "value": "{\"m_Value\":0}"
},
{
"type": "UnityEditor.ProBuilder.Actions.ExportAssetOptions, Unity.ProBuilder.Editor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
"key": "export.assetOptions",
"value": "{\"m_Value\":{\"makePrefab\":false,\"replaceOriginal\":false}}"
} }
] ]
} }

View file

@ -90,7 +90,7 @@ Unity **6.4 (6000.4.4f1)**, URP, IL2CPP, .NET Standard 2.1, Linear color space,
- **Visuals are placeholder** throughout (primitive/cone towers, sourced enemy models). See roadmap art TODOs. - **Visuals are placeholder** throughout (primitive/cone towers, sourced enemy models). See roadmap art TODOs.
- **Enemies lack idle animation support** — needs adding across all enemy prefabs. - **Enemies lack idle animation support** — needs adding across all enemy prefabs.
- **Tower footprint visuals don't fill their 2×2 space** — the cone/mesh sits centered and reads smaller than the tile area it occupies; should visually reflect the full footprint. - **Tower footprint visuals don't fill their 2×2 space** — the cone/mesh sits centered and reads smaller than the tile area it occupies; should visually reflect the full footprint.
- **Two redundant tower lists:** `TowerPlacementManager.towerDefinitions[]` (by-index catalog) and `TowerRegistry.definitions[]` (by-name, for instance resolution). Candidate for consolidation. - **Tower catalog is now a single source of truth:** `TowerPlacementManager.towerDefinitions[]` (by-index, = `TowerTypeId`). `TowerInstance` replicates that index and resolves via `TowerPlacementManager.GetDefinition`. *(The old by-name `TowerRegistry` was removed 2026-06-24 — add new towers only to `towerDefinitions` + `DraftPool`.)*
- **Catalog index 0 is a reserved sentinel** (valid `TowerTypeId`s start at 1) — easy to forget when wiring the catalog in the inspector. - **Catalog index 0 is a reserved sentinel** (valid `TowerTypeId`s start at 1) — easy to forget when wiring the catalog in the inspector.
- **Catalog-index identifiers aren't session-stable** — blocks cross-match persistence until a stable ID is added. - **Catalog-index identifiers aren't session-stable** — blocks cross-match persistence until a stable ID is added.
- **Paint system frozen**; **Race vs Builder** naming unresolved; the gold **"Buy Roll available any time"** rule is provisional and may change. - **Paint system frozen**; **Race vs Builder** naming unresolved; the gold **"Buy Roll available any time"** rule is provisional and may change.