using System.Collections; using UnityEngine; public class CharacterMovement : MonoBehaviour { public bool CanMove { get; private set; } = true; private bool isSprinting => canSprint && Input.GetKey(sprintKey); private bool ShouldJump => Input.GetKeyDown(jumpKey) && characterController.isGrounded; private bool ShouldCrouch => Input.GetKeyDown(crouchKey) && !duringCrouchAnim && characterController.isGrounded; [Header("Character Options")] [SerializeField] private bool canSprint = true; [SerializeField] private bool canJump = true; [SerializeField] private bool canCrouch = true; [SerializeField] private bool canHeadBob = true; [SerializeField] private bool canInteract = true; [SerializeField] private bool useFootsteps = true; [Header("Controls")] [SerializeField] private KeyCode sprintKey = KeyCode.LeftShift; [SerializeField] private KeyCode jumpKey = KeyCode.Space; [SerializeField] private KeyCode crouchKey = KeyCode.LeftControl; [Header("Movement Parameters")] [SerializeField] private float walkSpeed = 3.0f; [SerializeField] private float sprintSpeed = 6.0f; [SerializeField] private float crouchSpeed = 1.5f; [Header("Camera Parameters")] [SerializeField, Range(1, 10)] private float lookSpeedX = 2.0f; [SerializeField, Range(1, 10)] private float lookSpeedY = 2.0f; [SerializeField, Range(1, 180)] private float lowerLookLimit = 80.0f; [SerializeField, Range(1, 180)] private float upperLookLimit = 80.0f; private float currentLookSpeedX = 2.0f; private float currentLookSpeedY = 2.0f; private bool mouseInverted = false; [Header("Jumping Parameters")] [SerializeField] private float jumpForce = 8.0f; [SerializeField] private float gravity = 30.0f; [Header("Crouching Parameters")] [SerializeField] private float crouchHeight = 0.5f; [SerializeField] private float standinghHeight = 2f; [SerializeField] private float timeToCrouch = 0.25f; [SerializeField] private Vector3 crouchCenter = new Vector3(0, 0.5f, 0); [SerializeField] private Vector3 standingCenter = new Vector3(0, 0, 0); private bool isCrouching; private bool duringCrouchAnim; [Header("Headbob Parameters")] [SerializeField] private float walkBobSpeed = 14.0f; [SerializeField] private float walkBobAmount = 0.05f; [SerializeField] private float sprintBobSpeed = 18.0f; [SerializeField] private float sprintBobAmount = 0.11f; [Header("Footstep Parameters")] [SerializeField] private float baseStepSpeed = 0.5f; [SerializeField] private float crouchStepMultiplier = 1.5f; [SerializeField] private float sprintStepMultiplier = 0.6f; [SerializeField] private AudioSource footstepAudioSource = default; [SerializeField] private AudioClip[] woodClips = default; [SerializeField] private AudioClip[] stoneClips = default; [SerializeField] private AudioClip[] waterClips = default; [SerializeField] private AudioClip[] grassClips = default; [Header("Spawn Points")] [SerializeField] private Transform spawnPoint = default; [SerializeField] private Transform miniGameStartPoint = default; private float footstepTimer = 0; private float GetCurrentOffset => isCrouching ? baseStepSpeed * crouchStepMultiplier : isSprinting ? baseStepSpeed * sprintStepMultiplier : baseStepSpeed; private float defaultYPos = 0; private float timer; private Camera playerCamera; private CharacterController characterController; private Animator animator; private Vector3 moveDirection; private Vector2 currentInput; private float rotationX = 0; void Awake() { playerCamera = GetComponentInChildren(); characterController = GetComponent(); animator = GetComponentInChildren(); // [ANIMATION] Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; defaultYPos = playerCamera.transform.localPosition.y; } private void Start() { LoadMouseSettings(); if (LoadingScene.Instance != null && LoadingScene.Instance.playMiniGame) { StartMiniGame(); LoadingScene.Instance.playMiniGame = false; } } void Update() { if (LoadingScene.Instance != null && LoadingScene.Instance.playMiniGame) { StartMiniGame(); LoadingScene.Instance.playMiniGame = false; } if (CanMove) { HandleMovementInput(); HandleMouseLook(); if (canJump) HandleJump(); if (canCrouch) HandleCrouch(); if (canHeadBob) HandleHeadBob(); if (useFootsteps) HandleFootsteps(); HandleAnimations(); // [ANIMATION] ApplyFinalMovements(); } } private void HandleMovementInput() { currentInput = new Vector2((isCrouching ? crouchSpeed : isSprinting ? sprintSpeed : walkSpeed) * Input.GetAxis("Vertical"), (isCrouching ? crouchSpeed : isSprinting ? sprintSpeed : walkSpeed) * Input.GetAxis("Horizontal")); float moveDirectionY = moveDirection.y; moveDirection = (transform.TransformDirection(Vector3.forward) * currentInput.x) + (transform.TransformDirection(Vector3.right) * currentInput.y); moveDirection.y = moveDirectionY; } private void HandleHeadBob() { if (!characterController.isGrounded) return; if (Mathf.Abs(moveDirection.x) > 0.1f || Mathf.Abs(moveDirection.z) > 0.1f) { timer += Time.deltaTime * (isSprinting ? sprintBobSpeed : walkBobSpeed); playerCamera.transform.localPosition = new Vector3( playerCamera.transform.localPosition.x, defaultYPos + Mathf.Sin(timer) * (isSprinting ? sprintBobAmount : walkBobAmount), playerCamera.transform.localPosition.z); } } private void HandleMouseLook() { float mouseY = Input.GetAxis("Mouse Y") * currentLookSpeedY; float mouseX = Input.GetAxis("Mouse X") * currentLookSpeedX; if (mouseInverted) mouseY = -mouseY; rotationX -= mouseY; rotationX = Mathf.Clamp(rotationX, -upperLookLimit, lowerLookLimit); playerCamera.transform.localRotation = Quaternion.Euler(rotationX, 0, 0); transform.rotation *= Quaternion.Euler(0, mouseX, 0); } private void HandleJump() { if (ShouldJump) moveDirection.y = jumpForce; } private void HandleCrouch() { if (ShouldCrouch) StartCoroutine(CrouchStand()); } private void ApplyFinalMovements() { if (!characterController.isGrounded) moveDirection.y -= gravity * Time.deltaTime; characterController.Move(moveDirection * Time.deltaTime); } private IEnumerator CrouchStand() { if (isCrouching && Physics.Raycast(playerCamera.transform.position, Vector3.up, 1f)) yield break; duringCrouchAnim = true; float timeElapsed = 0; float targetHeight = isCrouching ? standinghHeight : crouchHeight; float currentHeight = characterController.height; Vector3 targetCenter = isCrouching ? standingCenter : crouchCenter; Vector3 currentCenter = characterController.center; while (timeElapsed < timeToCrouch) { characterController.height = Mathf.Lerp(currentHeight, targetHeight, timeElapsed / timeToCrouch); characterController.center = Vector3.Lerp(currentCenter, targetCenter, timeElapsed / timeToCrouch); timeElapsed += Time.deltaTime; yield return null; } characterController.height = targetHeight; characterController.center = targetCenter; isCrouching = !isCrouching; duringCrouchAnim = false; } private void HandleFootsteps() { if (!characterController.isGrounded) return; if (currentInput == Vector2.zero) return; footstepTimer -= Time.deltaTime; if (footstepTimer <= 0) { if (Physics.Raycast(playerCamera.transform.position, Vector3.down, out RaycastHit hit, 2)) { switch (hit.collider.tag) { case "Footsteps/Grass": footstepAudioSource.PlayOneShot(grassClips[Random.Range(0, grassClips.Length - 1)]); break; case "Footsteps/Stone": footstepAudioSource.PlayOneShot(stoneClips[Random.Range(0, stoneClips.Length - 1)]); break; case "Footsteps/Water": footstepAudioSource.PlayOneShot(waterClips[Random.Range(0, waterClips.Length - 1)]); break; case "Footsteps/Wood": footstepAudioSource.PlayOneShot(woodClips[Random.Range(0, woodClips.Length - 1)]); break; default: footstepAudioSource.PlayOneShot(grassClips[Random.Range(0, grassClips.Length - 1)]); break; } } footstepTimer = GetCurrentOffset; } } private void HandleAnimations() // [ANIMATION] { if (!animator) return; float movementMagnitude = new Vector2(moveDirection.x, moveDirection.z).magnitude; animator.SetBool("isWalking", movementMagnitude > 0.1f && !isSprinting && !isCrouching); animator.SetBool("isRunning", isSprinting && movementMagnitude > 0.1f); animator.SetBool("isCrouching", isCrouching); animator.SetBool("isJumping", !characterController.isGrounded); } private void StartMiniGame() { CanMove = true; characterController.enabled = false; if (miniGameStartPoint != null) { transform.position = miniGameStartPoint.position; } else { Debug.LogWarning("Mini-game start point not found!"); } characterController.enabled = true; } public Transform GetMiniGameStartPoint() => miniGameStartPoint; public Transform GetSpawnPoint() => spawnPoint; private void LoadMouseSettings() { currentLookSpeedX = PlayerPrefs.GetFloat("XSensitivity", lookSpeedX); currentLookSpeedY = PlayerPrefs.GetFloat("YSensitivity", lookSpeedY); mouseInverted = PlayerPrefs.GetInt("Inverted", 0) == 1; } public void UpdateMouseSettings(float sensitivityX, float sensitivityY, bool inverted) { currentLookSpeedX = sensitivityX; currentLookSpeedY = sensitivityY; mouseInverted = inverted; } public void SetCanMove(bool canMove) => CanMove = canMove; public void DisablePlayer() => CanMove = false; public void EnablePlayer() => CanMove = true; }