Files
MovementTests/player_controller/Scripts/PlayerController.cs

534 lines
16 KiB
C#

using System;
using Godot;
using GodotStateCharts;
using Movementtests.systems;
using Movementtests.player_controller.Scripts;
public partial class PlayerController : CharacterBody3D
{
// User API to important child nodes.
public HeadSystem HeadSystem;
public Bobbing Bobbing;
public FieldOfView FieldOfView;
public Stamina Stamina;
public StairsSystem StairsSystem;
public MantleSystem MantleSystem;
public DashSystem DashSystem;
public CapsuleCollider CapsuleCollider;
public Gravity Gravity;
public HealthSystem HealthSystem;
public MoveSystem MoveSystem;
public TweenQueueSystem TweenQueueSystem;
public Node3D WeaponRoot;
public WeaponSystem WeaponSystem;
public WallHugSystem WallHugSystem;
private bool _movementEnabled = true;
private bool _shouldMantle;
private Vector3 _mantleLocation = Vector3.Zero;
private Vector3 _dashDirection = Vector3.Zero;
private float _lastFrameWasOnFloor = -Mathf.Inf;
private const int NUM_OF_HEAD_COLLISION_DETECTORS = 4;
private RayCast3D[] _headCollisionDetectors;
private Vector3 _inputMove = Vector3.Zero;
private float _inputRotateY;
private float _inputRotateFloorplane;
private bool _isAiming;
private bool _dashCanceled;
private Timer _coyoteTimer;
private StateChart _playerState;
// Actions state
private StateChartState _weaponInHand;
private StateChartState _aiming;
private StateChartState _dashing;
private StateChartState _weaponThrown;
private StateChartState _actionHanging;
// Movement state
private StateChartState _grounded;
private StateChartState _mantling;
private StateChartState _movHanging;
private StateChartState _wallHugging;
private StateChartState _airborne;
private StateChartState _coyoteEnabled;
private StateChartState _jump;
private StateChartState _jumpFromWall;
private StateChartState _doubleJumpEnabled;
private StateChartState _doubleJump;
private StateChartState _falling;
public override void _Ready()
{
///////////////////////////
// Getting components /////
///////////////////////////
// General use stuff
TweenQueueSystem = GetNode<TweenQueueSystem>("TweenQueueSystem");
// Node3D mapNode = GetTree().Root.FindChild("Map", true, false) as Node3D;
// Camera stuff
HeadSystem = GetNode<HeadSystem>("HeadSystem");
Bobbing = GetNode<Bobbing>("Bobbing");
FieldOfView = GetNode<FieldOfView>("FieldOfView");
Camera3D camera = GetNode<Camera3D>("HeadSystem/CameraSmooth/Camera3D");
Node3D cameraSmooth = GetNode<Node3D>("HeadSystem/CameraSmooth");
ColorRect vignetteRect = GetNode<ColorRect>(
"HeadSystem/CameraSmooth/Camera3D/CLVignette(Layer_1)/HealthVignetteRect");
ColorRect distortionRect = GetNode<ColorRect>(
"HeadSystem/CameraSmooth/Camera3D/CLDistortion(Layer_2)/HealthDistortionRect");
ColorRect blurRect = GetNode<ColorRect>("HeadSystem/CameraSmooth/Camera3D/CLBlur(Layer_2)/BlurRect");
// Movement stuff
WeaponRoot = GetNode<Node3D>("WeaponRoot");
WeaponSystem = GetNode<WeaponSystem>("WeaponRoot/WeaponSystem");
MantleSystem = GetNode<MantleSystem>("MantleSystem");
CapsuleCollider = GetNode<CapsuleCollider>("CapsuleCollider");
Gravity = GetNode<Gravity>("Gravity");
MoveSystem = GetNode<MoveSystem>("MoveSystem");
DashSystem = GetNode<DashSystem>("DashSystem");
StairsSystem = GetNode<StairsSystem>("StairsSystem");
WallHugSystem = GetNode<WallHugSystem>("WallHugSystem");
RayCast3D stairsBelowRayCast3D = GetNode<RayCast3D>("StairsBelowRayCast3D");
RayCast3D stairsAheadRayCast3D = GetNode<RayCast3D>("StairsAheadRayCast3D");
_headCollisionDetectors = new RayCast3D[NUM_OF_HEAD_COLLISION_DETECTORS];
for (int i = 0; i < NUM_OF_HEAD_COLLISION_DETECTORS; i++)
{
_headCollisionDetectors[i] = GetNode<RayCast3D>(
"HeadCollisionDetectors/HeadCollisionDetector" + i);
}
// RPG Stuff
Stamina = GetNode<Stamina>("Stamina");
HealthSystem = GetNode<HealthSystem>("HealthSystem");
// State management
_playerState = StateChart.Of(GetNode("StateChart"));
// Actions states
_weaponInHand = StateChartState.Of(GetNode("StateChart/Root/Actions/WeaponInHand"));
_aiming = StateChartState.Of(GetNode("StateChart/Root/Actions/Aiming"));
_dashing = StateChartState.Of(GetNode("StateChart/Root/Actions/Dashing"));
_weaponThrown = StateChartState.Of(GetNode("StateChart/Root/Actions/WeaponThrown"));
_actionHanging = StateChartState.Of(GetNode("StateChart/Root/Actions/Hanging"));
// Movement states
_grounded = StateChartState.Of(GetNode("StateChart/Root/Movement/Grounded"));
_mantling = StateChartState.Of(GetNode("StateChart/Root/Movement/Mantling"));
_movHanging = StateChartState.Of(GetNode("StateChart/Root/Movement/Hanging"));
_airborne = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne"));
_wallHugging = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/WallHugging"));
_coyoteEnabled = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/CoyoteEnabled"));
_jump = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/Jump"));
_jumpFromWall = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/JumpFromWall"));
_doubleJumpEnabled = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/DoubleJumpEnabled"));
_doubleJump = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/DoubleJump"));
_falling = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/Falling"));
// State timers
_coyoteTimer = GetNode<Timer>("CoyoteTime");
///////////////////////////
// Initialize components //
///////////////////////////
// General use stuff
TweenQueueSystem.Init(this);
// Camera stuff
HeadSystem.Init();
Bobbing.Init(camera);
FieldOfView.Init(camera);
// Movement stuff
// Getting universal setting from GODOT editor to be in sync
float gravitySetting = (float)ProjectSettings.GetSetting("physics/3d/default_gravity");
Gravity.Init(gravitySetting);
MantleSystem.Init(HeadSystem);
var moveSystemParams = new MoveSystem.MoveSystemParameters(this, Gravity, MantleSystem, TweenQueueSystem,
HeadSystem, CapsuleCollider);
MoveSystem.Init(moveSystemParams);
StairsSystem.Init(stairsBelowRayCast3D, stairsAheadRayCast3D, cameraSmooth);
DashSystem.Init(HeadSystem, camera, TweenQueueSystem);
WeaponSystem.Init(HeadSystem, camera);
WallHugSystem.Init();
// RPG Stuff
HealthSystem.HealthSystemInitParams healthSystemParams = new HealthSystem.HealthSystemInitParams()
{
Gravity = Gravity,
Parent = this,
Camera = camera,
Head = HeadSystem,
VignetteRect = vignetteRect,
DistortionRect = distortionRect,
BlurRect = blurRect,
};
HealthSystem.Init(healthSystemParams);
Stamina.SetSpeeds(MoveSystem.WalkSpeed, MoveSystem.SprintSpeed);
///////////////////////////
// Signal setup ///////////
///////////////////////////
DashSystem.DashEnded += OnDashEnded;
_weaponInHand.StateProcessing += HandleWeaponInHand;
_aiming.StateProcessing += HandleAiming;
_grounded.StatePhysicsProcessing += HandleGrounded;
_airborne.StatePhysicsProcessing += HandleAirborne;
_wallHugging.StatePhysicsProcessing += HandleWallHugging;
_coyoteEnabled.StateEntered += StartCoyoteTime;
_coyoteTimer.Timeout += CoyoteExpired;
_jump.StateEntered += Jump;
_jumpFromWall.StateEntered += JumpFromWall;
_doubleJump.StateEntered += DoubleJump;
_mantling.StateEntered += Mantle;
_dashing.StateEntered += OnDashStarted;
_weaponThrown.StateEntered += OnWeaponThrown;
}
///////////////////////////
// Input Management ///////
///////////////////////////
public void OnInputMove(Vector3 value)
{
_inputMove = value;
}
public void OnInputRotateY(float value)
{
_inputRotateY = value;
}
public void OnInputRotateFloorplane(float value)
{
_inputRotateFloorplane = value;
}
public void OnInputAimPressed()
{
_playerState.SendEvent("aim_pressed");
}
public void OnInputAimReleased()
{
_playerState.SendEvent("aim_released");
}
public void OnInputAimCanceled()
{
_playerState.SendEvent("aim_canceled");
DashSystem.CancelDash();
}
public void OnInputHitPressed()
{
_playerState.SendEvent("hit_pressed");
}
public void OnInputJumpPressed()
{
if (MoveSystem.CanMantle())
{
_playerState.SendEvent("mantle");
return;
}
_playerState.SendEvent("jump");
}
public void OnInputDropPressed()
{
_playerState.SendEvent("drop");
}
///////////////////////////
// Stateful logic /////////
///////////////////////////
// Jumping
public void StartCoyoteTime()
{
_coyoteTimer.Start();
}
public void CoyoteExpired()
{
_playerState.SendEvent("coyote_expired");
}
public void Jump()
{
_playerState.SendEvent("to_double_jump");
PerformJump(false);
}
public void JumpFromWall()
{
_playerState.SendEvent("to_double_jump");
var wallNormal = WallHugSystem.GetWallNormal();
if (wallNormal.IsSome(out var normal))
PerformJump(false, normal);
PerformJump(false);
}
public void DoubleJump()
{
_playerState.SendEvent("to_falling");
PerformJump(true);
}
private void PerformJump(bool isDoubleJump, Vector3? jumpDirection = null)
{
var effectiveJumpDirection = jumpDirection ?? Vector3.Up;
var jumpVector = (effectiveJumpDirection.Normalized() + Vector3.Up).Normalized();
bool doesCapsuleHaveCrouchingHeight = CapsuleCollider.IsCrouchingHeight();
bool isPlayerDead = HealthSystem.IsDead();
if (!doesCapsuleHaveCrouchingHeight && !isPlayerDead)
MoveSystem.Jump(isDoubleJump, jumpVector);
}
// Mantling
public void Mantle()
{
var optionTween = MoveSystem.Mantle();
if (optionTween.IsSome(out var tween))
tween.Finished += MantleFinished;
}
public void MantleFinished()
{
_playerState.SendEvent("to_grounded");
}
// Dashing and weapon throwing
public void OnDashStarted()
{
if (WeaponSystem.FlyingState.Active)
{
DashSystem.DashResolve = new DashResolveRecord(false, WeaponSystem.GlobalPosition, Vector3.Zero);
}
else if (WeaponSystem.PlantedState.Active)
{
// Should we try to resolve with mantle?
DashSystem.DashResolve = new DashResolveRecord(false, WeaponSystem.PlayerDashLocation, Vector3.Zero);
}
_dashDirection = (DashSystem.DashResolve.DashLocation - GlobalPosition).Normalized();
DashSystem.Dash();
}
public void OnDashEnded()
{
// Regular dash
if (WeaponSystem.InHandState.Active)
{
_playerState.SendEvent("dash_ended");
return;
}
// Store the weapon state before resetting it
var isPlantedOnWall = WeaponSystem.PlantedState.Active
&& (Math.Abs(WeaponSystem.GlobalRotation.X) + Math.Abs(WeaponSystem.GlobalRotation.Z) < 0.3);
var isFlying = WeaponSystem.FlyingState.Active;
// Get the weapon back
GetTree().GetRoot().RemoveChild(WeaponRoot);
AddChild(WeaponRoot);
WeaponRoot.SetGlobalPosition(GlobalPosition);
WeaponSystem.ResetWeapon();
if (isFlying)
{
var vel = _dashDirection * DashSystem.PostDashSpeed;
SetVelocity(vel);
_playerState.SendEvent("dash_ended");
return; // In case states aren't exclusives
}
if (isPlantedOnWall)
{
_playerState.SendEvent("dash_to_planted");
return; // In case states aren't exclusives
}
// Weapon planted anywhere else
_playerState.SendEvent("dash_ended");
}
public void OnWeaponThrown()
{
RemoveChild(WeaponRoot);
GetTree().GetRoot().AddChild(WeaponRoot);
WeaponRoot.SetGlobalPosition(GlobalPosition);
var (hasHit, location, collisionPoint, collisionNormal) = DashSystem.DashComputation;
var (endWithMantle, dashLocation, mantleLocation) = DashSystem.DashResolve;
DashSystem.CancelDash();
WeaponSystem.ThrowWeapon(location, hasHit, collisionPoint, collisionNormal);
}
// Regular processes
public void HandleWeaponInHand(float delta)
{
RotateWeaponWithPlayer();
}
public void HandleAiming(float delta)
{
RotateWeaponWithPlayer();
DashSystem.PrepareDash();
}
// Physics processes
public void HandleGrounded(float delta)
{
if (!isOnFloorCustom())
_playerState.SendEvent("start_falling");
}
public void HandleAirborne(float delta)
{
if (isOnFloorCustom())
_playerState.SendEvent("grounded");
if (WallHugSystem.IsWallHugging() && Velocity.Y < 0)
_playerState.SendEvent("wall_hug");
}
public void HandleWallHugging(float delta)
{
if (!WallHugSystem.IsWallHugging())
_playerState.SendEvent("start_falling");
}
///////////////////////////
// Stateless logic ////////
///////////////////////////
private void LookAround()
{
Vector2 inputLookDir = new Vector2(_inputRotateY, _inputRotateFloorplane);
HeadSystem.LookAround(inputLookDir);
}
private void MoveAround(double delta)
{
var moveAroundParams = new MoveSystem.MoveAroundParameters(
delta,
_inputMove,
isOnFloorCustom(),
HealthSystem.IsDead(),
IsHeadTouchingCeiling(),
_actionHanging.Active,
_wallHugging.Active);
MoveSystem.MoveAround(moveAroundParams);
}
private void HandleStairs(float delta)
{
StairsSystem.UpStairsCheckParams upStairsCheckParams = new StairsSystem.UpStairsCheckParams
{
IsOnFloorCustom = isOnFloorCustom(),
IsCapsuleHeightLessThanNormal = CapsuleCollider.IsCapsuleHeightLessThanNormal(),
CurrentSpeedGreaterThanWalkSpeed = MoveSystem._currentSpeed > MoveSystem.WalkSpeed,
IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
Delta = (float)delta,
FloorMaxAngle = FloorMaxAngle,
GlobalPositionFromDriver = GlobalPosition,
Velocity = Velocity,
GlobalTransformFromDriver = GlobalTransform,
Rid = GetRid()
};
StairsSystem.UpStairsCheckResult upStairsCheckResult = StairsSystem.SnapUpStairsCheck(upStairsCheckParams);
if (upStairsCheckResult.UpdateRequired)
{
upStairsCheckResult.Update(this);
}
else
{
MoveAndSlide();
StairsSystem.DownStairsCheckParams downStairsCheckParams = new StairsSystem.DownStairsCheckParams
{
IsOnFloor = IsOnFloor(), // TODO: replace on IsOnFloor Custom
IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
LastFrameWasOnFloor = _lastFrameWasOnFloor,
CapsuleDefaultHeight = CapsuleCollider.GetDefaultHeight(),
CurrentCapsuleHeight = CapsuleCollider.GetCurrentHeight(),
FloorMaxAngle = FloorMaxAngle,
VelocityY = Velocity.Y,
GlobalTransformFromDriver = GlobalTransform,
Rid = GetRid()
};
StairsSystem.DownStairsCheckResult downStairsCheckResult = StairsSystem.SnapDownStairsCheck(
downStairsCheckParams);
if (downStairsCheckResult.UpdateIsRequired)
{
downStairsCheckResult.Update(this);
}
}
StairsSystem.SlideCameraParams slideCameraParams = new StairsSystem.SlideCameraParams
{
IsCapsuleHeightLessThanNormal = CapsuleCollider.IsCapsuleHeightLessThanNormal(),
CurrentSpeedGreaterThanWalkSpeed = MoveSystem._currentSpeed > MoveSystem.WalkSpeed,
BetweenCrouchingAndNormalHeight = CapsuleCollider.IsBetweenCrouchingAndNormalHeight(),
Delta = (float)delta
};
StairsSystem.SlideCameraSmoothBackToOrigin(slideCameraParams);
}
private void CameraModifications(float delta)
{
Bobbing.CameraBobbingParams cameraBobbingParams = new Bobbing.CameraBobbingParams
{
Delta = delta,
IsOnFloorCustom = isOnFloorCustom(),
Velocity = Velocity
};
Bobbing.PerformCameraBobbing(cameraBobbingParams);
FieldOfView.FovParameters fovParams = new FieldOfView.FovParameters
{
IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
Delta = (float)delta,
SprintSpeed = MoveSystem.SprintSpeed,
Velocity = Velocity
};
FieldOfView.PerformFovAdjustment(fovParams);
}
///////////////////////////
// Helpers ////////////////
///////////////////////////
private bool IsHeadTouchingCeiling()
{
for (int i = 0; i < NUM_OF_HEAD_COLLISION_DETECTORS; i++)
{
if (_headCollisionDetectors[i].IsColliding())
{
return true;
}
}
return false;
}
private bool isOnFloorCustom()
{
return IsOnFloor() || StairsSystem.WasSnappedToStairsLastFrame();
}
public void RotateWeaponWithPlayer()
{
WeaponRoot.SetRotation(HeadSystem.Rotation);
}
///////////////////////////
// Processes //////////////
///////////////////////////
public override void _PhysicsProcess(double delta)
{
TweenQueueSystem.ProcessTweens();
LookAround();
MoveAround(delta);
CameraModifications((float) delta);
HandleStairs((float) delta);
}
}