You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
5.0 KiB
159 lines
5.0 KiB
2 years ago
|
using Godot;
|
||
|
using System;
|
||
|
|
||
|
public partial class Player : CharacterBody3D
|
||
|
{
|
||
|
public float MouseSensitivity { get; set; } = 0.2F;
|
||
|
|
||
|
/// <summary> Time after pressing the jump button a jump may occur late. </summary>
|
||
|
public TimeSpan JumpEarlyTime { get; set; } = TimeSpan.FromSeconds(0.2);
|
||
|
|
||
|
/// <summary> Time after leaving a jumpable surface when a jump may still occur. </summary>
|
||
|
public TimeSpan JumpCoyoteTime { get; set; } = TimeSpan.FromSeconds(0.2);
|
||
|
|
||
|
public Vector3 Gravity { get; set; } = new(0, -12.0F, 0);
|
||
|
public float JumpVelocity { get; set; } = 5.0F;
|
||
|
public float MoveAccel { get; set; } = 6.0F;
|
||
|
public float MaxMoveSpeed { get; set; } = 4.0F;
|
||
|
public float FrictionFloor { get; set; } = 12.0F;
|
||
|
public float FrictionAir { get; set; } = 2.0F;
|
||
|
|
||
|
public enum MovementMode { Default, Flying, NoClip }
|
||
|
public MovementMode Movement { get; set; } = MovementMode.Default;
|
||
|
|
||
|
|
||
|
private Node3D _neckBone = null!;
|
||
|
private Node3D _headBone = null!;
|
||
|
private Camera3D _camera = null!;
|
||
|
|
||
|
private DateTime? _jumpPressed = null;
|
||
|
private DateTime? _lastOnFloor = null;
|
||
|
|
||
|
public bool IsSprinting { get; private set; }
|
||
|
|
||
|
|
||
|
public override void _Ready()
|
||
|
{
|
||
|
_neckBone = GetNode<Node3D>("Neck");
|
||
|
_headBone = GetNode<Node3D>("Neck/Head");
|
||
|
_camera = GetNode<Camera3D>("Neck/Head/Camera");
|
||
|
}
|
||
|
|
||
|
public override void _Input(InputEvent ev)
|
||
|
{
|
||
|
// Inputs that are valid when the game is focused.
|
||
|
// ===============================================
|
||
|
|
||
|
if (ev.IsAction("move_sprint"))
|
||
|
{
|
||
|
IsSprinting = ev.IsPressed();
|
||
|
GetViewport().SetInputAsHandled();
|
||
|
}
|
||
|
|
||
|
if (ev.IsActionPressed("move_jump"))
|
||
|
{
|
||
|
_jumpPressed = DateTime.Now;
|
||
|
GetViewport().SetInputAsHandled();
|
||
|
}
|
||
|
|
||
|
// Cycle movement mode between default, flying and flying+noclip.
|
||
|
if (ev.IsActionPressed("cycle_movement_mode"))
|
||
|
{
|
||
|
if (++Movement > MovementMode.NoClip)
|
||
|
Movement = MovementMode.Default;
|
||
|
GetViewport().SetInputAsHandled();
|
||
|
}
|
||
|
|
||
|
// Inputs that are valid only when the mouse is captured.
|
||
|
// ======================================================
|
||
|
if (Input.MouseMode == Input.MouseModeEnum.Captured) {
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void _UnhandledInput(InputEvent ev)
|
||
|
{
|
||
|
var isMouseCaptured = Input.MouseMode == Input.MouseModeEnum.Captured;
|
||
|
// When pressing escape and mouse is currently captured, release it.
|
||
|
if (ev.IsActionPressed("ui_cancel") && isMouseCaptured)
|
||
|
Input.MouseMode = Input.MouseModeEnum.Visible;
|
||
|
|
||
|
// Grab the mouse when pressing the primary mouse button.
|
||
|
// TODO: Make "primary mouse button" configurable.
|
||
|
if (ev is InputEventMouseButton button && button.ButtonIndex == MouseButton.Left)
|
||
|
Input.MouseMode = Input.MouseModeEnum.Captured;
|
||
|
|
||
|
if (ev is InputEventMouseMotion motion && isMouseCaptured)
|
||
|
{
|
||
|
_neckBone.RotateX(Mathf.DegToRad(motion.Relative.Y * -MouseSensitivity));
|
||
|
_headBone.RotateY(Mathf.DegToRad(motion.Relative.X * -MouseSensitivity));
|
||
|
|
||
|
var rotation = _neckBone.RotationDegrees;
|
||
|
rotation.X = Mathf.Clamp(rotation.X, -80, 80);
|
||
|
_neckBone.RotationDegrees = rotation;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void _PhysicsProcess(double delta)
|
||
|
{
|
||
|
var movementVector = new Vector3(
|
||
|
Input.GetActionStrength("move_strafe_right") - Input.GetActionStrength("move_strafe_left"),
|
||
|
Input.GetActionStrength("move_upward") - Input.GetActionStrength("move_downward"),
|
||
|
Input.GetActionStrength("move_backward") - Input.GetActionStrength("move_forward"));
|
||
|
|
||
|
if (Movement == MovementMode.Default)
|
||
|
{
|
||
|
Velocity += Gravity * (float)delta;
|
||
|
|
||
|
var dir = Vector3.Zero;
|
||
|
var camTransform = _camera.GlobalTransform;
|
||
|
dir += camTransform.Basis.Z.Normalized() * movementVector.Z;
|
||
|
dir += camTransform.Basis.X.Normalized() * movementVector.X;
|
||
|
dir.Y = 0;
|
||
|
dir = dir.Normalized() * movementVector.Length();
|
||
|
|
||
|
var hvel = Velocity;
|
||
|
hvel.Y = 0;
|
||
|
|
||
|
var target = dir * MaxMoveSpeed;
|
||
|
var friction = IsOnFloor() ? FrictionFloor : FrictionAir;
|
||
|
var accel = (dir.Dot(hvel) > 0) ? MoveAccel : friction;
|
||
|
|
||
|
if (IsSprinting) { target *= 5; accel *= 5; }
|
||
|
hvel = hvel.Lerp(target, accel * (float)delta);
|
||
|
|
||
|
Velocity = new(hvel.X, Velocity.Y, hvel.Z);
|
||
|
|
||
|
// Sometimes, when pushing into a wall, jumping wasn't working.
|
||
|
// Possibly due to `IsOnFloor` returning `false` for some reason.
|
||
|
// The `JumpEarlyTime` feature seems to avoid this issue, thankfully.
|
||
|
|
||
|
if (IsOnFloor()) _lastOnFloor = DateTime.Now;
|
||
|
|
||
|
if (((DateTime.Now - _jumpPressed) <= JumpEarlyTime)
|
||
|
&& ((DateTime.Now - _lastOnFloor) <= JumpCoyoteTime))
|
||
|
{
|
||
|
Velocity = new(Velocity.X, JumpVelocity, Velocity.Z);
|
||
|
_jumpPressed = null;
|
||
|
_lastOnFloor = null;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Velocity *= 1 - FrictionAir * (float)delta;
|
||
|
|
||
|
var cameraRot = _headBone.GlobalTransform.Basis.GetRotationQuaternion();
|
||
|
var dir = cameraRot * movementVector;
|
||
|
var target = dir * MaxMoveSpeed;
|
||
|
var accel = (dir.Dot(Velocity) > 0) ? MoveAccel : FrictionAir;
|
||
|
target *= 4; accel *= 4;
|
||
|
|
||
|
if (IsSprinting) { target *= 5; accel *= 5; }
|
||
|
Velocity = Velocity.Lerp(target, accel * (float)delta);
|
||
|
}
|
||
|
|
||
|
if (Movement == MovementMode.NoClip)
|
||
|
Translate(Velocity * (float)delta);
|
||
|
else MoveAndSlide();
|
||
|
}
|
||
|
}
|