306 lines
11 KiB
C#
306 lines
11 KiB
C#
|
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<Camera>();
|
||
|
characterController = GetComponent<CharacterController>();
|
||
|
animator = GetComponentInChildren<Animator>(); // [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;
|
||
|
}
|