160 lines
4.5 KiB
C#
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;
|
|
}
|
|
}
|
|
} |