Files
RebootKit/Runtime/Engine/Code/Services/Simulation/Characters/CharacterLocomotion.cs
2025-03-14 19:53:29 +01:00

160 lines
4.5 KiB
C#

using UnityEngine;
namespace RebootKit.Engine.Services.Simulation.Characters
{
public class CharacterLocomotion : MonoBehaviour
{
[SerializeField]
private CharacterController _characterController;
public float MaxMovementSpeed = 4.0f;
public float MaxSprintSpeed = 15.0f;
public float JumpHeight = 1.0f;
public float Gravity = 10f;
public float MaxFallSpeed = 20f;
public float Damping = 20.0f;
private Vector3 _pendingInputValue;
private Vector3 _currentVelocity;
private bool _isSprinting;
private bool _jumpRequested;
private bool _isFalling;
public bool IsGrounded => _characterController.isGrounded;
public Vector3 Velocity => _currentVelocity;
private void Update()
{
ConsumePendingInput();
UpdateVerticalVelocity();
_characterController.Move(_currentVelocity * Time.deltaTime);
ApplyFriction();
DetectFall();
}
private void DetectFall()
{
if (_isFalling && _characterController.isGrounded)
{
_isFalling = false;
}
else if (!_characterController.isGrounded)
{
_isFalling = true;
}
}
private void ConsumePendingInput()
{
if (!IsGrounded)
{
_pendingInputValue = Vector3.zero;
return;
}
_pendingInputValue.y = 0.0f;
float pendingInputMagnitude = _pendingInputValue.magnitude;
Vector3 direction = Vector3.zero;
if (pendingInputMagnitude > 0.0f)
{
// normalize vector, reusing magnitude to avoid multiple sqrt calls
direction = _pendingInputValue / pendingInputMagnitude;
}
float movementSpeed = _isSprinting ? MaxSprintSpeed : MaxMovementSpeed;
Vector3 movementVelocity = _currentVelocity;
movementVelocity.y = 0.0f;
movementVelocity += direction * (movementSpeed * pendingInputMagnitude);
movementVelocity.y = 0.0f;
// Clamp speed
float movementVelocityMagnitude = movementVelocity.magnitude;
Vector3 movementVelocityDirection = movementVelocityMagnitude > 0.0f ? movementVelocity / movementVelocityMagnitude : Vector3.zero;
if (movementVelocityMagnitude > movementSpeed)
{
movementVelocityMagnitude = movementSpeed;
}
movementVelocity = movementVelocityDirection * movementVelocityMagnitude;
_currentVelocity.x = movementVelocity.x;
_currentVelocity.z = movementVelocity.z;
_pendingInputValue = Vector3.zero;
}
private void UpdateVerticalVelocity()
{
if (_characterController.isGrounded)
{
if (_jumpRequested)
{
_currentVelocity.y = Mathf.Sqrt(2.0f * Gravity * JumpHeight);
_jumpRequested = false;
}
else
{
_currentVelocity.y = -1f;
}
}
else
{
_currentVelocity.y -= Gravity * Time.deltaTime;
_currentVelocity.y = Mathf.Max(_currentVelocity.y, -MaxFallSpeed);
}
}
private void ApplyFriction()
{
if (!IsGrounded)
{
return;
}
Vector3 movementVelocity = _currentVelocity;
movementVelocity.y = 0.0f;
movementVelocity = Vector3.MoveTowards(movementVelocity, Vector3.zero, Damping * Time.deltaTime);
_currentVelocity.x = movementVelocity.x;
_currentVelocity.z = movementVelocity.z;
}
public void AddVelocity(Vector3 velocity)
{
_currentVelocity += velocity;
}
public void AddMovementInput(Vector3 input, float scale)
{
_pendingInputValue += input * scale;
}
public void Jump()
{
if (!_characterController.isGrounded)
{
return;
}
_jumpRequested = true;
}
public void StartSprint()
{
_isSprinting = true;
}
public void StopSprint()
{
_isSprinting = false;
}
}
}