Files
factory-game/source/Scripts/PlayerController.cs
2026-03-01 08:32:24 +03:00

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);
}
}
}
}