using UnityEngine; [RequireComponent(typeof(CharacterController))] public class PlayerController : MonoBehaviour { [Header("Movement")] public float walkSpeed = 6f; public float sprintSpeed = 10f; public float crouchSpeed = 3f; public float jumpHeight = 1.2f; public float gravity = -20f; [Header("Mouse Look")] public float mouseSensitivity = 2f; public float maxLookAngle = 85f; public Transform cameraHolder; [Header("Crouch")] public float standingHeight = 2f; public float crouchingHeight = 1f; public float crouchTransitionSpeed = 10f; [Header("Head Bob")] public float bobFrequency = 10f; public float bobAmplitude = 0.04f; public float sprintBobMultiplier = 1.6f; // --- Private State --- private CharacterController _cc; private Vector3 _velocity; private float _xRotation; private bool _isCrouching; private bool _isSprinting; private float _bobTimer; private Vector3 _cameraLocalOrigin; void Awake() { _cc = GetComponent(); if (cameraHolder == null && Camera.main != null) cameraHolder = Camera.main.transform; if (cameraHolder != null) _cameraLocalOrigin = cameraHolder.localPosition; Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; } void Update() { HandleMouseLook(); HandleCrouch(); HandleMovement(); HandleHeadBob(); } // ----------------------------------------------------------------------- // Mouse Look // ----------------------------------------------------------------------- void HandleMouseLook() { float mouseX = Input.GetAxisRaw("Mouse X") * mouseSensitivity; float mouseY = Input.GetAxisRaw("Mouse Y") * mouseSensitivity; _xRotation -= mouseY; _xRotation = Mathf.Clamp(_xRotation, -maxLookAngle, maxLookAngle); if (cameraHolder != null) cameraHolder.localRotation = Quaternion.Euler(_xRotation, 0f, 0f); transform.Rotate(Vector3.up * mouseX); } // ----------------------------------------------------------------------- // Movement & Jumping // ----------------------------------------------------------------------- void HandleMovement() { bool grounded = _cc.isGrounded; if (grounded && _velocity.y < 0f) _velocity.y = -2f; // keep grounded float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical"); _isSprinting = grounded && Input.GetKey(KeyCode.LeftShift) && !_isCrouching && v > 0f; float speed = _isCrouching ? crouchSpeed : (_isSprinting ? sprintSpeed : walkSpeed); // Instant direction change — no momentum (arcade feel) Vector3 move = (transform.right * h + transform.forward * v).normalized * speed; _cc.Move(move * Time.deltaTime); // Jump if (Input.GetButtonDown("Jump") && grounded && !_isCrouching) _velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity); // Gravity _velocity.y += gravity * Time.deltaTime; _cc.Move(new Vector3(0f, _velocity.y, 0f) * Time.deltaTime); } // ----------------------------------------------------------------------- // Crouch // ----------------------------------------------------------------------- void HandleCrouch() { bool wantCrouch = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.C); // Prevent standing up if something is above the player if (_isCrouching && !wantCrouch && !CanStandUp()) wantCrouch = true; _isCrouching = wantCrouch; float targetHeight = _isCrouching ? crouchingHeight : standingHeight; _cc.height = Mathf.Lerp(_cc.height, targetHeight, crouchTransitionSpeed * Time.deltaTime); _cc.center = new Vector3(0f, _cc.height / 2f, 0f); // Move camera with the capsule top if (cameraHolder != null) { float targetCamY = _cameraLocalOrigin.y - (standingHeight - _cc.height) * 0.5f; Vector3 pos = cameraHolder.localPosition; pos.y = Mathf.Lerp(pos.y, targetCamY, crouchTransitionSpeed * Time.deltaTime); cameraHolder.localPosition = new Vector3(_cameraLocalOrigin.x, pos.y, _cameraLocalOrigin.z); } } bool CanStandUp() { float checkDistance = standingHeight - _cc.height; Vector3 top = transform.position + Vector3.up * (_cc.height - _cc.radius); return !Physics.SphereCast(top, _cc.radius * 0.9f, Vector3.up, out _, checkDistance); } // ----------------------------------------------------------------------- // Head Bob // ----------------------------------------------------------------------- void HandleHeadBob() { if (cameraHolder == null) return; bool isMoving = _cc.isGrounded && _cc.velocity.magnitude > 0.1f; if (isMoving) { float multiplier = _isSprinting ? sprintBobMultiplier : 1f; _bobTimer += Time.deltaTime * bobFrequency * multiplier; float bobY = Mathf.Sin(_bobTimer) * bobAmplitude; float bobX = Mathf.Sin(_bobTimer * 0.5f) * bobAmplitude * 0.5f; Vector3 pos = cameraHolder.localPosition; pos.y += bobY; pos.x = _cameraLocalOrigin.x + bobX; cameraHolder.localPosition = pos; } else { _bobTimer = 0f; } } }