using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(BoxCollider))]
public class PlayerMovement : MonoBehaviour
{
public float moveSpeed = 6f;
public float acceleration = 10f;
public float deceleration = 10f;
public float jumpForce = 6f;
private float crouchHeight = 0.6f;
private float normalHeight = 1.4f;
private float crouchSmooth = 7.5f;
[SerializeField] private Transform groundCheck;
[SerializeField] private Transform ceilingCheck;
[SerializeField] private float groundCheckRadius = 0.25f;
[SerializeField] private float ceilingCheckRadius = 0.25f;
[SerializeField] private LayerMask groundLayer;
[SerializeField] private LayerMask ceilingLayer;
private bool isGrounded;
private bool isCrouching;
//private bool isOnCrouch;
public Transform cameraHolder;
public float lookSensitivity = 2f;
public float maxLookX = 88f;
public float minLookX = -88f;
//public float bodyTurnSpeed = 5f;
private float uncrouchMassLimit = 19;
private float upwardForceLimit = 8;
public KeyCode forwardKey = KeyCode.W;
public KeyCode backKey = KeyCode.S;
public KeyCode leftKey = KeyCode.A;
public KeyCode rightKey = KeyCode.D;
public KeyCode jumpKey = KeyCode.Space;
public KeyCode crouchKey = KeyCode.LeftControl;
[SerializeField] private float groundedTimeThreshold = 0.1f;
[SerializeField] private float jumpCooldown = 0.2f;
private float lastGroundedTime;
private float lastJumpTime;
private Rigidbody rb;
private BoxCollider col;
private Vector3 moveInput;
private float rotX;
//private float currentBodyYaw;
void Start()
{
rb = GetComponent<Rigidbody>();
col = GetComponent<BoxCollider>();
rb.freezeRotation = true;
Cursor.lockState = CursorLockMode.Locked;
}
void Update()
{
HandleInput();
HandleCrouch();
HandleCamera();
if (canJump)
{
HandleJump();
}
}
void FixedUpdate()
{
MovePlayer();
GroundCheck();
MovePlayer();
}
void MovePlayer()
{
Vector3 targetVelocity = transform.TransformDirection(moveInput) * moveSpeed;
Vector3 velocityChange = targetVelocity - new Vector3(rb.linearVelocity.x, 0, rb.linearVelocity.z);
velocityChange = Vector3.ClampMagnitude(velocityChange, acceleration);
rb.AddForce(velocityChange, ForceMode.VelocityChange);
}
// ---------------------------------------------
bool canJump = true;
void HandleCrouch()
{
bool wantsCrouch = Input.GetKey(crouchKey);
bool ceilingBlocked = CeilingCheck();
bool hasHeavyAbove = CheckHeavyObjectAbove();
if (wantsCrouch || ceilingBlocked || hasHeavyAbove)
{
isCrouching = true;
canJump = false;
}
else if (!wantsCrouch && !ceilingBlocked && !hasHeavyAbove)
{
isCrouching = false;
canJump = true;
}
float targetHeight = isCrouching ? crouchHeight : normalHeight;
Vector3 size = col.size;
size.y = Mathf.Lerp(size.y, targetHeight, Time.deltaTime * crouchSmooth);
col.size = size;
}
void HandleInput()
{
float x = 0f;
float z = 0f;
if (Input.GetKey(forwardKey)) z += 1;
if (Input.GetKey(backKey)) z -= 1;
if (Input.GetKey(rightKey)) x += 1;
if (Input.GetKey(leftKey)) x -= 1;
Vector3 targetInput = new Vector3(x, 0, z).normalized;
moveInput = Vector3.Lerp(moveInput, targetInput, Time.deltaTime * acceleration);
}
void HandleJump()
{
if (Input.GetKey(jumpKey) &&
isGrounded &&
(Time.time - lastGroundedTime > groundedTimeThreshold) &&
(Time.time - lastJumpTime > jumpCooldown) &&
!isCrouching)
{
Vector3 vel = rb.linearVelocity;
vel.y = 0;
rb.linearVelocity = vel;
rb.AddForce(Vector3.up * jumpForce, ForceMode.VelocityChange);
lastJumpTime = Time.time;
}
}
// ---------------------------------------------
void HandleCamera()
{
if (!cameraHolder) return;
float mouseX = Input.GetAxis("Mouse X") * lookSensitivity;
float mouseY = Input.GetAxis("Mouse Y") * lookSensitivity;
transform.Rotate(Vector3.up * mouseX);
rotX -= mouseY;
rotX = Mathf.Clamp(rotX, minLookX, maxLookX);
cameraHolder.localRotation = Quaternion.Euler(rotX, 0, 0);
}
void GroundCheck()
{
Vector3 groundPos = transform.position + Vector3.down * (col.size.y * 0.5f - 0.05f);
Collider[] hits = Physics.OverlapSphere(groundPos, groundCheckRadius, groundLayer);
bool wasGrounded = isGrounded;
isGrounded = false;
foreach (Collider hit in hits)
{
if (hit.attachedRigidbody)
{
if (hit.attachedRigidbody.linearVelocity.y < -0.2f)
continue;
}
isGrounded = true;
break;
}
if (isGrounded && !wasGrounded)
lastGroundedTime = Time.time;
Debug.DrawRay(groundPos, Vector3.down * 0.1f, isGrounded ? Color.green : Color.red);
}
bool CheckHeavyObjectAbove()
{
Vector3 ceilingPos = transform.position + Vector3.up * (col.size.y * 0.5f - 0.05f);
Collider[] hits = Physics.OverlapSphere(ceilingPos, ceilingCheckRadius, ceilingLayer);
foreach (var hit in hits)
{
if (hit == null || hit.transform == transform)
continue;
Rigidbody rbHit = hit.attachedRigidbody;
if (rbHit != null)
{
float downVelocity = Vector3.Dot(rbHit.linearVelocity, Vector3.down);
if (rbHit.mass > uncrouchMassLimit || downVelocity > upwardForceLimit)
return true;
}
else
{
return true;
}
}
return false;
}
bool CeilingCheck()
{
Vector3 ceilingPos = transform.position + Vector3.up * (col.size.y * 0.5f - 0.05f);
bool ceilingBlocked = Physics.CheckSphere(ceilingPos, ceilingCheckRadius, ceilingLayer);
Debug.DrawRay(ceilingPos, Vector3.up, ceilingBlocked ? Color.red : Color.cyan);
if (ceilingBlocked && !isCrouching)
return true;
return false;
}
}