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