194 lines
6.7 KiB
C#
194 lines
6.7 KiB
C#
using FactoryGame.Entities;
|
|
using Godot;
|
|
|
|
using static FactoryGame.Utils.Constants.PropertyHints;
|
|
|
|
public partial class PlayerController : CharacterBody3D {
|
|
[ExportGroup("Nodes")]
|
|
[Export]
|
|
public CollisionShape3D? StandCollision { get; set; }
|
|
[Export]
|
|
public CollisionShape3D? CrouchCollision { get; set; }
|
|
[Export]
|
|
public CollisionComponent? HeadCollisionComponent { get; set; }
|
|
[Export]
|
|
public CollisionComponent? BottomCollisionComponent { get; set; }
|
|
[Export]
|
|
public Camera3D? Camera { get; set; }
|
|
[Export]
|
|
public Marker3D? StandCameraPosition { get; set; }
|
|
[Export]
|
|
public Marker3D? CrouchCameraPosition { get; set; }
|
|
|
|
[ExportGroup("Behavior")]
|
|
[Export(PropertyHint.Range, PrimaRange)]
|
|
public float CameraSpeed { get; set; } = 0.25f;
|
|
[Export(PropertyHint.Range, OctaveRange)]
|
|
public float PushImpulse { get; set; } = 0.5f;
|
|
[Export(PropertyHint.Range, PrimaRange)]
|
|
public float CeilingBounce { get; set; } = 0.3f;
|
|
[Export]
|
|
public PlayerMovingState MovingState {
|
|
get => _movingState; set {
|
|
if (_movingState == value) return;
|
|
if (crouchLock) { _movingState = PlayerMovingState.Crouching; return; }
|
|
|
|
_movingState = value;
|
|
StandCollision!.Disabled = !(_movingState == PlayerMovingState.Walking || _movingState == PlayerMovingState.Sprinting);
|
|
CrouchCollision!.Disabled = _movingState != PlayerMovingState.Crouching;
|
|
if (_movingState == PlayerMovingState.Walking || _movingState == PlayerMovingState.Sprinting)
|
|
_cameraTargetPosition = StandCameraPosition!.Position;
|
|
if (_movingState == PlayerMovingState.Crouching)
|
|
_cameraTargetPosition = CrouchCameraPosition!.Position;
|
|
}
|
|
}
|
|
private PlayerMovingState _movingState = PlayerMovingState.Walking;
|
|
private bool crouchLock = false;
|
|
|
|
[ExportSubgroup("Gravity Stabilization")]
|
|
[Export(PropertyHint.Range, TwoOctaveRange)]
|
|
public float FlyGravityStabilizationSpeed { get; set; } = 2f;
|
|
[Export(PropertyHint.Range, TwoOctaveRange)]
|
|
public float FloorGravityStabilizationSpeed { get; set; } = 15f;
|
|
public float GetCurrentGravityStabilizationSpeed() => BottomCollisionComponent!.IsCollide() ? FloorGravityStabilizationSpeed : FlyGravityStabilizationSpeed;
|
|
|
|
[ExportSubgroup("Movement")]
|
|
[Export(PropertyHint.Range, OctaveRange)]
|
|
public float MoveVelocity { get; set; } = 4f;
|
|
[Export(PropertyHint.Range, OctaveRange)]
|
|
public float CrouchVelocity { get; set; } = 2f;
|
|
[Export(PropertyHint.Range, OctaveRange)]
|
|
public float SprintVelocity { get; set; } = 8f;
|
|
[Export(PropertyHint.Range, QuartRange)]
|
|
public float FlyVelocityMultiplier { get; set; } = 1.05f;
|
|
private float GetCurrentSpeed() {
|
|
var speed = MovingState switch {
|
|
PlayerMovingState.Walking => MoveVelocity,
|
|
PlayerMovingState.Crouching => CrouchVelocity,
|
|
PlayerMovingState.Sprinting => SprintVelocity,
|
|
_ => MoveVelocity
|
|
};
|
|
|
|
if (!IsOnFloor()) return FlyVelocityMultiplier * speed;
|
|
return speed;
|
|
}
|
|
[Export(PropertyHint.Range, OctaveRange)]
|
|
public float JumpVelocity { get; set; } = 4f;
|
|
|
|
[ExportSubgroup("Friction")]
|
|
[Export(PropertyHint.Range, PrimaRange)]
|
|
public float MoveFriction { get; set; } = 0.5f;
|
|
[Export(PropertyHint.Range, PrimaRange)]
|
|
public float CrouchFriction { get; set; } = 0.2f;
|
|
[Export(PropertyHint.Range, OctaveRange)]
|
|
public float SprintFriction { get; set; } = 0.6f;
|
|
[Export(PropertyHint.Range, PrimaRange)]
|
|
public float FlyFriction { get; set; } = 0.1f;
|
|
private float GetCurrentFriction() => !IsOnFloor() ? FlyFriction : MovingState switch {
|
|
PlayerMovingState.Walking => MoveFriction,
|
|
PlayerMovingState.Crouching => CrouchFriction,
|
|
PlayerMovingState.Sprinting => SprintFriction,
|
|
_ => MoveFriction
|
|
};
|
|
|
|
public override void _Ready() {
|
|
_cameraTargetPosition = Camera!.Position;
|
|
HeadCollisionComponent!.BodyEntered += (_) => ProceedCrouchingCheck(HeadCollisionComponent!);
|
|
HeadCollisionComponent!.BodyExited += (_) => ProceedCrouchingCheck(HeadCollisionComponent!);
|
|
|
|
base._Ready();
|
|
}
|
|
public override void _PhysicsProcess(double delta) {
|
|
var fDelta = (float)delta;
|
|
UpdatePlayerOrientation(fDelta);
|
|
|
|
var gravity = ProceedGravityVelocity(fDelta);
|
|
var move = ProceedMoveVelocity(fDelta);
|
|
|
|
Velocity = gravity + move;
|
|
MoveAndSlide();
|
|
|
|
Push();
|
|
|
|
base._PhysicsProcess(delta);
|
|
}
|
|
|
|
private void ProceedCrouchingCheck(CollisionComponent collisionComponent) {
|
|
if (collisionComponent.IsCollide()) {
|
|
crouchLock = true;
|
|
MovingState = PlayerMovingState.Crouching;
|
|
} else crouchLock = false;
|
|
}
|
|
|
|
private void UpdatePlayerOrientation(float delta) {
|
|
var gravity = GetGravity();
|
|
if (gravity == Vector3.Zero) return;
|
|
|
|
UpDirection = -gravity;
|
|
var objectBottomDirection = -Transform.Basis.Y;
|
|
var angleToTarget = objectBottomDirection.AngleTo(gravity);
|
|
|
|
var rotationAxis = objectBottomDirection.Cross(gravity).Normalized();
|
|
|
|
var interpolatedAngle = Mathf.Lerp(0, angleToTarget, GetCurrentGravityStabilizationSpeed() * delta);
|
|
if (rotationAxis.IsNormalized())
|
|
Rotate(rotationAxis, interpolatedAngle);
|
|
}
|
|
|
|
private Vector3 _gravityVelocity = Vector3.Zero;
|
|
private Vector3 ProceedGravityVelocity(float delta) {
|
|
var localUp = GlobalTransform.Basis.Y;
|
|
var gravityForce = GetGravity();
|
|
|
|
if (IsOnFloor()) {
|
|
_gravityVelocity = gravityForce.Normalized() * 0.1f;
|
|
|
|
if (Input.IsActionPressed("move_jump"))
|
|
_gravityVelocity = localUp * JumpVelocity;
|
|
|
|
return _gravityVelocity;
|
|
}
|
|
|
|
if (IsOnCeiling() && GetSlideCollisionCount() > 0) {
|
|
var normal = GetLastSlideCollision().GetNormal();
|
|
if (_gravityVelocity.Dot(localUp) > 0 && normal.Dot(localUp) < -0.5f)
|
|
_gravityVelocity = _gravityVelocity.Bounce(normal) * CeilingBounce;
|
|
}
|
|
|
|
_gravityVelocity += gravityForce * delta;
|
|
return _gravityVelocity;
|
|
}
|
|
|
|
private Vector3 _cameraTargetPosition;
|
|
private Vector3 _moveVelocity = Vector3.Zero;
|
|
private Vector3 ProceedMoveVelocity(float delta) {
|
|
var movingState = Input.IsActionPressed("action_sprint") ? PlayerMovingState.Sprinting : PlayerMovingState.Walking;
|
|
movingState = Input.IsActionPressed("action_crouch") ? PlayerMovingState.Crouching : movingState;
|
|
MovingState = movingState;
|
|
|
|
Camera!.Position = Camera!.Position.Lerp(_cameraTargetPosition, CameraSpeed);
|
|
|
|
var inputDir = Input.GetVector("move_left", "move_right", "move_forward", "move_backward");
|
|
|
|
var dir = (GlobalTransform.Basis * new Vector3(inputDir.X, 0, inputDir.Y)).Normalized();
|
|
|
|
if (inputDir == Vector2.Zero)
|
|
_moveVelocity = _moveVelocity.Lerp(Vector3.Zero, GetCurrentFriction());
|
|
else
|
|
_moveVelocity = dir * GetCurrentSpeed();
|
|
|
|
return _moveVelocity;
|
|
}
|
|
|
|
private void Push() {
|
|
for (int i = 0; i < GetSlideCollisionCount(); i++) {
|
|
var collision = GetSlideCollision(i);
|
|
if (collision.GetCollider() is RigidBody3D body) {
|
|
var pushDir = -collision.GetNormal();
|
|
|
|
body.ApplyCentralImpulse(pushDir * _moveVelocity.Length() * PushImpulse);
|
|
}
|
|
}
|
|
}
|
|
}
|