diff --git a/.claude/worktrees/dazzling-hoover-3d45a2/.claude/settings.local.json b/.claude/worktrees/dazzling-hoover-3d45a2/.claude/settings.local.json
new file mode 100644
index 0000000..e0e244a
--- /dev/null
+++ b/.claude/worktrees/dazzling-hoover-3d45a2/.claude/settings.local.json
@@ -0,0 +1,7 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(grep -E \"\\\\.\\(prefab|fbx|controller\\)$\")"
+ ]
+ }
+}
diff --git a/Assets/_Project/Animators.meta b/Assets/_Project/Animators.meta
new file mode 100644
index 0000000..7b5e900
--- /dev/null
+++ b/Assets/_Project/Animators.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ab59c25d640184f4c85024d3cd9d1e0f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/_Project/Animators/Builder.meta b/Assets/_Project/Animators/Builder.meta
new file mode 100644
index 0000000..5bd571e
--- /dev/null
+++ b/Assets/_Project/Animators/Builder.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2a8267d2a3aed83408da66e894e5ba50
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/_Project/Animators/Builder/BuilderAnimator.controller b/Assets/_Project/Animators/Builder/BuilderAnimator.controller
new file mode 100644
index 0000000..98624a2
--- /dev/null
+++ b/Assets/_Project/Animators/Builder/BuilderAnimator.controller
@@ -0,0 +1,272 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!91 &9100000
+AnimatorController:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name: BuilderAnimator
+ serializedVersion: 5
+ m_AnimatorParameters:
+ - m_Name: IsMoving
+ m_Type: 4
+ m_DefaultFloat: 0
+ m_DefaultInt: 0
+ m_DefaultBool: 0
+ m_Controller: {fileID: 9100000}
+ - m_Name: IsConstructing
+ m_Type: 4
+ m_DefaultFloat: 0
+ m_DefaultInt: 0
+ m_DefaultBool: 0
+ m_Controller: {fileID: 9100000}
+ m_AnimatorLayers:
+ - serializedVersion: 5
+ m_Name: Base Layer
+ m_StateMachine: {fileID: 110700001}
+ m_Mask: {fileID: 0}
+ m_Motions: []
+ m_Behaviours: []
+ m_BlendingMode: 0
+ m_SyncedLayerIndex: -1
+ m_DefaultWeight: 0
+ m_IKPass: 0
+ m_SyncedLayerAffectsTiming: 0
+ m_Controller: {fileID: 9100000}
+--- !u!1107 &110700001
+AnimatorStateMachine:
+ serializedVersion: 6
+ m_ObjectHideFlags: 1
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name: Base Layer
+ m_ChildStates:
+ - serializedVersion: 1
+ m_State: {fileID: 110200001}
+ m_Position: {x: 200, y: 0, z: 0}
+ - serializedVersion: 1
+ m_State: {fileID: 110200002}
+ m_Position: {x: 200, y: 100, z: 0}
+ - serializedVersion: 1
+ m_State: {fileID: 110200003}
+ m_Position: {x: 200, y: 200, z: 0}
+ m_ChildStateMachines: []
+ m_AnyStateTransitions: []
+ m_EntryTransitions: []
+ m_StateMachineTransitions: {}
+ m_StateMachineBehaviours: []
+ m_AnyStatePosition: {x: 50, y: 20, z: 0}
+ m_EntryPosition: {x: 50, y: 120, z: 0}
+ m_ExitPosition: {x: 800, y: 120, z: 0}
+ m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
+ m_DefaultState: {fileID: 110200001}
+--- !u!1102 &110200001
+AnimatorState:
+ serializedVersion: 6
+ m_ObjectHideFlags: 1
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name: Idle
+ m_Speed: 1
+ m_CycleOffset: 0
+ m_Transitions:
+ - {fileID: 110100001}
+ - {fileID: 110100003}
+ m_StateMachineBehaviours: []
+ m_Position: {x: 50, y: 50, z: 0}
+ m_IKOnFeet: 0
+ m_WriteDefaultValues: 1
+ m_Mirror: 0
+ m_SpeedParameterActive: 0
+ m_MirrorParameterActive: 0
+ m_CycleOffsetParameterActive: 0
+ m_TimeParameterActive: 0
+ m_Motion: {fileID: -2576967968662016515, guid: 56fd86b76fc74d24d83522069f5deb9b, type: 3}
+ m_Tag:
+ m_SpeedParameter:
+ m_MirrorParameter:
+ m_CycleOffsetParameter:
+ m_TimeParameter:
+--- !u!1102 &110200002
+AnimatorState:
+ serializedVersion: 6
+ m_ObjectHideFlags: 1
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name: Run
+ m_Speed: 1
+ m_CycleOffset: 0
+ m_Transitions:
+ - {fileID: 110100002}
+ - {fileID: 110100004}
+ m_StateMachineBehaviours: []
+ m_Position: {x: 50, y: 50, z: 0}
+ m_IKOnFeet: 0
+ m_WriteDefaultValues: 1
+ m_Mirror: 0
+ m_SpeedParameterActive: 0
+ m_MirrorParameterActive: 0
+ m_CycleOffsetParameterActive: 0
+ m_TimeParameterActive: 0
+ m_Motion: {fileID: 3094330708855449807, guid: 6deac83e30d8acd4cbb8c7d8a11545bd, type: 3}
+ m_Tag:
+ m_SpeedParameter:
+ m_MirrorParameter:
+ m_CycleOffsetParameter:
+ m_TimeParameter:
+--- !u!1102 &110200003
+AnimatorState:
+ serializedVersion: 6
+ m_ObjectHideFlags: 1
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name: Hammer
+ m_Speed: 1
+ m_CycleOffset: 0
+ m_Transitions:
+ - {fileID: 110100005}
+ m_StateMachineBehaviours: []
+ m_Position: {x: 50, y: 50, z: 0}
+ m_IKOnFeet: 0
+ m_WriteDefaultValues: 1
+ m_Mirror: 0
+ m_SpeedParameterActive: 0
+ m_MirrorParameterActive: 0
+ m_CycleOffsetParameterActive: 0
+ m_TimeParameterActive: 0
+ m_Motion: {fileID: 3094330708855449807, guid: 1264c74b59285994a85c7eefd31c0c83, type: 3}
+ m_Tag:
+ m_SpeedParameter:
+ m_MirrorParameter:
+ m_CycleOffsetParameter:
+ m_TimeParameter:
+--- !u!1101 &110100001
+AnimatorStateTransition:
+ m_ObjectHideFlags: 1
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name:
+ m_Conditions:
+ - m_ConditionMode: 1
+ m_ConditionEvent: IsMoving
+ m_EventTreshold: 0
+ m_DstStateMachine: {fileID: 0}
+ m_DstState: {fileID: 110200002}
+ m_Solo: 0
+ m_Mute: 0
+ m_IsExit: 0
+ serializedVersion: 3
+ m_TransitionDuration: 0.1
+ m_TransitionOffset: 0
+ m_ExitTime: 0.75
+ m_HasExitTime: 0
+ m_HasFixedDuration: 1
+ m_InterruptionSource: 0
+ m_OrderedInterruption: 1
+ m_CanTransitionToSelf: 1
+--- !u!1101 &110100002
+AnimatorStateTransition:
+ m_ObjectHideFlags: 1
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name:
+ m_Conditions:
+ - m_ConditionMode: 2
+ m_ConditionEvent: IsMoving
+ m_EventTreshold: 0
+ m_DstStateMachine: {fileID: 0}
+ m_DstState: {fileID: 110200001}
+ m_Solo: 0
+ m_Mute: 0
+ m_IsExit: 0
+ serializedVersion: 3
+ m_TransitionDuration: 0.1
+ m_TransitionOffset: 0
+ m_ExitTime: 0.75
+ m_HasExitTime: 0
+ m_HasFixedDuration: 1
+ m_InterruptionSource: 0
+ m_OrderedInterruption: 1
+ m_CanTransitionToSelf: 1
+--- !u!1101 &110100003
+AnimatorStateTransition:
+ m_ObjectHideFlags: 1
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name:
+ m_Conditions:
+ - m_ConditionMode: 1
+ m_ConditionEvent: IsConstructing
+ m_EventTreshold: 0
+ m_DstStateMachine: {fileID: 0}
+ m_DstState: {fileID: 110200003}
+ m_Solo: 0
+ m_Mute: 0
+ m_IsExit: 0
+ serializedVersion: 3
+ m_TransitionDuration: 0.1
+ m_TransitionOffset: 0
+ m_ExitTime: 0.75
+ m_HasExitTime: 0
+ m_HasFixedDuration: 1
+ m_InterruptionSource: 0
+ m_OrderedInterruption: 1
+ m_CanTransitionToSelf: 1
+--- !u!1101 &110100004
+AnimatorStateTransition:
+ m_ObjectHideFlags: 1
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name:
+ m_Conditions:
+ - m_ConditionMode: 1
+ m_ConditionEvent: IsConstructing
+ m_EventTreshold: 0
+ m_DstStateMachine: {fileID: 0}
+ m_DstState: {fileID: 110200003}
+ m_Solo: 0
+ m_Mute: 0
+ m_IsExit: 0
+ serializedVersion: 3
+ m_TransitionDuration: 0.1
+ m_TransitionOffset: 0
+ m_ExitTime: 0.75
+ m_HasExitTime: 0
+ m_HasFixedDuration: 1
+ m_InterruptionSource: 0
+ m_OrderedInterruption: 1
+ m_CanTransitionToSelf: 1
+--- !u!1101 &110100005
+AnimatorStateTransition:
+ m_ObjectHideFlags: 1
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name:
+ m_Conditions:
+ - m_ConditionMode: 2
+ m_ConditionEvent: IsConstructing
+ m_EventTreshold: 0
+ m_DstStateMachine: {fileID: 0}
+ m_DstState: {fileID: 110200001}
+ m_Solo: 0
+ m_Mute: 0
+ m_IsExit: 0
+ serializedVersion: 3
+ m_TransitionDuration: 0.1
+ m_TransitionOffset: 0
+ m_ExitTime: 0.75
+ m_HasExitTime: 0
+ m_HasFixedDuration: 1
+ m_InterruptionSource: 0
+ m_OrderedInterruption: 1
+ m_CanTransitionToSelf: 1
diff --git a/Assets/_Project/Animators/Builder/BuilderAnimator.controller.meta b/Assets/_Project/Animators/Builder/BuilderAnimator.controller.meta
new file mode 100644
index 0000000..8ca3012
--- /dev/null
+++ b/Assets/_Project/Animators/Builder/BuilderAnimator.controller.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3a51bf2db6957d549b96d6c998aa68d3
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/_Project/Prefabs/Builders/Builder_Basic.prefab b/Assets/_Project/Prefabs/Builders/Builder_Basic.prefab
index 760b8b4..7324504 100644
--- a/Assets/_Project/Prefabs/Builders/Builder_Basic.prefab
+++ b/Assets/_Project/Prefabs/Builders/Builder_Basic.prefab
@@ -9,8 +9,6 @@ GameObject:
serializedVersion: 6
m_Component:
- component: {fileID: 5490805221566030526}
- - component: {fileID: 1354786839850046103}
- - component: {fileID: 4167417797825706430}
- component: {fileID: 5001137156876984302}
- component: {fileID: 2903356073138602249}
- component: {fileID: 4225942884111122364}
@@ -33,71 +31,15 @@ Transform:
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
- m_LocalScale: {x: 0.6, y: 0.9, z: 0.6}
+ m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 2153758330548988791}
- {fileID: 5176306400449771234}
- {fileID: 6565619444702228235}
+ - {fileID: 6214765925043807804}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
---- !u!33 &1354786839850046103
-MeshFilter:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 116861493430507844}
- m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0}
---- !u!23 &4167417797825706430
-MeshRenderer:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 116861493430507844}
- 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!114 &5001137156876984302
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -182,19 +124,11 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.Builder
ShowTopMostFoldoutHeaderGroup: 1
- moveSpeed: 8
- arrivalThreshold: 0.05
- turnRateDegPerSec: 540
- heightOffset: 2
- terrainRaycastMaxDistance: 100
- terrainLayerMask:
- serializedVersion: 2
- m_Bits: 128
- buildRange: 6
- maxQueueDepth: 32
+ settings: {fileID: 11400000, guid: 369475c5a672fe54ebaab32041655ca0, type: 2}
buildSiteVisualPrefab: {fileID: 7720770984308489338, guid: dff852699e2897b4494fcbc7f7e547d6, type: 3}
tintedRenderers:
- - {fileID: 4167417797825706430}
+ - {fileID: 863616762514530608}
+ animator: {fileID: 62879399133707334}
--- !u!114 &4533726421250799861
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -422,7 +356,7 @@ Transform:
m_GameObject: {fileID: 6563645777727655090}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalPosition: {x: 0, y: 1, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -449,3 +383,79 @@ BoxCollider:
serializedVersion: 3
m_Size: {x: 1, y: 2, z: 1}
m_Center: {x: 0, y: 0, z: 0}
+--- !u!1001 &5887877976444191191
+PrefabInstance:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_Modification:
+ serializedVersion: 3
+ m_TransformParent: {fileID: 5490805221566030526}
+ m_Modifications:
+ - target: {fileID: -8679921383154817045, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ propertyPath: m_LocalPosition.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: -8679921383154817045, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ propertyPath: m_LocalPosition.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: -8679921383154817045, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ propertyPath: m_LocalPosition.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: -8679921383154817045, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ propertyPath: m_LocalRotation.w
+ value: 1
+ objectReference: {fileID: 0}
+ - target: {fileID: -8679921383154817045, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ propertyPath: m_LocalRotation.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: -8679921383154817045, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ propertyPath: m_LocalRotation.y
+ value: -0
+ objectReference: {fileID: 0}
+ - target: {fileID: -8679921383154817045, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ propertyPath: m_LocalRotation.z
+ value: -0
+ objectReference: {fileID: 0}
+ - target: {fileID: -8679921383154817045, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: -8679921383154817045, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: -8679921383154817045, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 919132149155446097, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ propertyPath: m_Name
+ value: BuilderBody
+ objectReference: {fileID: 0}
+ - target: {fileID: 5866666021909216657, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ propertyPath: m_Controller
+ value:
+ objectReference: {fileID: 9100000, guid: 3a51bf2db6957d549b96d6c998aa68d3, type: 2}
+ m_RemovedComponents: []
+ m_RemovedGameObjects: []
+ m_AddedGameObjects: []
+ m_AddedComponents: []
+ m_SourcePrefab: {fileID: 100100000, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+--- !u!95 &62879399133707334 stripped
+Animator:
+ m_CorrespondingSourceObject: {fileID: 5866666021909216657, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ m_PrefabInstance: {fileID: 5887877976444191191}
+ m_PrefabAsset: {fileID: 0}
+--- !u!137 &863616762514530608 stripped
+SkinnedMeshRenderer:
+ m_CorrespondingSourceObject: {fileID: -2717395650310496025, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ m_PrefabInstance: {fileID: 5887877976444191191}
+ m_PrefabAsset: {fileID: 0}
+--- !u!4 &6214765925043807804 stripped
+Transform:
+ m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: 2faa610713d3b3c439473daa55e8c60a, type: 3}
+ m_PrefabInstance: {fileID: 5887877976444191191}
+ m_PrefabAsset: {fileID: 0}
diff --git a/Assets/_Project/Scripts/Gameplay/Builder.cs b/Assets/_Project/Scripts/Gameplay/Builder.cs
index b2e46c4..6c6346f 100644
--- a/Assets/_Project/Scripts/Gameplay/Builder.cs
+++ b/Assets/_Project/Scripts/Gameplay/Builder.cs
@@ -78,45 +78,11 @@ namespace TD.Gameplay
// ----- Inspector --------------------------------------------------
- [Header("Movement")]
- [Tooltip("Speed at which the builder moves toward its target position, in world " +
- "units per second.")]
- [SerializeField] private float moveSpeed = 8f;
-
- [Tooltip("Distance below which the builder is considered to have arrived at its " +
- "target. Smaller = more precise but more jitter; larger = less precise " +
- "but smoother.")]
- [SerializeField] private float arrivalThreshold = 0.05f;
-
- [Tooltip("Degrees per second the builder rotates to face its movement direction. " +
- "Lower = lazier turns; higher = snappier. The builder only rotates while " +
- "moving; it keeps its last facing when idle.")]
- [SerializeField] private float turnRateDegPerSec = 540f;
-
- [Header("Height tracking")]
- [Tooltip("Vertical offset above the terrain at which the builder hovers. " +
- "Re-evaluated every server tick by raycasting straight down.")]
- [SerializeField] private float heightOffset = 2f;
-
- [Tooltip("Maximum distance to cast downward when sampling terrain height. Should " +
- "exceed your map's vertical range.")]
- [SerializeField] private float terrainRaycastMaxDistance = 100f;
-
- [Tooltip("Physics layer mask used for terrain height sampling. Towers MUST NOT be " +
- "on this layer — only ground geometry. Falls back to the buildable plane Y " +
- "if no terrain hit.")]
- [SerializeField] private LayerMask terrainLayerMask;
-
- [Header("Build range")]
- [Tooltip("Maximum distance from the builder's center to a tower's anchor tile center " +
- "for placement to be allowed, measured in world units (== tiles).")]
- [SerializeField] private float buildRange = 6f;
+ [Header("Settings")]
+ [Tooltip("Shared tunable values for all builders. Create via TD/Builder Settings.")]
+ [SerializeField] private BuilderSettings settings;
[Header("Build queue")]
- [Tooltip("Maximum number of pending build jobs. Bounds memory and prevents a player " +
- "from spamming queue entries faster than the server can process them.")]
- [SerializeField] private int maxQueueDepth = 32;
-
[Tooltip("Build-site visual prefab. Spawned at queue-time as a green ghost; " +
"transitions to staged-construction visuals on arrival; despawned on " +
"completion (replaced by the real TowerInstance) or cancellation.")]
@@ -128,7 +94,12 @@ namespace TD.Gameplay
"BuildRangeIndicator, or any other visual that has its own color rules. " +
"If left empty, the builder will not be tinted (other meshes' colors " +
"from the prefab are preserved).")]
- [SerializeField] private MeshRenderer[] tintedRenderers;
+ [SerializeField] private SkinnedMeshRenderer[] tintedRenderers;
+
+ [Header("Animation")]
+ [Tooltip("Animator on the character model child. Drives IsMoving and IsConstructing " +
+ "bool parameters each frame on all clients.")]
+ [SerializeField] private Animator animator;
// ----- Networked state --------------------------------------------
@@ -173,16 +144,16 @@ namespace TD.Gameplay
public Vector3 TargetPosition => targetPosition.Value;
/// True if the builder has arrived at its target (within
- /// ).
+ /// ).
public bool IsAtTarget =>
Vector3.SqrMagnitude(transform.position - targetPosition.Value)
- < arrivalThreshold * arrivalThreshold;
+ < settings.arrivalThreshold * settings.arrivalThreshold;
/// Build range in world units.
- public float BuildRange => buildRange;
+ public float BuildRange => settings.buildRange;
/// Maximum jobs allowed in the queue.
- public int MaxQueueDepth => maxQueueDepth;
+ public int MaxQueueDepth => settings.maxQueueDepth;
/// True if a tile is currently part of any queued or constructing job.
///
@@ -299,17 +270,37 @@ namespace TD.Gameplay
}
}
- // ----- Per-frame movement (server only) ---------------------------
+ // ----- Animation parameter hashes (cached to avoid per-frame string lookup) ---
+
+ private static readonly int IsMovingHash = Animator.StringToHash("IsMoving");
+ private static readonly int IsConstructingHash = Animator.StringToHash("IsConstructing");
+
+ // ----- Per-frame update -------------------------------------------
private void Update()
{
- if (!IsServer) return;
+ if (IsServer)
+ {
+ ServerDriveQueue();
+ ServerStepMovement();
+ }
- // Step 1: drive movement target from the queue head, if appropriate.
- ServerDriveQueue();
+ UpdateAnimatorState();
+ }
- // Step 2: move toward the target on XZ, sample terrain Y.
- ServerStepMovement();
+ private void UpdateAnimatorState()
+ {
+ if (animator == null) return;
+
+ Vector3 flatCurrent = new Vector3(transform.position.x, 0f, transform.position.z);
+ Vector3 flatTarget = new Vector3(targetPosition.Value.x, 0f, targetPosition.Value.z);
+ bool isMoving = Vector3.SqrMagnitude(flatCurrent - flatTarget)
+ > settings.arrivalThreshold * settings.arrivalThreshold;
+
+ bool isConstructing = jobs.Count > 0 && jobs[0].Stage == BuildStage.Constructing;
+
+ animator.SetBool(IsMovingHash, isMoving);
+ animator.SetBool(IsConstructingHash, isConstructing);
}
private void ServerStepMovement()
@@ -323,20 +314,20 @@ namespace TD.Gameplay
Vector3 newXZ;
bool moving;
- if (Vector3.SqrMagnitude(currentXZ - targetXZ) <= arrivalThreshold * arrivalThreshold)
+ if (Vector3.SqrMagnitude(currentXZ - targetXZ) <= settings.arrivalThreshold * settings.arrivalThreshold)
{
newXZ = targetXZ;
moving = false;
}
else
{
- newXZ = Vector3.MoveTowards(currentXZ, targetXZ, moveSpeed * Time.deltaTime);
+ newXZ = Vector3.MoveTowards(currentXZ, targetXZ, settings.moveSpeed * Time.deltaTime);
moving = true;
}
// Resolve Y from terrain.
float groundY = SampleTerrainY(new Vector3(newXZ.x, 0f, newXZ.z));
- transform.position = new Vector3(newXZ.x, groundY + heightOffset, newXZ.z);
+ transform.position = new Vector3(newXZ.x, groundY + settings.heightOffset, newXZ.z);
// Smoothly face the movement direction. We rotate on the server only;
// NetworkTransform replicates the rotation to clients alongside position.
@@ -348,7 +339,7 @@ namespace TD.Gameplay
{
Quaternion desired = Quaternion.LookRotation(dir, Vector3.up);
transform.rotation = Quaternion.RotateTowards(
- transform.rotation, desired, turnRateDegPerSec * Time.deltaTime);
+ transform.rotation, desired, settings.turnRateDegPerSec * Time.deltaTime);
}
}
}
@@ -361,9 +352,9 @@ namespace TD.Gameplay
private float SampleTerrainY(Vector3 xzPos)
{
// Ray origin: high above the map. terrainRaycastMaxDistance defines how far to cast.
- Vector3 origin = new Vector3(xzPos.x, terrainRaycastMaxDistance, xzPos.z);
+ Vector3 origin = new Vector3(xzPos.x, settings.terrainRaycastMaxDistance, xzPos.z);
if (Physics.Raycast(origin, Vector3.down, out RaycastHit hit,
- terrainRaycastMaxDistance * 2f, terrainLayerMask))
+ settings.terrainRaycastMaxDistance * 2f, settings.terrainLayerMask))
{
return hit.point.y;
}
@@ -448,7 +439,7 @@ namespace TD.Gameplay
Vector3 nearestPoint = new Vector3(nearestX, 0f, nearestZ);
return Vector3.SqrMagnitude(builderXZ - nearestPoint)
- <= buildRange * buildRange;
+ <= settings.buildRange * settings.buildRange;
}
// ===================================================================
@@ -466,7 +457,7 @@ namespace TD.Gameplay
{
jobId = 0;
if (!IsServer) return false;
- if (jobs.Count >= maxQueueDepth) return false;
+ if (jobs.Count >= settings.maxQueueDepth) return false;
jobId = nextJobId++;
var job = BuildJob.CreateQueued(jobId, anchor, towerTypeId, goldSpent);
diff --git a/Assets/_Project/Scripts/Gameplay/BuilderSettings.cs b/Assets/_Project/Scripts/Gameplay/BuilderSettings.cs
new file mode 100644
index 0000000..d499e2d
--- /dev/null
+++ b/Assets/_Project/Scripts/Gameplay/BuilderSettings.cs
@@ -0,0 +1,22 @@
+using UnityEngine;
+
+namespace TD.Gameplay
+{
+ [CreateAssetMenu(fileName = "BuilderSettings", menuName = "TD/Builder Settings")]
+ public class BuilderSettings : ScriptableObject
+ {
+ [Header("Movement")]
+ public float moveSpeed = 8f;
+ public float arrivalThreshold = 0.05f;
+ public float turnRateDegPerSec = 540f;
+
+ [Header("Height Tracking")]
+ public float heightOffset = 2f;
+ public float terrainRaycastMaxDistance = 100f;
+ public LayerMask terrainLayerMask;
+
+ [Header("Build")]
+ public float buildRange = 6f;
+ public int maxQueueDepth = 32;
+ }
+}
diff --git a/Assets/_Project/Scripts/Gameplay/BuilderSettings.cs.meta b/Assets/_Project/Scripts/Gameplay/BuilderSettings.cs.meta
new file mode 100644
index 0000000..a7ebc9a
--- /dev/null
+++ b/Assets/_Project/Scripts/Gameplay/BuilderSettings.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: f17f6455bf6de0a49aea4e4984c6fa79
\ No newline at end of file
diff --git a/Assets/_Project/Settings/Gameplay Settings.meta b/Assets/_Project/Settings/Gameplay Settings.meta
new file mode 100644
index 0000000..32ae8a4
--- /dev/null
+++ b/Assets/_Project/Settings/Gameplay Settings.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 7f3c54bd80a08f049ab3381ed8409523
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/_Project/Settings/Gameplay Settings/BuilderSettings_Default.asset b/Assets/_Project/Settings/Gameplay Settings/BuilderSettings_Default.asset
new file mode 100644
index 0000000..4bca29e
--- /dev/null
+++ b/Assets/_Project/Settings/Gameplay Settings/BuilderSettings_Default.asset
@@ -0,0 +1,24 @@
+%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: f17f6455bf6de0a49aea4e4984c6fa79, type: 3}
+ m_Name: BuilderSettings_Default
+ m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.BuilderSettings
+ moveSpeed: 8
+ arrivalThreshold: 0.05
+ turnRateDegPerSec: 540
+ heightOffset: 2
+ terrainRaycastMaxDistance: 100
+ terrainLayerMask:
+ serializedVersion: 2
+ m_Bits: 128
+ buildRange: 4
+ maxQueueDepth: 32
diff --git a/Assets/_Project/Settings/Gameplay Settings/BuilderSettings_Default.asset.meta b/Assets/_Project/Settings/Gameplay Settings/BuilderSettings_Default.asset.meta
new file mode 100644
index 0000000..c6e210b
--- /dev/null
+++ b/Assets/_Project/Settings/Gameplay Settings/BuilderSettings_Default.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 369475c5a672fe54ebaab32041655ca0
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/_Project/Settings/TowerPlacementSettings.asset b/Assets/_Project/Settings/Gameplay Settings/TowerPlacementSettings.asset
similarity index 100%
rename from Assets/_Project/Settings/TowerPlacementSettings.asset
rename to Assets/_Project/Settings/Gameplay Settings/TowerPlacementSettings.asset
diff --git a/Assets/_Project/Settings/TowerPlacementSettings.asset.meta b/Assets/_Project/Settings/Gameplay Settings/TowerPlacementSettings.asset.meta
similarity index 100%
rename from Assets/_Project/Settings/TowerPlacementSettings.asset.meta
rename to Assets/_Project/Settings/Gameplay Settings/TowerPlacementSettings.asset.meta
diff --git a/Assets/_Project/Settings/Render Settings.meta b/Assets/_Project/Settings/Render Settings.meta
new file mode 100644
index 0000000..24cfef0
--- /dev/null
+++ b/Assets/_Project/Settings/Render Settings.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 10ce56db87ae8e949bb255f90017724b
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/_Project/Settings/Mobile_RPAsset.asset b/Assets/_Project/Settings/Render Settings/Mobile_RPAsset.asset
similarity index 100%
rename from Assets/_Project/Settings/Mobile_RPAsset.asset
rename to Assets/_Project/Settings/Render Settings/Mobile_RPAsset.asset
diff --git a/Assets/_Project/Settings/Mobile_RPAsset.asset.meta b/Assets/_Project/Settings/Render Settings/Mobile_RPAsset.asset.meta
similarity index 100%
rename from Assets/_Project/Settings/Mobile_RPAsset.asset.meta
rename to Assets/_Project/Settings/Render Settings/Mobile_RPAsset.asset.meta
diff --git a/Assets/_Project/Settings/Mobile_Renderer.asset b/Assets/_Project/Settings/Render Settings/Mobile_Renderer.asset
similarity index 100%
rename from Assets/_Project/Settings/Mobile_Renderer.asset
rename to Assets/_Project/Settings/Render Settings/Mobile_Renderer.asset
diff --git a/Assets/_Project/Settings/Mobile_Renderer.asset.meta b/Assets/_Project/Settings/Render Settings/Mobile_Renderer.asset.meta
similarity index 100%
rename from Assets/_Project/Settings/Mobile_Renderer.asset.meta
rename to Assets/_Project/Settings/Render Settings/Mobile_Renderer.asset.meta
diff --git a/Assets/_Project/Settings/PC_RPAsset.asset b/Assets/_Project/Settings/Render Settings/PC_RPAsset.asset
similarity index 100%
rename from Assets/_Project/Settings/PC_RPAsset.asset
rename to Assets/_Project/Settings/Render Settings/PC_RPAsset.asset
diff --git a/Assets/_Project/Settings/PC_RPAsset.asset.meta b/Assets/_Project/Settings/Render Settings/PC_RPAsset.asset.meta
similarity index 100%
rename from Assets/_Project/Settings/PC_RPAsset.asset.meta
rename to Assets/_Project/Settings/Render Settings/PC_RPAsset.asset.meta
diff --git a/Assets/_Project/Settings/PC_Renderer.asset b/Assets/_Project/Settings/Render Settings/PC_Renderer.asset
similarity index 100%
rename from Assets/_Project/Settings/PC_Renderer.asset
rename to Assets/_Project/Settings/Render Settings/PC_Renderer.asset
diff --git a/Assets/_Project/Settings/PC_Renderer.asset.meta b/Assets/_Project/Settings/Render Settings/PC_Renderer.asset.meta
similarity index 100%
rename from Assets/_Project/Settings/PC_Renderer.asset.meta
rename to Assets/_Project/Settings/Render Settings/PC_Renderer.asset.meta
diff --git a/Assets/_Project/Settings/URP-High.asset b/Assets/_Project/Settings/Render Settings/URP-High.asset
similarity index 98%
rename from Assets/_Project/Settings/URP-High.asset
rename to Assets/_Project/Settings/Render Settings/URP-High.asset
index 16c11b7..861e291 100644
--- a/Assets/_Project/Settings/URP-High.asset
+++ b/Assets/_Project/Settings/Render Settings/URP-High.asset
@@ -26,7 +26,7 @@ MonoBehaviour:
m_SupportsHDR: 1
m_HDRColorBufferPrecision: 0
m_MSAA: 4
- m_RenderScale: 1
+ m_RenderScale: 0.85
m_UpscalingFilter: 0
m_FsrOverrideSharpness: 0
m_FsrSharpness: 0.92
@@ -45,7 +45,7 @@ MonoBehaviour:
m_MainLightShadowsSupported: 1
m_MainLightShadowmapResolution: 4096
m_AdditionalLightsRenderingMode: 1
- m_AdditionalLightsPerObjectLimit: 4
+ m_AdditionalLightsPerObjectLimit: 2
m_AdditionalLightShadowsSupported: 1
m_AdditionalLightsShadowmapResolution: 4096
m_AdditionalLightsShadowResolutionTierLow: 256
@@ -54,7 +54,7 @@ MonoBehaviour:
m_ReflectionProbeBlending: 1
m_ReflectionProbeBoxProjection: 1
m_ReflectionProbeAtlas: 1
- m_ShadowDistance: 150
+ m_ShadowDistance: 30
m_ShadowCascadeCount: 4
m_Cascade2Split: 0.25
m_Cascade3Split: {x: 0.1, y: 0.3}
diff --git a/Assets/_Project/Settings/URP-High.asset.meta b/Assets/_Project/Settings/Render Settings/URP-High.asset.meta
similarity index 100%
rename from Assets/_Project/Settings/URP-High.asset.meta
rename to Assets/_Project/Settings/Render Settings/URP-High.asset.meta
diff --git a/Assets/_Project/Settings/URP-Low.asset b/Assets/_Project/Settings/Render Settings/URP-Low.asset
similarity index 100%
rename from Assets/_Project/Settings/URP-Low.asset
rename to Assets/_Project/Settings/Render Settings/URP-Low.asset
diff --git a/Assets/_Project/Settings/URP-Low.asset.meta b/Assets/_Project/Settings/Render Settings/URP-Low.asset.meta
similarity index 100%
rename from Assets/_Project/Settings/URP-Low.asset.meta
rename to Assets/_Project/Settings/Render Settings/URP-Low.asset.meta
diff --git a/Assets/_Project/Settings/URP-Medium.asset b/Assets/_Project/Settings/Render Settings/URP-Medium.asset
similarity index 98%
rename from Assets/_Project/Settings/URP-Medium.asset
rename to Assets/_Project/Settings/Render Settings/URP-Medium.asset
index 6a68e82..23aa7cd 100644
--- a/Assets/_Project/Settings/URP-Medium.asset
+++ b/Assets/_Project/Settings/Render Settings/URP-Medium.asset
@@ -26,7 +26,7 @@ MonoBehaviour:
m_SupportsHDR: 1
m_HDRColorBufferPrecision: 0
m_MSAA: 2
- m_RenderScale: 1
+ m_RenderScale: 0.85
m_UpscalingFilter: 0
m_FsrOverrideSharpness: 0
m_FsrSharpness: 0.92
@@ -45,7 +45,7 @@ MonoBehaviour:
m_MainLightShadowsSupported: 1
m_MainLightShadowmapResolution: 2048
m_AdditionalLightsRenderingMode: 1
- m_AdditionalLightsPerObjectLimit: 6
+ m_AdditionalLightsPerObjectLimit: 2
m_AdditionalLightShadowsSupported: 1
m_AdditionalLightsShadowmapResolution: 2048
m_AdditionalLightsShadowResolutionTierLow: 256
@@ -54,7 +54,7 @@ MonoBehaviour:
m_ReflectionProbeBlending: 1
m_ReflectionProbeBoxProjection: 1
m_ReflectionProbeAtlas: 1
- m_ShadowDistance: 80
+ m_ShadowDistance: 30
m_ShadowCascadeCount: 3
m_Cascade2Split: 0.25
m_Cascade3Split: {x: 0.1, y: 0.3}
diff --git a/Assets/_Project/Settings/URP-Medium.asset.meta b/Assets/_Project/Settings/Render Settings/URP-Medium.asset.meta
similarity index 100%
rename from Assets/_Project/Settings/URP-Medium.asset.meta
rename to Assets/_Project/Settings/Render Settings/URP-Medium.asset.meta
diff --git a/Assets/_Project/Settings/URP-Ultra.asset b/Assets/_Project/Settings/Render Settings/URP-Ultra.asset
similarity index 98%
rename from Assets/_Project/Settings/URP-Ultra.asset
rename to Assets/_Project/Settings/Render Settings/URP-Ultra.asset
index e4130e9..1d32c61 100644
--- a/Assets/_Project/Settings/URP-Ultra.asset
+++ b/Assets/_Project/Settings/Render Settings/URP-Ultra.asset
@@ -26,7 +26,7 @@ MonoBehaviour:
m_SupportsHDR: 1
m_HDRColorBufferPrecision: 0
m_MSAA: 8
- m_RenderScale: 1.5
+ m_RenderScale: 0.85
m_UpscalingFilter: 0
m_FsrOverrideSharpness: 0
m_FsrSharpness: 0.92
@@ -45,7 +45,7 @@ MonoBehaviour:
m_MainLightShadowsSupported: 1
m_MainLightShadowmapResolution: 8192
m_AdditionalLightsRenderingMode: 1
- m_AdditionalLightsPerObjectLimit: 4
+ m_AdditionalLightsPerObjectLimit: 2
m_AdditionalLightShadowsSupported: 1
m_AdditionalLightsShadowmapResolution: 8192
m_AdditionalLightsShadowResolutionTierLow: 256
@@ -54,7 +54,7 @@ MonoBehaviour:
m_ReflectionProbeBlending: 1
m_ReflectionProbeBoxProjection: 1
m_ReflectionProbeAtlas: 1
- m_ShadowDistance: 200
+ m_ShadowDistance: 30
m_ShadowCascadeCount: 4
m_Cascade2Split: 0.25
m_Cascade3Split: {x: 0.1, y: 0.3}
diff --git a/Assets/_Project/Settings/URP-Ultra.asset.meta b/Assets/_Project/Settings/Render Settings/URP-Ultra.asset.meta
similarity index 100%
rename from Assets/_Project/Settings/URP-Ultra.asset.meta
rename to Assets/_Project/Settings/Render Settings/URP-Ultra.asset.meta
diff --git a/Assets/_Project/Settings/UniversalRenderPipelineGlobalSettings.asset b/Assets/_Project/Settings/Render Settings/UniversalRenderPipelineGlobalSettings.asset
similarity index 100%
rename from Assets/_Project/Settings/UniversalRenderPipelineGlobalSettings.asset
rename to Assets/_Project/Settings/Render Settings/UniversalRenderPipelineGlobalSettings.asset
diff --git a/Assets/_Project/Settings/UniversalRenderPipelineGlobalSettings.asset.meta b/Assets/_Project/Settings/Render Settings/UniversalRenderPipelineGlobalSettings.asset.meta
similarity index 100%
rename from Assets/_Project/Settings/UniversalRenderPipelineGlobalSettings.asset.meta
rename to Assets/_Project/Settings/Render Settings/UniversalRenderPipelineGlobalSettings.asset.meta