refacto: moved systems from player controller physics process to their own signal based systems

This commit is contained in:
2025-06-02 17:58:40 +02:00
parent c3e2c974ca
commit 178553956d
13 changed files with 377 additions and 248 deletions

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=26 format=3 uid="uid://bei4nhkf8lwdo"] [gd_scene load_steps=28 format=3 uid="uid://bei4nhkf8lwdo"]
[ext_resource type="Script" uid="uid://bbbrf5ckydfna" path="res://player_controller/Scripts/PlayerController.cs" id="1_poq2x"] [ext_resource type="Script" uid="uid://bbbrf5ckydfna" path="res://player_controller/Scripts/PlayerController.cs" id="1_poq2x"]
[ext_resource type="Resource" uid="uid://bl5crtu1gkrtr" path="res://systems/inputs/walk_mode/base_mode.tres" id="3_cresl"] [ext_resource type="Resource" uid="uid://bl5crtu1gkrtr" path="res://systems/inputs/walk_mode/base_mode.tres" id="3_cresl"]
@ -14,12 +14,14 @@
[ext_resource type="Resource" uid="uid://7wm8ywvujwf" path="res://systems/inputs/walk_mode/aim_cancel.tres" id="9_5p2qc"] [ext_resource type="Resource" uid="uid://7wm8ywvujwf" path="res://systems/inputs/walk_mode/aim_cancel.tres" id="9_5p2qc"]
[ext_resource type="Script" uid="uid://bt0xv2q8iv1vn" path="res://player_controller/Scripts/Gravity.cs" id="9_lsueh"] [ext_resource type="Script" uid="uid://bt0xv2q8iv1vn" path="res://player_controller/Scripts/Gravity.cs" id="9_lsueh"]
[ext_resource type="Script" uid="uid://dwoppk8j5fxeg" path="res://player_controller/Scripts/DashSystem.cs" id="9_qu4wy"] [ext_resource type="Script" uid="uid://dwoppk8j5fxeg" path="res://player_controller/Scripts/DashSystem.cs" id="9_qu4wy"]
[ext_resource type="Resource" uid="uid://bdit2jy5gbpts" path="res://systems/inputs/walk_mode/jump.tres" id="10_4u7i3"]
[ext_resource type="Script" uid="uid://g8idirw62qe0" path="res://player_controller/Scripts/Bobbing.cs" id="10_7wk1w"] [ext_resource type="Script" uid="uid://g8idirw62qe0" path="res://player_controller/Scripts/Bobbing.cs" id="10_7wk1w"]
[ext_resource type="Script" uid="uid://c6bx47wr7fbdm" path="res://player_controller/Scripts/Mouse.cs" id="11_huhen"]
[ext_resource type="PackedScene" uid="uid://0ysqmqphq6mq" path="res://systems/head/head_system.tscn" id="11_rxwoh"] [ext_resource type="PackedScene" uid="uid://0ysqmqphq6mq" path="res://systems/head/head_system.tscn" id="11_rxwoh"]
[ext_resource type="Script" uid="uid://b6k73aj5povgv" path="res://player_controller/Scripts/FieldOfView.cs" id="12_m2mxi"] [ext_resource type="Script" uid="uid://b6k73aj5povgv" path="res://player_controller/Scripts/FieldOfView.cs" id="12_m2mxi"]
[ext_resource type="Script" uid="uid://b5nk6ntlps3x0" path="res://systems/inputs/input_system.gd" id="16_v31n3"] [ext_resource type="Script" uid="uid://b5nk6ntlps3x0" path="res://systems/inputs/input_system.gd" id="16_v31n3"]
[ext_resource type="Resource" uid="uid://htqvokm8mufq" path="res://systems/inputs/walk_mode/move.tres" id="17_h6vvl"] [ext_resource type="Resource" uid="uid://htqvokm8mufq" path="res://systems/inputs/walk_mode/move.tres" id="17_h6vvl"]
[ext_resource type="Script" uid="uid://dyy5njw6pxoh4" path="res://systems/move/MoveSystem.cs" id="20_rxwoh"]
[ext_resource type="PackedScene" uid="uid://dbe5f0p6lvqtr" path="res://systems/tween_queue/tween_queue_system.tscn" id="22_rpwev"]
[sub_resource type="CapsuleMesh" id="CapsuleMesh_xc2g5"] [sub_resource type="CapsuleMesh" id="CapsuleMesh_xc2g5"]
@ -33,8 +35,6 @@
[node name="Player" type="CharacterBody3D"] [node name="Player" type="CharacterBody3D"]
script = ExtResource("1_poq2x") script = ExtResource("1_poq2x")
WalkSpeed = 10.0
SprintSpeed = 15.0
[node name="InputController" type="Node3D" parent="."] [node name="InputController" type="Node3D" parent="."]
script = ExtResource("16_v31n3") script = ExtResource("16_v31n3")
@ -45,6 +45,7 @@ rotate_floorplane = ExtResource("5_4u7i3")
aim_pressed = ExtResource("7_cresl") aim_pressed = ExtResource("7_cresl")
aim_released = ExtResource("8_lhb11") aim_released = ExtResource("8_lhb11")
aim_canceled = ExtResource("9_5p2qc") aim_canceled = ExtResource("9_5p2qc")
jump = ExtResource("10_4u7i3")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."] [node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
@ -64,6 +65,9 @@ script = ExtResource("5_umw0l")
[node name="Stamina" type="Node3D" parent="."] [node name="Stamina" type="Node3D" parent="."]
script = ExtResource("6_lxtc4") script = ExtResource("6_lxtc4")
[node name="StairsSystem" type="Node3D" parent="."]
script = ExtResource("7_bmt5a")
[node name="StairsAheadRayCast3D" type="RayCast3D" parent="."] [node name="StairsAheadRayCast3D" type="RayCast3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, -0.828) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, -0.828)
target_position = Vector3(0, -0.55, 0) target_position = Vector3(0, -0.55, 0)
@ -94,19 +98,9 @@ surface_material_override/0 = SubResource("StandardMaterial3D_v31n3")
MantleEndLocationDistanceFromWall = 1.0 MantleEndLocationDistanceFromWall = 1.0
MantleHeightCastStart = 2.0 MantleHeightCastStart = 2.0
[node name="StairsSystem" type="Node3D" parent="."]
script = ExtResource("7_bmt5a")
[node name="Gravity" type="Node3D" parent="."]
script = ExtResource("9_lsueh")
AdditionalGravityPower = 5.0
[node name="Bobbing" type="Node3D" parent="."] [node name="Bobbing" type="Node3D" parent="."]
script = ExtResource("10_7wk1w") script = ExtResource("10_7wk1w")
[node name="Mouse" type="Node3D" parent="."]
script = ExtResource("11_huhen")
[node name="FieldOfView" type="Node3D" parent="."] [node name="FieldOfView" type="Node3D" parent="."]
script = ExtResource("12_m2mxi") script = ExtResource("12_m2mxi")
@ -128,9 +122,22 @@ target_position = Vector3(0, 1, 0)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.296, 1.4, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.296, 1.4, 0)
target_position = Vector3(0, 1, 0) target_position = Vector3(0, 1, 0)
[node name="MoveSystem" type="Node3D" parent="."]
script = ExtResource("20_rxwoh")
WalkSpeed = 10.0
SprintSpeed = 15.0
[node name="Gravity" type="Node3D" parent="."]
script = ExtResource("9_lsueh")
Weight = 10.0
StartVelocity = 4.0
[node name="TweenQueueSystem" parent="." instance=ExtResource("22_rpwev")]
[connection signal="input_aim_canceled" from="InputController" to="." method="OnInputAimCanceled"] [connection signal="input_aim_canceled" from="InputController" to="." method="OnInputAimCanceled"]
[connection signal="input_aim_pressed" from="InputController" to="." method="OnInputAimPressed"] [connection signal="input_aim_pressed" from="InputController" to="." method="OnInputAimPressed"]
[connection signal="input_aim_released" from="InputController" to="." method="OnInputAimReleased"] [connection signal="input_aim_released" from="InputController" to="." method="OnInputAimReleased"]
[connection signal="input_jump" from="InputController" to="." method="OnInputJumpPressed"]
[connection signal="input_move" from="InputController" to="." method="OnInputMove"] [connection signal="input_move" from="InputController" to="." method="OnInputMove"]
[connection signal="input_rotate_floorplane" from="InputController" to="." method="OnInputRotateFloorplane"] [connection signal="input_rotate_floorplane" from="InputController" to="." method="OnInputRotateFloorplane"]
[connection signal="input_rotate_y" from="InputController" to="." method="OnInputRotateY"] [connection signal="input_rotate_y" from="InputController" to="." method="OnInputRotateY"]

View File

@ -18,6 +18,6 @@ public partial class Gravity: Node3D
_gravity = gravitySetting; _gravity = gravitySetting;
} }
public float CalculateJumpForce() => Weight * (_gravity * (StartVelocity / AdditionalGravityPower)); public float CalculateJumpForce() => _gravity * (StartVelocity / AdditionalGravityPower);
public float CalculateGravityForce() => _gravity * Weight / 30.0f; public float CalculateGravityForce() => _gravity * Weight;
} }

View File

@ -18,37 +18,15 @@ public partial class PlayerController : CharacterBody3D
public CapsuleCollider CapsuleCollider; public CapsuleCollider CapsuleCollider;
public Gravity Gravity; public Gravity Gravity;
public HealthSystem HealthSystem; public HealthSystem HealthSystem;
public MoveSystem MoveSystem;
public TweenQueueSystem TweenQueueSystem;
[Export(PropertyHint.Range, "0,20,0.1,or_greater")]
public float WalkSpeed { get; set; } = 5.0f;
[Export(PropertyHint.Range, "0,20,0.1,or_greater")]
public float SprintSpeed { get; set; } = 7.2f;
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float CrouchSpeed { get; set; } = 2.5f;
[Export(PropertyHint.Range, "0,100,0.1,or_greater")]
public float CrouchTransitionSpeed { get; set; } = 20.0f;
[Export(PropertyHint.Range, "0,5,0.1,or_greater")]
public float DoubleJumpSpeedFactor { get; set; } = 2f;
private bool _canDoubleJump = true;
private bool _movementEnabled = true; private bool _movementEnabled = true;
private bool _isTweening = false;
private record TweenInputs(Vector3 Location, float Duration);
private Queue<TweenInputs> _tweenInputs = new Queue<TweenInputs>();
private bool _shouldMantle = false; private bool _shouldMantle = false;
private Vector3 _dashLocation = Vector3.Zero; private Vector3 _dashLocation = Vector3.Zero;
private Vector3 _mantleLocation = Vector3.Zero; private Vector3 _mantleLocation = Vector3.Zero;
private float _currentSpeed;
private const float DecelerationSpeedFactorFloor = 15.0f;
private const float DecelerationSpeedFactorAir = 7.0f;
private float _lastFrameWasOnFloor = -Mathf.Inf; private float _lastFrameWasOnFloor = -Mathf.Inf;
private const int NumOfHeadCollisionDetectors = 4; private const int NumOfHeadCollisionDetectors = 4;
@ -91,12 +69,18 @@ public partial class PlayerController : CharacterBody3D
GD.Print("Aim canceled"); GD.Print("Aim canceled");
} }
public void OnInputJumpPressed()
{
bool doesCapsuleHaveCrouchingHeight = CapsuleCollider.IsCrouchingHeight();
bool isPlayerDead = HealthSystem.IsDead();
if (!doesCapsuleHaveCrouchingHeight && !isPlayerDead)
MoveSystem.Jump(IsOnFloor());
}
public override void _Ready() public override void _Ready()
{ {
_currentSpeed = WalkSpeed;
HeadSystem = GetNode<HeadSystem>("HeadSystem"); HeadSystem = GetNode<HeadSystem>("HeadSystem");
HeadSystem.Init(); HeadSystem.Init();
@ -137,23 +121,31 @@ public partial class PlayerController : CharacterBody3D
FieldOfView = GetNode<FieldOfView>("FieldOfView"); FieldOfView = GetNode<FieldOfView>("FieldOfView");
FieldOfView.Init(camera); FieldOfView.Init(camera);
Stamina = GetNode<Stamina>("Stamina");
Stamina.SetSpeeds(WalkSpeed, SprintSpeed);
StairsSystem = GetNode<StairsSystem>("StairsSystem");
StairsSystem.Init(stairsBelowRayCast3D, stairsAheadRayCast3D, cameraSmooth);
MantleSystem = GetNode<MantleSystem>("MantleSystem");
MantleSystem.Init(HeadSystem);
DashSystem = GetNode<DashSystem>("DashSystem");
DashSystem.Init(HeadSystem, camera);
CapsuleCollider = GetNode<CapsuleCollider>("CapsuleCollider"); CapsuleCollider = GetNode<CapsuleCollider>("CapsuleCollider");
Gravity = GetNode<Gravity>("Gravity"); Gravity = GetNode<Gravity>("Gravity");
Gravity.Init(gravitySetting); Gravity.Init(gravitySetting);
MantleSystem = GetNode<MantleSystem>("MantleSystem");
MantleSystem.Init(HeadSystem);
TweenQueueSystem = GetNode<TweenQueueSystem>("TweenQueueSystem");
TweenQueueSystem.Init(this);
MoveSystem = GetNode<MoveSystem>("MoveSystem");
var moveSystemParams = new MoveSystem.MoveSystemParameters(this, Gravity, MantleSystem, TweenQueueSystem,
HeadSystem, CapsuleCollider);
MoveSystem.Init(moveSystemParams);
Stamina = GetNode<Stamina>("Stamina");
Stamina.SetSpeeds(MoveSystem.WalkSpeed, MoveSystem.SprintSpeed);
StairsSystem = GetNode<StairsSystem>("StairsSystem");
StairsSystem.Init(stairsBelowRayCast3D, stairsAheadRayCast3D, cameraSmooth);
DashSystem = GetNode<DashSystem>("DashSystem");
DashSystem.Init(HeadSystem, camera);
HealthSystem = GetNode<HealthSystem>("HealthSystem"); HealthSystem = GetNode<HealthSystem>("HealthSystem");
HealthSystem.HealthSystemInitParams healthSystemParams = new HealthSystem.HealthSystemInitParams() HealthSystem.HealthSystemInitParams healthSystemParams = new HealthSystem.HealthSystemInitParams()
@ -170,50 +162,9 @@ public partial class PlayerController : CharacterBody3D
HealthSystem.Init(healthSystemParams); HealthSystem.Init(healthSystemParams);
} }
private void DisableMovement()
{
_movementEnabled = false;
}
public void EnableMovement()
{
_movementEnabled = true;
}
public void EndTween()
{
EnableMovement();
_isTweening = false;
}
private void TweenToLocation(TweenInputs inputs)
{
var (location, duration) = inputs;
var tween = GetTree().CreateTween();
var callback = new Callable(this, MethodName.EndTween);
tween.TweenProperty(this, "position", location, duration);
tween.TweenCallback(callback);
DisableMovement();
_isTweening = true;
tween.Play();
}
private void QueueTween(TweenInputs inputs)
{
_tweenInputs.Enqueue(inputs);
}
private void QueueTween(Vector3 location, float duration)
{
QueueTween(new TweenInputs(location, duration));
}
public override void _PhysicsProcess(double delta) public override void _PhysicsProcess(double delta)
{ {
if (_tweenInputs.Count > 0 && !_isTweening) TweenQueueSystem.ProcessTweens();
TweenToLocation(_tweenInputs.Dequeue());
if (Input.IsActionPressed("aim_dash")) if (Input.IsActionPressed("aim_dash"))
{ {
@ -223,154 +174,20 @@ public partial class PlayerController : CharacterBody3D
if (Input.IsActionJustReleased("aim_dash")) if (Input.IsActionJustReleased("aim_dash"))
{ {
DashSystem.Dash(); DashSystem.Dash();
QueueTween(_dashLocation, 0.1f); TweenQueueSystem.QueueTween(_dashLocation, 0.1f);
if (_shouldMantle) if (_shouldMantle)
{ {
QueueTween(_mantleLocation, 0.1f); TweenQueueSystem.QueueTween(_mantleLocation, 0.1f);
}
}
var mantleLocationResult = MantleSystem.FindMantleInFrontOfPlayer();
if (isOnFloorCustom())
{
_lastFrameWasOnFloor = Engine.GetPhysicsFrames();
_canDoubleJump = true;
}
// Adding the gravity
if (!isOnFloorCustom())
{
Velocity = new Vector3(
x: Velocity.X,
y: Velocity.Y - (Gravity.CalculateGravityForce() * (float)delta),
z: Velocity.Z);
}
bool doesCapsuleHaveCrouchingHeight = CapsuleCollider.IsCrouchingHeight();
bool isPlayerDead = HealthSystem.IsDead();
// Handle Jump input
if (Input.IsActionJustPressed("jump")
&& !doesCapsuleHaveCrouchingHeight
&& !isPlayerDead)
{
if (mantleLocationResult.IsSome(out var mantleLocation))
{
var duration = 0.1f * mantleLocation.DistanceTo(Position);
QueueTween(mantleLocation, duration);
}
else if (isOnFloorCustom())
{
Velocity = new Vector3(
x: Velocity.X,
y: Gravity.CalculateJumpForce() * (float)delta,
z: Velocity.Z);
}
else if (!isOnFloorCustom()
&& _canDoubleJump)
{
_canDoubleJump = false;
Velocity = new Vector3(
x: Velocity.X,
y: Gravity.CalculateJumpForce() * (float)delta * DoubleJumpSpeedFactor,
z: Velocity.Z);
}
}
bool isHeadTouchingCeiling = IsHeadTouchingCeiling();
bool doesCapsuleHaveDefaultHeight = CapsuleCollider.IsDefaultHeight();
// The code below is required to quickly adjust player's position on Y-axis when there's a ceiling on the
// trajectory of player's jump and player is standing
if (isHeadTouchingCeiling && doesCapsuleHaveDefaultHeight)
{
Velocity = new Vector3(
x: Velocity.X,
y: Velocity.Y - 2.0f,
z: Velocity.Z);
}
if (!isPlayerDead)
{
// Used both for detecting the moment when we enter into crouching mode and the moment when we're already
// in the crouching mode
if (Input.IsActionPressed("crouch") ||
(doesCapsuleHaveCrouchingHeight && isHeadTouchingCeiling))
{
CapsuleCollider.Crouch((float)delta, CrouchTransitionSpeed);
_currentSpeed = CrouchSpeed;
}
// Used both for the moment when we exit the crouching mode and for the moment when we just walk
else
{
CapsuleCollider.UndoCrouching((float)delta, CrouchTransitionSpeed);
_currentSpeed = WalkSpeed;
} }
} }
// Each component of the boolean statement for sprinting is required var isPlayerDead = HealthSystem.IsDead();
if (Input.IsActionPressed("sprint") && !isHeadTouchingCeiling && var isHeadTouchingCeiling = IsHeadTouchingCeiling();
!doesCapsuleHaveCrouchingHeight && !isPlayerDead)
{ MoveSystem.MoveAround(delta, _inputMove, isOnFloorCustom(), isPlayerDead, isHeadTouchingCeiling);
_currentSpeed = SprintSpeed;
}
Vector2 inputLookDir = new Vector2(_inputRotateY, _inputRotateFloorplane); Vector2 inputLookDir = new Vector2(_inputRotateY, _inputRotateFloorplane);
HeadSystem.LookAround(inputLookDir); HeadSystem.LookAround(inputLookDir);
// Basis is a 3x4 matrix. It contains information about scaling and rotation of head.
// By multiplying our Vector3 by this matrix we're doing multiple things:
// a) We start to operate in global space;
// b) We're applying to Vector3 the current rotation of "head" object;
// c) We're applying to Vector3 the current scaling of "head" object;
Vector3 direction = HeadSystem.Transform.Basis * _inputMove;
if (isPlayerDead)
{
direction = Vector3.Zero;
}
if (isOnFloorCustom())
{
// Set velocity based on input direction when on the floor
if (direction.Length() > 0)
{
float availableSpeed = Stamina.AccountStamina(delta, _currentSpeed);
float newX = direction.X * availableSpeed;
float newZ = direction.Z * availableSpeed;
Velocity = new Vector3(newX, Velocity.Y, newZ);
}
// If there is no input, smoothly decelerate the character on the floor
else
{
float xDeceleration = Mathf.Lerp(Velocity.X, direction.X * _currentSpeed,
(float)delta * DecelerationSpeedFactorFloor);
float zDeceleration = Mathf.Lerp(Velocity.Z, direction.Z * _currentSpeed,
(float)delta * DecelerationSpeedFactorFloor);
Velocity = new Vector3(xDeceleration, Velocity.Y, zDeceleration);
}
}
else
{
float xDeceleration = Mathf.Lerp(Velocity.X, direction.X * _currentSpeed,
(float)delta * DecelerationSpeedFactorAir);
float zDeceleration = Mathf.Lerp(Velocity.Z, direction.Z * _currentSpeed,
(float)delta * DecelerationSpeedFactorAir);
Velocity = new Vector3(xDeceleration, Velocity.Y, zDeceleration);
}
if (isPlayerDead)
{
MoveAndSlide();
return;
}
Bobbing.CameraBobbingParams cameraBobbingParams = new Bobbing.CameraBobbingParams Bobbing.CameraBobbingParams cameraBobbingParams = new Bobbing.CameraBobbingParams
{ {
Delta = (float)delta, Delta = (float)delta,
@ -384,7 +201,7 @@ public partial class PlayerController : CharacterBody3D
{ {
IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(), IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
Delta = (float)delta, Delta = (float)delta,
SprintSpeed = SprintSpeed, SprintSpeed = MoveSystem.SprintSpeed,
Velocity = Velocity Velocity = Velocity
}; };
@ -394,7 +211,7 @@ public partial class PlayerController : CharacterBody3D
{ {
IsOnFloorCustom = isOnFloorCustom(), IsOnFloorCustom = isOnFloorCustom(),
IsCapsuleHeightLessThanNormal = CapsuleCollider.IsCapsuleHeightLessThanNormal(), IsCapsuleHeightLessThanNormal = CapsuleCollider.IsCapsuleHeightLessThanNormal(),
CurrentSpeedGreaterThanWalkSpeed = _currentSpeed > WalkSpeed, CurrentSpeedGreaterThanWalkSpeed = MoveSystem._currentSpeed > MoveSystem.WalkSpeed,
IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(), IsCrouchingHeight = CapsuleCollider.IsCrouchingHeight(),
Delta = (float)delta, Delta = (float)delta,
FloorMaxAngle = FloorMaxAngle, FloorMaxAngle = FloorMaxAngle,
@ -443,7 +260,7 @@ public partial class PlayerController : CharacterBody3D
StairsSystem.SlideCameraParams slideCameraParams = new StairsSystem.SlideCameraParams StairsSystem.SlideCameraParams slideCameraParams = new StairsSystem.SlideCameraParams
{ {
IsCapsuleHeightLessThanNormal = CapsuleCollider.IsCapsuleHeightLessThanNormal(), IsCapsuleHeightLessThanNormal = CapsuleCollider.IsCapsuleHeightLessThanNormal(),
CurrentSpeedGreaterThanWalkSpeed = _currentSpeed > WalkSpeed, CurrentSpeedGreaterThanWalkSpeed = MoveSystem._currentSpeed > MoveSystem.WalkSpeed,
BetweenCrouchingAndNormalHeight = CapsuleCollider.IsBetweenCrouchingAndNormalHeight(), BetweenCrouchingAndNormalHeight = CapsuleCollider.IsBetweenCrouchingAndNormalHeight(),
Delta = (float)delta Delta = (float)delta
}; };

View File

@ -12,6 +12,7 @@ extends Node3D
@export var aim_pressed:GUIDEAction @export var aim_pressed:GUIDEAction
@export var aim_released:GUIDEAction @export var aim_released:GUIDEAction
@export var aim_canceled:GUIDEAction @export var aim_canceled:GUIDEAction
@export var jump:GUIDEAction
signal input_move(value: Vector3) signal input_move(value: Vector3)
signal input_rotate_y(value: float) signal input_rotate_y(value: float)
@ -20,6 +21,7 @@ signal input_rotate_floorplane(value: float)
signal input_aim_pressed signal input_aim_pressed
signal input_aim_released signal input_aim_released
signal input_aim_canceled signal input_aim_canceled
signal input_jump
func _ready() -> void: func _ready() -> void:
GUIDE.enable_mapping_context(base_mode) GUIDE.enable_mapping_context(base_mode)
@ -27,8 +29,12 @@ func _ready() -> void:
aim_pressed.triggered.connect(on_input_aim_pressed) aim_pressed.triggered.connect(on_input_aim_pressed)
aim_released.triggered.connect(on_input_aim_released) aim_released.triggered.connect(on_input_aim_released)
aim_canceled.triggered.connect(on_input_aim_canceled) aim_canceled.triggered.connect(on_input_aim_canceled)
jump.triggered.connect(on_input_jump)
func on_input_jump():
input_jump.emit()
func on_input_aim_pressed(): func on_input_aim_pressed():
input_aim_pressed.emit() input_aim_pressed.emit()

View File

@ -1,4 +1,4 @@
[gd_resource type="Resource" script_class="GUIDEMappingContext" load_steps=53 format=3 uid="uid://bl5crtu1gkrtr"] [gd_resource type="Resource" script_class="GUIDEMappingContext" load_steps=58 format=3 uid="uid://bl5crtu1gkrtr"]
[ext_resource type="Script" uid="uid://cpplm41b5bt6m" path="res://addons/guide/guide_action_mapping.gd" id="1_0pi3k"] [ext_resource type="Script" uid="uid://cpplm41b5bt6m" path="res://addons/guide/guide_action_mapping.gd" id="1_0pi3k"]
[ext_resource type="Script" uid="uid://dsa1dnifd6w32" path="res://addons/guide/guide_mapping_context.gd" id="2_ho3ad"] [ext_resource type="Script" uid="uid://dsa1dnifd6w32" path="res://addons/guide/guide_mapping_context.gd" id="2_ho3ad"]
@ -22,6 +22,7 @@
[ext_resource type="Script" uid="uid://rvttn472ix6v" path="res://addons/guide/inputs/guide_input_joy_button.gd" id="19_2murt"] [ext_resource type="Script" uid="uid://rvttn472ix6v" path="res://addons/guide/inputs/guide_input_joy_button.gd" id="19_2murt"]
[ext_resource type="Script" uid="uid://brsxcrai2te83" path="res://addons/guide/triggers/guide_trigger_chorded_action.gd" id="20_xcfo4"] [ext_resource type="Script" uid="uid://brsxcrai2te83" path="res://addons/guide/triggers/guide_trigger_chorded_action.gd" id="20_xcfo4"]
[ext_resource type="Script" uid="uid://b52rqq28tuqpg" path="res://addons/guide/triggers/guide_trigger_pressed.gd" id="21_k8ji4"] [ext_resource type="Script" uid="uid://b52rqq28tuqpg" path="res://addons/guide/triggers/guide_trigger_pressed.gd" id="21_k8ji4"]
[ext_resource type="Resource" uid="uid://bdit2jy5gbpts" path="res://systems/inputs/walk_mode/jump.tres" id="22_ufouq"]
[sub_resource type="Resource" id="Resource_vkvga"] [sub_resource type="Resource" id="Resource_vkvga"]
script = ExtResource("4_oapce") script = ExtResource("4_oapce")
@ -52,6 +53,7 @@ triggers = Array[ExtResource("8_nf3uo")]([])
script = ExtResource("1_0pi3k") script = ExtResource("1_0pi3k")
action = ExtResource("2_p4e1v") action = ExtResource("2_p4e1v")
input_mappings = Array[ExtResource("3_ufouq")]([SubResource("Resource_1igva")]) input_mappings = Array[ExtResource("3_ufouq")]([SubResource("Resource_1igva")])
metadata/_guide_input_mappings_collapsed = false
[sub_resource type="Resource" id="Resource_05q5j"] [sub_resource type="Resource" id="Resource_05q5j"]
script = ExtResource("10_500v3") script = ExtResource("10_500v3")
@ -88,6 +90,7 @@ triggers = Array[ExtResource("8_nf3uo")]([])
script = ExtResource("1_0pi3k") script = ExtResource("1_0pi3k")
action = ExtResource("9_paxxe") action = ExtResource("9_paxxe")
input_mappings = Array[ExtResource("3_ufouq")]([SubResource("Resource_dew8i")]) input_mappings = Array[ExtResource("3_ufouq")]([SubResource("Resource_dew8i")])
metadata/_guide_input_mappings_collapsed = true
[sub_resource type="Resource" id="Resource_pf0ii"] [sub_resource type="Resource" id="Resource_pf0ii"]
script = ExtResource("10_500v3") script = ExtResource("10_500v3")
@ -124,8 +127,9 @@ triggers = Array[ExtResource("8_nf3uo")]([])
script = ExtResource("1_0pi3k") script = ExtResource("1_0pi3k")
action = ExtResource("13_3y0c4") action = ExtResource("13_3y0c4")
input_mappings = Array[ExtResource("3_ufouq")]([SubResource("Resource_qu2wi")]) input_mappings = Array[ExtResource("3_ufouq")]([SubResource("Resource_qu2wi")])
metadata/_guide_input_mappings_collapsed = true
[sub_resource type="Resource" id="Resource_lg6ir"] [sub_resource type="Resource" id="Resource_ufouq"]
script = ExtResource("10_500v3") script = ExtResource("10_500v3")
axis = 4 axis = 4
joy_index = -1 joy_index = -1
@ -140,7 +144,7 @@ override_action_settings = false
is_remappable = false is_remappable = false
display_name = "" display_name = ""
display_category = "" display_category = ""
input = SubResource("Resource_lg6ir") input = SubResource("Resource_ufouq")
modifiers = Array[ExtResource("5_j3mg7")]([]) modifiers = Array[ExtResource("5_j3mg7")]([])
triggers = Array[ExtResource("8_nf3uo")]([SubResource("Resource_n42ky")]) triggers = Array[ExtResource("8_nf3uo")]([SubResource("Resource_n42ky")])
@ -148,6 +152,7 @@ triggers = Array[ExtResource("8_nf3uo")]([SubResource("Resource_n42ky")])
script = ExtResource("1_0pi3k") script = ExtResource("1_0pi3k")
action = ExtResource("14_bi271") action = ExtResource("14_bi271")
input_mappings = Array[ExtResource("3_ufouq")]([SubResource("Resource_qbthx")]) input_mappings = Array[ExtResource("3_ufouq")]([SubResource("Resource_qbthx")])
metadata/_guide_input_mappings_collapsed = false
[sub_resource type="Resource" id="Resource_cqc4k"] [sub_resource type="Resource" id="Resource_cqc4k"]
script = ExtResource("10_500v3") script = ExtResource("10_500v3")
@ -172,6 +177,7 @@ triggers = Array[ExtResource("8_nf3uo")]([SubResource("Resource_vanwy")])
script = ExtResource("1_0pi3k") script = ExtResource("1_0pi3k")
action = ExtResource("16_34gm1") action = ExtResource("16_34gm1")
input_mappings = Array[ExtResource("3_ufouq")]([SubResource("Resource_bkx7d")]) input_mappings = Array[ExtResource("3_ufouq")]([SubResource("Resource_bkx7d")])
metadata/_guide_input_mappings_collapsed = true
[sub_resource type="Resource" id="Resource_lfx76"] [sub_resource type="Resource" id="Resource_lfx76"]
script = ExtResource("19_2murt") script = ExtResource("19_2murt")
@ -202,8 +208,33 @@ script = ExtResource("1_0pi3k")
action = ExtResource("18_4dlli") action = ExtResource("18_4dlli")
input_mappings = Array[ExtResource("3_ufouq")]([SubResource("Resource_4ee3d")]) input_mappings = Array[ExtResource("3_ufouq")]([SubResource("Resource_4ee3d")])
[sub_resource type="Resource" id="Resource_oapce"]
script = ExtResource("19_2murt")
button = 0
joy_index = -1
[sub_resource type="Resource" id="Resource_j3mg7"]
script = ExtResource("21_k8ji4")
actuation_threshold = 0.5
[sub_resource type="Resource" id="Resource_8w5gu"]
script = ExtResource("3_ufouq")
override_action_settings = false
is_remappable = false
display_name = ""
display_category = ""
input = SubResource("Resource_oapce")
modifiers = Array[ExtResource("5_j3mg7")]([])
triggers = Array[ExtResource("8_nf3uo")]([SubResource("Resource_j3mg7")])
metadata/_guide_triggers_collapsed = false
[sub_resource type="Resource" id="Resource_xt1x5"]
script = ExtResource("1_0pi3k")
action = ExtResource("22_ufouq")
input_mappings = Array[ExtResource("3_ufouq")]([SubResource("Resource_8w5gu")])
[resource] [resource]
script = ExtResource("2_ho3ad") script = ExtResource("2_ho3ad")
display_name = "" display_name = ""
mappings = Array[ExtResource("1_0pi3k")]([SubResource("Resource_88x08"), SubResource("Resource_tgr2g"), SubResource("Resource_iarn8"), SubResource("Resource_0hmrk"), SubResource("Resource_iihs4"), SubResource("Resource_0s4kt")]) mappings = Array[ExtResource("1_0pi3k")]([SubResource("Resource_88x08"), SubResource("Resource_tgr2g"), SubResource("Resource_iarn8"), SubResource("Resource_0hmrk"), SubResource("Resource_iihs4"), SubResource("Resource_0s4kt"), SubResource("Resource_xt1x5")])
metadata/_custom_type_script = "uid://dsa1dnifd6w32" metadata/_custom_type_script = "uid://dsa1dnifd6w32"

View File

@ -0,0 +1,14 @@
[gd_resource type="Resource" script_class="GUIDEAction" load_steps=2 format=3 uid="uid://bdit2jy5gbpts"]
[ext_resource type="Script" uid="uid://cluhc11vixkf1" path="res://addons/guide/guide_action.gd" id="1_pxv2l"]
[resource]
script = ExtResource("1_pxv2l")
name = &""
action_value_type = 0
block_lower_priority_actions = true
emit_as_godot_actions = false
is_remappable = false
display_name = ""
display_category = ""
metadata/_custom_type_script = "uid://cluhc11vixkf1"

View File

@ -17,6 +17,8 @@ public partial class MantleSystem: Node3D
private ShapeCast3D _wallInFrontCast3D; private ShapeCast3D _wallInFrontCast3D;
private ShapeCast3D _mantleCast3D; private ShapeCast3D _mantleCast3D;
private RayCast3D _mantleCheckCast3D; private RayCast3D _mantleCheckCast3D;
private Option<Vector3> _mantleLocation;
public void Init(Node3D head) public void Init(Node3D head)
{ {
@ -25,21 +27,29 @@ public partial class MantleSystem: Node3D
_mantleCast3D = GetNode<ShapeCast3D>("MantleCast3D"); _mantleCast3D = GetNode<ShapeCast3D>("MantleCast3D");
} }
public Option<Vector3> FindMantleInFrontOfPlayer() public override void _PhysicsProcess(double delta)
{ {
base._PhysicsProcess(delta);
_wallInFrontCast3D.SetRotation(new Vector3( _wallInFrontCast3D.SetRotation(new Vector3(
_wallInFrontCast3D.Rotation.X, _wallInFrontCast3D.Rotation.X,
_head.Rotation.Y, _head.Rotation.Y,
_wallInFrontCast3D.Rotation.Z)); _wallInFrontCast3D.Rotation.Z));
if (!_wallInFrontCast3D.IsColliding()) if (!_wallInFrontCast3D.IsColliding())
{ {
return Option<Vector3>.None; _mantleLocation = Option<Vector3>.None;
return;
} }
var collisionPoint = _wallInFrontCast3D.GetCollisionPoint(0); var collisionPoint = _wallInFrontCast3D.GetCollisionPoint(0);
var collisionNormal = _wallInFrontCast3D.GetCollisionNormal(0); var collisionNormal = _wallInFrontCast3D.GetCollisionNormal(0);
return FindMantleLocationAtPoint(collisionPoint, collisionNormal); _mantleLocation = FindMantleLocationAtPoint(collisionPoint, collisionNormal);
}
public Option<Vector3> FindMantleInFrontOfPlayer()
{
return _mantleLocation;
} }
public Option<Vector3> FindMantleLocationAtPoint(Vector3 point, Vector3 wallNormal) public Option<Vector3> FindMantleLocationAtPoint(Vector3 point, Vector3 wallNormal)

View File

@ -22,7 +22,7 @@ debug_shape_custom_color = Color(1, 0, 0, 1)
[node name="WallInFrontCast3D" type="ShapeCast3D" parent="."] [node name="WallInFrontCast3D" type="ShapeCast3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
shape = SubResource("CapsuleShape3D_qu4wy") shape = SubResource("CapsuleShape3D_qu4wy")
target_position = Vector3(0, 0, -1.5) target_position = Vector3(0, 0, -2)
max_results = 1 max_results = 1
collision_mask = 2 collision_mask = 2
debug_shape_custom_color = Color(0.911631, 0.11884, 0.656218, 1) debug_shape_custom_color = Color(0.911631, 0.11884, 0.656218, 1)

184
systems/move/MoveSystem.cs Normal file
View File

@ -0,0 +1,184 @@
using Godot;
using PolarBears.PlayerControllerAddon;
public partial class MoveSystem : Node3D
{
public record MoveSystemParameters(
CharacterBody3D Parent,
Gravity Gravity,
MantleSystem MantleSystem,
TweenQueueSystem TweenQueueSystem,
HeadSystem HeadSystem,
CapsuleCollider CapsuleCollider);
[Export(PropertyHint.Range, "0,20,0.1,or_greater")]
public float WalkSpeed { get; set; } = 5.0f;
[Export(PropertyHint.Range, "0,20,0.1,or_greater")]
public float SprintSpeed { get; set; } = 7.2f;
[Export(PropertyHint.Range, "0,10,0.1,or_greater")]
public float CrouchSpeed { get; set; } = 2.5f;
[Export(PropertyHint.Range, "0,100,0.1,or_greater")]
public float _currentSpeed;
private const float DecelerationSpeedFactorFloor = 15.0f;
private const float DecelerationSpeedFactorAir = 7.0f;
public float CrouchTransitionSpeed { get; set; } = 20.0f;
[Export(PropertyHint.Range, "0,5,0.1,or_greater")]
public float DoubleJumpSpeedFactor { get; set; } = 2f;
private bool _canDoubleJump = true;
private float _lastFrameWasOnFloor = -Mathf.Inf;
private Gravity _gravity;
private CharacterBody3D _parent;
private MantleSystem _mantleSystem;
private TweenQueueSystem _tweenQueueSystem;
private CapsuleCollider _capsuleCollider;
private HeadSystem _headSystem;
public void Init(MoveSystemParameters parameters)
{
_parent = parameters.Parent;
_gravity = parameters.Gravity;
_mantleSystem = parameters.MantleSystem;
_tweenQueueSystem = parameters.TweenQueueSystem;
_capsuleCollider = parameters.CapsuleCollider;
_headSystem = parameters.HeadSystem;
_currentSpeed = WalkSpeed;
}
public void MoveAround(double delta, Vector3 movementDirection, bool isOnFloor, bool isDead, bool isHeadTouchingCeiling)
{
var doesCapsuleHaveCrouchingHeight = _capsuleCollider.IsCrouchingHeight();
var doesCapsuleHaveDefaultHeight = _capsuleCollider.IsDefaultHeight();
// Adding the gravity
if (!isOnFloor)
{
_parent.Velocity = new Vector3(
x: _parent.Velocity.X,
y: _parent.Velocity.Y - (_gravity.CalculateGravityForce() * (float)delta),
z: _parent.Velocity.Z);
}
if (isOnFloor)
{
_lastFrameWasOnFloor = Engine.GetPhysicsFrames();
_canDoubleJump = true;
}
// The code below is required to quickly adjust player's position on Y-axis when there's a ceiling on the
// trajectory of player's jump and player is standing
if (isHeadTouchingCeiling && doesCapsuleHaveDefaultHeight)
{
_parent.Velocity = new Vector3(
x: _parent.Velocity.X,
y: _parent.Velocity.Y - 2.0f,
z: _parent.Velocity.Z);
}
if (!isDead)
{
// Used both for detecting the moment when we enter into crouching mode and the moment when we're already
// in the crouching mode
if (Input.IsActionPressed("crouch") ||
(doesCapsuleHaveCrouchingHeight && isHeadTouchingCeiling))
{
_capsuleCollider.Crouch((float)delta, CrouchTransitionSpeed);
_currentSpeed = CrouchSpeed;
}
// Used both for the moment when we exit the crouching mode and for the moment when we just walk
else
{
_capsuleCollider.UndoCrouching((float)delta, CrouchTransitionSpeed);
_currentSpeed = WalkSpeed;
}
}
// Each component of the boolean statement for sprinting is required
if (Input.IsActionPressed("sprint") && !isHeadTouchingCeiling &&
!doesCapsuleHaveCrouchingHeight && !isDead)
{
_currentSpeed = SprintSpeed;
}
// Basis is a 3x4 matrix. It contains information about scaling and rotation of head.
// By multiplying our Vector3 by this matrix we're doing multiple things:
// a) We start to operate in global space;
// b) We're applying to Vector3 the current rotation of "head" object;
// c) We're applying to Vector3 the current scaling of "head" object;
Vector3 direction = _headSystem.Transform.Basis * movementDirection;
if (isDead)
{
direction = Vector3.Zero;
}
if (isOnFloor)
{
// Set velocity based on input direction when on the floor
if (direction.Length() > 0)
{
float newX = direction.X * _currentSpeed;
float newZ = direction.Z * _currentSpeed;
_parent.Velocity = new Vector3(newX, _parent.Velocity.Y, newZ);
}
// If there is no input, smoothly decelerate the character on the floor
else
{
float xDeceleration = Mathf.Lerp(_parent.Velocity.X, direction.X * _currentSpeed,
(float)delta * DecelerationSpeedFactorFloor);
float zDeceleration = Mathf.Lerp(_parent.Velocity.Z, direction.Z * _currentSpeed,
(float)delta * DecelerationSpeedFactorFloor);
_parent.Velocity = new Vector3(xDeceleration, _parent.Velocity.Y, zDeceleration);
}
}
else
{
float xDeceleration = Mathf.Lerp(_parent.Velocity.X, direction.X * _currentSpeed,
(float)delta * DecelerationSpeedFactorAir);
float zDeceleration = Mathf.Lerp(_parent.Velocity.Z, direction.Z * _currentSpeed,
(float)delta * DecelerationSpeedFactorAir);
_parent.Velocity = new Vector3(xDeceleration, _parent.Velocity.Y, zDeceleration);
}
if (isDead)
{
_parent.MoveAndSlide();
return;
}
}
public void Jump(bool isOnFloor)
{
var mantleLocationResult = _mantleSystem.FindMantleInFrontOfPlayer();
if (mantleLocationResult.IsSome(out var mantleLocation))
{
var duration = 0.1f * mantleLocation.DistanceTo(_parent.Position);
_tweenQueueSystem.QueueTween(mantleLocation, duration);
}
else if (isOnFloor)
{
_parent.Velocity = new Vector3(
x: _parent.Velocity.X,
y: _gravity.CalculateJumpForce(),
z: _parent.Velocity.Z);
}
else if (_canDoubleJump)
{
_canDoubleJump = false;
_parent.Velocity = new Vector3(
x: _parent.Velocity.X,
y: _gravity.CalculateJumpForce() * DoubleJumpSpeedFactor,
z: _parent.Velocity.Z);
}
}
}

View File

@ -0,0 +1 @@
uid://dyy5njw6pxoh4

View File

@ -0,0 +1,52 @@
using Godot;
using System.Collections.Generic;
public partial class TweenQueueSystem : Node3D
{
public record TweenInputs(Vector3 Location, float Duration);
private Queue<TweenInputs> _tweenInputs = new Queue<TweenInputs>();
private Node3D _tweenObject;
private bool _isTweening = false;
public void Init(Node3D tweenObject)
{
_tweenObject = tweenObject;
}
public void EndTween()
{
_isTweening = false;
}
private void TweenToLocation(TweenInputs inputs)
{
var (location, duration) = inputs;
var tween = GetTree().CreateTween();
var callback = new Callable(this, MethodName.EndTween);
tween.TweenProperty(_tweenObject, "position", location, duration);
tween.TweenCallback(callback);
_isTweening = true;
tween.Play();
}
public void QueueTween(TweenInputs inputs)
{
_tweenInputs.Enqueue(inputs);
}
public void QueueTween(Vector3 location, float duration)
{
QueueTween(new TweenInputs(location, duration));
}
public void ProcessTweens()
{
if (_tweenInputs.Count > 0 && !_isTweening)
TweenToLocation(_tweenInputs.Dequeue());
}
}

View File

@ -0,0 +1 @@
uid://crm4u4r56hvg7

View File

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://dbe5f0p6lvqtr"]
[ext_resource type="Script" uid="uid://crm4u4r56hvg7" path="res://systems/tween_queue/TweenQueueSystem.cs" id="1_iqosd"]
[node name="TweenQueueSystem" type="Node3D"]
script = ExtResource("1_iqosd")