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