public partial class MovementController : Node
{
[ExportGroup("Movement")]
[Export] public float Acceleration { get; set; } = 4.0f;
[Export] public float MaxSpeed { get; set; } = 2.1f;
[Export] public float FrictionFloor { get; set; } = 12.0f;
[Export] public float FrictionAir { get; set; } = 2.0f;
public float Gravity { get; } = (float)ProjectSettings.GetSetting("physics/3d/default_gravity");
[ExportGroup("Jumping")]
[Export] public float JumpVelocity { get; set; } = 4.0f;
/// Time (in seconds) after pressing the jump button a jump may occur late.
[Export] public float JumpEarlyTime { get; set; } = 0.0f;
/// Time (in seconds) after leaving a jumpable surface when a jump may still occur.
[Export] public float JumpCoyoteTime { get; set; } = 0.0f;
public bool IsSprinting { get; private set; }
/// The raw input movement vector with a maximum length of 1.
public Vector2 InputVector { get; private set; }
public bool IsMoving => LocalMoveVector.Length() > 0.01f;
/// The actual amount the player is moving, relative to the world. Y is always 0.
public Vector3 GlobalMoveVector { get; private set; }
/// The actual amount the player is moving, relative to the player's viewpoint. Y is always 0.
public Vector3 LocalMoveVector { get; private set; }
///
public float LocalMoveAngle { get; private set; }
public float TimeSinceJumpPressed { get; private set; } = float.PositiveInfinity;
public float TimeSinceOnFloor { get; private set; } = float.PositiveInfinity;
Player _player;
public override void _Ready()
=> _player = GetParent();
public override void _UnhandledInput(InputEvent @event)
{
if (!_player.IsLocal) return;
// TODO: Sprinting.
if (@event.IsActionPressed("move_jump")) {
TimeSinceJumpPressed = 0.0f;
GetViewport().SetInputAsHandled();
}
}
public override void _PhysicsProcess(double delta)
{
var velocity = _player.Velocity;
velocity.Y -= Gravity * (float)delta;
// Get the (normalized) movement vector from the current input.
InputVector = _player.IsLocal
? Input.GetVector("move_strafe_left", "move_strafe_right", "move_forward", "move_back")
: Vector2.Zero;
var horVelocity = velocity with { Y = 0 };
var basis = _player.Basis.Rotated(Vector3.Up, _player.Camera.CurrentYaw);
var target = basis * new Vector3(InputVector.X, 0, InputVector.Y) * MaxSpeed;
var isMoving = target.Dot(horVelocity) > 0.0f;
var isOnFloor = _player.IsOnFloor();
var accel = isMoving ? Acceleration
: isOnFloor ? FrictionFloor
: FrictionAir;
if (IsSprinting) {
target *= 5;
accel *= 5;
}
horVelocity = horVelocity.Lerp(target, accel * (float)delta);
velocity.X = horVelocity.X;
velocity.Z = horVelocity.Z;
if (isOnFloor) TimeSinceOnFloor = 0.0f;
else TimeSinceOnFloor += (float)delta;
if ((TimeSinceJumpPressed <= JumpEarlyTime) && (TimeSinceOnFloor <= JumpCoyoteTime)) {
TimeSinceJumpPressed = TimeSinceOnFloor = float.PositiveInfinity;
velocity.Y = JumpVelocity;
} else
TimeSinceJumpPressed += (float)delta;
_player.Velocity = velocity;
_player.MoveAndSlide();
// TODO: Very simplified, but works for now.
GlobalMoveVector = _player.Velocity with { Y = 0 };
LocalMoveVector = _player.Basis.Inverse() * GlobalMoveVector;
LocalMoveAngle = Vector3.Forward.SignedAngleTo(LocalMoveVector, Vector3.Up);
}
}