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; private bool _movementEnabled = true; private bool _shouldMantle; private Vector3 _dashLocation = Vector3.Zero; private Vector3 _mantleLocation = 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 StateChart _playerState; private StateChartState _weaponInHand; private StateChartState _aiming; private StateChartState _dashing; private StateChartState _weaponThrown; public override void _Ready() { /////////////////////////// // Getting components ///// /////////////////////////// // General use stuff TweenQueueSystem = GetNode("TweenQueueSystem"); // Node3D mapNode = GetTree().Root.FindChild("Map", true, false) as Node3D; // Camera stuff HeadSystem = GetNode("HeadSystem"); Bobbing = GetNode("Bobbing"); FieldOfView = GetNode("FieldOfView"); Camera3D camera = GetNode("HeadSystem/CameraSmooth/Camera3D"); Node3D cameraSmooth = GetNode("HeadSystem/CameraSmooth"); ColorRect vignetteRect = GetNode( "HeadSystem/CameraSmooth/Camera3D/CLVignette(Layer_1)/HealthVignetteRect"); ColorRect distortionRect = GetNode( "HeadSystem/CameraSmooth/Camera3D/CLDistortion(Layer_2)/HealthDistortionRect"); ColorRect blurRect = GetNode("HeadSystem/CameraSmooth/Camera3D/CLBlur(Layer_2)/BlurRect"); // Movement stuff WeaponRoot = GetNode("WeaponRoot"); WeaponSystem = GetNode("WeaponRoot/WeaponSystem"); MantleSystem = GetNode("MantleSystem"); CapsuleCollider = GetNode("CapsuleCollider"); Gravity = GetNode("Gravity"); MoveSystem = GetNode("MoveSystem"); DashSystem = GetNode("DashSystem"); StairsSystem = GetNode("StairsSystem"); RayCast3D stairsBelowRayCast3D = GetNode("StairsBelowRayCast3D"); RayCast3D stairsAheadRayCast3D = GetNode("StairsAheadRayCast3D"); _headCollisionDetectors = new RayCast3D[NUM_OF_HEAD_COLLISION_DETECTORS]; for (int i = 0; i < NUM_OF_HEAD_COLLISION_DETECTORS; i++) { _headCollisionDetectors[i] = GetNode( "HeadCollisionDetectors/HeadCollisionDetector" + i); } // RPG Stuff Stamina = GetNode("Stamina"); HealthSystem = GetNode("HealthSystem"); // State management _playerState = StateChart.Of(GetNode("StateChart")); _weaponInHand = StateChartState.Of(GetNode("StateChart/Root/WeaponInHand")); _aiming = StateChartState.Of(GetNode("StateChart/Root/Aiming")); _dashing = StateChartState.Of(GetNode("StateChart/Root/Dashing")); _weaponThrown = StateChartState.Of(GetNode("StateChart/Root/WeaponThrown")); /////////////////////////// // 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); // 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; _dashing.StateEntered += OnDashStarted; _weaponThrown.StateEntered += OnWeaponThrown; } public void OnDashStarted() { DashSystem.Dash(); } 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); } public void OnDashEnded() { // Generates an error when dashing normally // This should solve itself when we handle weapon thrown dashes and regular dashes through different states GetTree().GetRoot().RemoveChild(WeaponRoot); AddChild(WeaponRoot); WeaponRoot.SetGlobalPosition(GlobalPosition); WeaponSystem.ResetWeapon(); _playerState.SendEvent("dash_ended"); } 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() { bool doesCapsuleHaveCrouchingHeight = CapsuleCollider.IsCrouchingHeight(); bool isPlayerDead = HealthSystem.IsDead(); if (!doesCapsuleHaveCrouchingHeight && !isPlayerDead) MoveSystem.Jump(IsOnFloor()); } public override void _PhysicsProcess(double delta) { var isPlayerDead = HealthSystem.IsDead(); var isHeadTouchingCeiling = IsHeadTouchingCeiling(); TweenQueueSystem.ProcessTweens(); if (_weaponInHand.Active || _aiming.Active) WeaponRoot.SetRotation(HeadSystem.Rotation); if (_aiming.Active) DashSystem.PrepareDash(); var moveAroundParams = new MoveSystem.MoveAroundParameters( delta, _inputMove, isOnFloorCustom(), isPlayerDead, isHeadTouchingCeiling); MoveSystem.MoveAround(moveAroundParams); Vector2 inputLookDir = new Vector2(_inputRotateY, _inputRotateFloorplane); HeadSystem.LookAround(inputLookDir); Bobbing.CameraBobbingParams cameraBobbingParams = new Bobbing.CameraBobbingParams { Delta = (float)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); 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() }; // TODO: SnapUpStairsCheck influences the ability of player to crouch because of `stepHeightY <= 0.01` part // Ideally, it should not. SnapUpStairsCheck and SnapDownStairsCheck should be called, when player is actually // on the stairs 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 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(); } }