273 lines
10 KiB
C#
273 lines
10 KiB
C#
using UnityEngine;
|
||
using UnityEngine.InputSystem;
|
||
|
||
namespace _Project.Scripts.PlayerUtils
|
||
{
|
||
[RequireComponent(typeof(Rigidbody), typeof(CapsuleCollider))]
|
||
public class PlayerMovement : MonoBehaviour
|
||
{
|
||
[Header("Скорости передвижения (м/с)")]
|
||
[SerializeField] private float moveSpeed = 6.5f;
|
||
[SerializeField] private float sprintSpeed = 9.5f;
|
||
[SerializeField] private float crouchSpeed = 3f;
|
||
|
||
[Header("Параметры Подката (Slide)")]
|
||
[SerializeField] private bool canSlide = true;
|
||
[SerializeField] private float slideInitialSpeed = 15f;
|
||
[SerializeField] private float slideDuration = 0.85f;
|
||
[SerializeField] private float slideFriction = 2.8f;
|
||
[SerializeField] private float slideCooldown = 0.35f;
|
||
|
||
[Header("Физика CS / Source (Bunnyhop & Strafe)")]
|
||
[SerializeField] private float groundFriction = 5f;
|
||
[SerializeField] private float groundAcceleration = 10f;
|
||
[SerializeField] private float airAcceleration = 80f;
|
||
[SerializeField] private float airCap = 2.0f;
|
||
|
||
[Header("Гравитация и Прыжок")]
|
||
[SerializeField] private float jumpHeight = 1.35f;
|
||
[SerializeField] private float gravity = -25f;
|
||
[SerializeField] private float fallMultiplier = 1.8f;
|
||
[SerializeField] private LayerMask groundLayer;
|
||
[SerializeField] private float groundCheckDistance = 0.12f;
|
||
[SerializeField] private float jumpBufferTime = 0.12f;
|
||
|
||
[Header("Коллайдер (Приседание)")]
|
||
[SerializeField] private float standingHeight = 2f;
|
||
[SerializeField] private float crouchingHeight = 1.05f;
|
||
[SerializeField] private float crouchTransitionSpeed = 15f;
|
||
|
||
private Rigidbody rb;
|
||
private CapsuleCollider col;
|
||
private PlayerControls controls;
|
||
|
||
public bool IsGrounded { get; private set; }
|
||
public bool IsSprinting { get; private set; }
|
||
public bool IsCrouching { get; private set; }
|
||
public bool IsSliding { get; private set; }
|
||
public Vector2 MoveInput { get; private set; }
|
||
public float CurrentSpeed => horizontalVelocity.magnitude;
|
||
|
||
private bool wasGrounded;
|
||
private float jumpBufferTimer = 0f;
|
||
private Vector3 horizontalVelocity = Vector3.zero;
|
||
private float verticalVelocity = 0f;
|
||
|
||
private float slideTimer = 0f;
|
||
private float slideCurrentSpeed = 0f;
|
||
private Vector3 slideDirection = Vector3.zero;
|
||
private float slideCooldownTimer = 0f;
|
||
|
||
private void Awake()
|
||
{
|
||
rb = GetComponent<Rigidbody>();
|
||
col = GetComponent<CapsuleCollider>();
|
||
rb.useGravity = false;
|
||
rb.freezeRotation = true;
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
controls = Player.Instance.Controls;
|
||
|
||
controls.Player.Sprint.started += _ => IsSprinting = true;
|
||
controls.Player.Sprint.canceled += _ => IsSprinting = false;
|
||
|
||
controls.Player.Crouch.started += _ => OnCrouchPressed();
|
||
controls.Player.Crouch.canceled += _ => OnCrouchReleased();
|
||
|
||
controls.Player.Jump.performed += _ => QueueJump();
|
||
}
|
||
|
||
public void ApplyProfile(BodyProfile profile)
|
||
{
|
||
moveSpeed = profile.moveSpeed;
|
||
sprintSpeed = profile.sprintSpeed;
|
||
crouchSpeed = profile.crouchSpeed;
|
||
canSlide = profile.canSlide;
|
||
slideInitialSpeed = profile.slideInitialSpeed;
|
||
slideDuration = profile.slideDuration;
|
||
airAcceleration = profile.airAcceleration;
|
||
jumpHeight = profile.jumpHeight;
|
||
gravity = profile.gravity;
|
||
standingHeight = profile.standingHeight;
|
||
|
||
if (!canSlide && IsSliding) StopSlide();
|
||
}
|
||
|
||
public void AddVerticalImpulse(float velocityY)
|
||
{
|
||
verticalVelocity = velocityY;
|
||
IsGrounded = false;
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
MoveInput = controls.Player.Move.ReadValue<Vector2>();
|
||
|
||
float dt = Time.unscaledDeltaTime;
|
||
if (jumpBufferTimer > 0f) jumpBufferTimer -= dt;
|
||
if (slideCooldownTimer > 0f) slideCooldownTimer -= dt;
|
||
|
||
if (Mouse.current != null && Mathf.Abs(Mouse.current.scroll.y.ReadValue()) > 0.1f)
|
||
QueueJump();
|
||
|
||
HandleColliderHeight();
|
||
}
|
||
|
||
private void FixedUpdate()
|
||
{
|
||
CheckGrounded();
|
||
CalculateCustomGravity();
|
||
|
||
if (jumpBufferTimer > 0f && IsGrounded)
|
||
ExecuteJump();
|
||
|
||
ApplyMovement();
|
||
|
||
float timeMult = Player.Instance.Possession != null ? Player.Instance.Possession.PlayerTimeMultiplier : 1f;
|
||
rb.linearVelocity = new Vector3(
|
||
horizontalVelocity.x * timeMult,
|
||
verticalVelocity,
|
||
horizontalVelocity.z * timeMult);
|
||
wasGrounded = IsGrounded;
|
||
}
|
||
|
||
private void HandleColliderHeight()
|
||
{
|
||
bool isLowered = IsCrouching || IsSliding;
|
||
float targetHeight = isLowered ? crouchingHeight : standingHeight;
|
||
col.height = Mathf.Lerp(col.height, targetHeight, Time.unscaledDeltaTime * crouchTransitionSpeed);
|
||
col.center = new Vector3(col.center.x, col.height * 0.5f, col.center.z);
|
||
}
|
||
|
||
private void QueueJump() => jumpBufferTimer = jumpBufferTime;
|
||
|
||
private void ExecuteJump()
|
||
{
|
||
if (IsSliding)
|
||
{
|
||
horizontalVelocity = slideDirection * slideCurrentSpeed;
|
||
StopSlide();
|
||
}
|
||
|
||
verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
|
||
jumpBufferTimer = 0f;
|
||
|
||
Player.Instance.CameraController.TriggerJumpImpact();
|
||
Player.Instance.Sound.PlayJump();
|
||
}
|
||
|
||
private void ApplyMovement()
|
||
{
|
||
if (IsSliding)
|
||
{
|
||
slideCurrentSpeed = Mathf.Lerp(slideCurrentSpeed, crouchSpeed, Time.fixedDeltaTime * slideFriction);
|
||
slideTimer -= Time.fixedDeltaTime;
|
||
|
||
Vector3 steerDir = (transform.forward * MoveInput.y + transform.right * MoveInput.x).normalized;
|
||
if (steerDir.sqrMagnitude > 0.01f)
|
||
slideDirection = Vector3.Slerp(slideDirection, steerDir, Time.fixedDeltaTime * 2.5f);
|
||
|
||
horizontalVelocity = slideDirection * slideCurrentSpeed;
|
||
|
||
if (slideTimer <= 0f || slideCurrentSpeed <= crouchSpeed + 0.5f)
|
||
{
|
||
StopSlide();
|
||
IsCrouching = controls.Player.Crouch.IsPressed();
|
||
}
|
||
return;
|
||
}
|
||
|
||
Vector3 wishDir = (transform.forward * MoveInput.y + transform.right * MoveInput.x);
|
||
wishDir.y = 0f;
|
||
wishDir.Normalize();
|
||
|
||
float targetSpeed = IsCrouching ? crouchSpeed : (IsSprinting ? sprintSpeed : moveSpeed);
|
||
|
||
if (IsGrounded)
|
||
{
|
||
if (jumpBufferTimer <= 0f) ApplyGroundFriction();
|
||
Accelerate(wishDir, targetSpeed, groundAcceleration);
|
||
}
|
||
else
|
||
{
|
||
AirAccelerate(wishDir, targetSpeed, airAcceleration, airCap);
|
||
}
|
||
}
|
||
|
||
private void ApplyGroundFriction()
|
||
{
|
||
float speed = horizontalVelocity.magnitude;
|
||
if (speed < 0.0001f) { horizontalVelocity = Vector3.zero; return; }
|
||
float drop = speed * groundFriction * Time.fixedDeltaTime;
|
||
horizontalVelocity *= (Mathf.Max(speed - drop, 0f) / speed);
|
||
}
|
||
|
||
private void Accelerate(Vector3 wishDir, float wishSpeed, float accel)
|
||
{
|
||
float currentSpeed = Vector3.Dot(horizontalVelocity, wishDir);
|
||
float addSpeed = Mathf.Max(wishSpeed - currentSpeed, 0f);
|
||
horizontalVelocity += wishDir * Mathf.Min(accel * wishSpeed * Time.fixedDeltaTime, addSpeed);
|
||
}
|
||
|
||
private void AirAccelerate(Vector3 wishDir, float wishSpeed, float accel, float cap)
|
||
{
|
||
float currentSpeed = Vector3.Dot(horizontalVelocity, wishDir);
|
||
float addSpeed = Mathf.Max(Mathf.Min(wishSpeed, cap) - currentSpeed, 0f);
|
||
horizontalVelocity += wishDir * Mathf.Min(accel * wishSpeed * Time.fixedDeltaTime, addSpeed);
|
||
}
|
||
|
||
private void CheckGrounded()
|
||
{
|
||
float checkRadius = col.radius * 0.85f;
|
||
float bottomOffset = (col.height * 0.5f) - checkRadius;
|
||
Vector3 rayOrigin = transform.position + col.center - (Vector3.up * bottomOffset) + (Vector3.up * 0.05f);
|
||
|
||
IsGrounded = Physics.SphereCast(rayOrigin, checkRadius, Vector3.down, out _, groundCheckDistance + 0.05f, groundLayer, QueryTriggerInteraction.Ignore);
|
||
|
||
if (!wasGrounded && IsGrounded)
|
||
{
|
||
Player.Instance.Sound.PlayLand();
|
||
}
|
||
|
||
if (IsGrounded && jumpBufferTimer <= 0f && rb.linearVelocity.magnitude > 0.01f && horizontalVelocity == Vector3.zero)
|
||
horizontalVelocity = new Vector3(rb.linearVelocity.x, 0f, rb.linearVelocity.z);
|
||
}
|
||
|
||
private void CalculateCustomGravity()
|
||
{
|
||
if (IsGrounded && verticalVelocity < 0f) verticalVelocity = -2f;
|
||
else verticalVelocity += ((verticalVelocity < 0f) ? (gravity * fallMultiplier) : gravity) * Time.fixedDeltaTime;
|
||
}
|
||
|
||
private void OnCrouchPressed()
|
||
{
|
||
if (canSlide && IsSprinting && IsGrounded && MoveInput.y > 0.2f && slideCooldownTimer <= 0f) StartSlide();
|
||
else IsCrouching = true;
|
||
}
|
||
|
||
private void OnCrouchReleased()
|
||
{
|
||
IsCrouching = false;
|
||
if (IsSliding) StopSlide();
|
||
}
|
||
|
||
private void StartSlide()
|
||
{
|
||
IsSliding = true;
|
||
slideTimer = slideDuration;
|
||
slideCurrentSpeed = slideInitialSpeed;
|
||
slideDirection = (transform.forward * MoveInput.y + transform.right * MoveInput.x).normalized;
|
||
if (slideDirection.sqrMagnitude < 0.01f) slideDirection = transform.forward;
|
||
|
||
Player.Instance.Sound.PlaySlide();
|
||
}
|
||
|
||
private void StopSlide()
|
||
{
|
||
IsSliding = false;
|
||
slideCooldownTimer = slideCooldown;
|
||
}
|
||
}
|
||
} |