From 0c015750fde661c9d06759fd1052f940a61b0903 Mon Sep 17 00:00:00 2001 From: Minimata Date: Tue, 10 Jun 2025 11:46:51 +0200 Subject: [PATCH] gd: added wall hugging --- player_controller/PlayerController.tscn | 64 ++++++++++++++----- player_controller/Scripts/PlayerController.cs | 28 ++++++-- systems/move/MoveSystem.cs | 25 ++++++-- systems/wall_hug/WallHugSystem.cs | 45 +++++++++++++ systems/wall_hug/WallHugSystem.cs.uid | 1 + 5 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 systems/wall_hug/WallHugSystem.cs create mode 100644 systems/wall_hug/WallHugSystem.cs.uid diff --git a/player_controller/PlayerController.tscn b/player_controller/PlayerController.tscn index 13b5122..9f6a977 100644 --- a/player_controller/PlayerController.tscn +++ b/player_controller/PlayerController.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=34 format=3 uid="uid://bei4nhkf8lwdo"] +[gd_scene load_steps=35 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="Resource" uid="uid://bl5crtu1gkrtr" path="res://systems/inputs/base_mode/base_mode.tres" id="3_cresl"] @@ -29,6 +29,7 @@ [ext_resource type="Script" uid="uid://jk2jm1g6q853" path="res://addons/godot_state_charts/compound_state.gd" id="26_infe6"] [ext_resource type="Script" uid="uid://cytafq8i1y8qm" path="res://addons/godot_state_charts/atomic_state.gd" id="27_34snm"] [ext_resource type="Script" uid="uid://c1vp0ojjvaby1" path="res://addons/godot_state_charts/parallel_state.gd" id="27_infe6"] +[ext_resource type="Script" uid="uid://tjiji63wlom5" path="res://systems/wall_hug/WallHugSystem.cs" id="27_n7qhm"] [ext_resource type="Script" uid="uid://cf1nsco3w0mf6" path="res://addons/godot_state_charts/transition.gd" id="28_n7qhm"] [ext_resource type="PackedScene" uid="uid://ckm3d6k08a72u" path="res://systems/weapon/weapon.tscn" id="29_wv70j"] @@ -141,6 +142,26 @@ StraightThrowDuration = 0.07 [node name="CoyoteTime" type="Timer" parent="."] wait_time = 0.2 +[node name="WallHugSystem" type="Node3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) +script = ExtResource("27_n7qhm") + +[node name="back" type="RayCast3D" parent="WallHugSystem"] +target_position = Vector3(0, 0, 1) +collision_mask = 2 + +[node name="front" type="RayCast3D" parent="WallHugSystem"] +target_position = Vector3(0, 0, -1) +collision_mask = 2 + +[node name="right" type="RayCast3D" parent="WallHugSystem"] +target_position = Vector3(1, 0, 0) +collision_mask = 2 + +[node name="left" type="RayCast3D" parent="WallHugSystem"] +target_position = Vector3(-1, 0, 0) +collision_mask = 2 + [node name="StateChart" type="Node" parent="."] script = ExtResource("25_wv70j") metadata/_custom_type_script = "uid://couw105c3bde4" @@ -273,7 +294,7 @@ script = ExtResource("27_34snm") [node name="OnJump" type="Node" parent="StateChart/Root/Movement/Hanging"] script = ExtResource("28_n7qhm") -to = NodePath("../../Airborne/Jump") +to = NodePath("../../Airborne/JumpFromWall") event = &"jump" delay_in_seconds = "0.0" @@ -283,21 +304,6 @@ to = NodePath("../../Airborne/CoyoteEnabled") event = &"drop" delay_in_seconds = "0.0" -[node name="WallHugging" type="Node" parent="StateChart/Root/Movement"] -script = ExtResource("27_34snm") - -[node name="OnJump" type="Node" parent="StateChart/Root/Movement/WallHugging"] -script = ExtResource("28_n7qhm") -to = NodePath("../../Airborne/Jump") -event = &"jump" -delay_in_seconds = "0.0" - -[node name="OnDrop" type="Node" parent="StateChart/Root/Movement/WallHugging"] -script = ExtResource("28_n7qhm") -to = NodePath("../../Airborne/CoyoteEnabled") -event = &"drop" -delay_in_seconds = "0.0" - [node name="Airborne" type="Node" parent="StateChart/Root/Movement"] script = ExtResource("26_infe6") initial_state = NodePath("CoyoteEnabled") @@ -308,6 +314,21 @@ to = NodePath("../../Grounded") event = &"grounded" delay_in_seconds = "0.0" +[node name="OnWallHug" type="Node" parent="StateChart/Root/Movement/Airborne"] +script = ExtResource("28_n7qhm") +to = NodePath("../WallHugging") +event = &"wall_hug" +delay_in_seconds = "0.0" + +[node name="WallHugging" type="Node" parent="StateChart/Root/Movement/Airborne"] +script = ExtResource("27_34snm") + +[node name="OnJump" type="Node" parent="StateChart/Root/Movement/Airborne/WallHugging"] +script = ExtResource("28_n7qhm") +to = NodePath("../../JumpFromWall") +event = &"jump" +delay_in_seconds = "0.0" + [node name="CoyoteEnabled" type="Node" parent="StateChart/Root/Movement/Airborne"] script = ExtResource("27_34snm") @@ -332,6 +353,15 @@ to = NodePath("../../DoubleJumpEnabled") event = &"to_double_jump" delay_in_seconds = "0.0" +[node name="JumpFromWall" type="Node" parent="StateChart/Root/Movement/Airborne"] +script = ExtResource("27_34snm") + +[node name="ToDoubleJump" type="Node" parent="StateChart/Root/Movement/Airborne/JumpFromWall"] +script = ExtResource("28_n7qhm") +to = NodePath("../../DoubleJumpEnabled") +event = &"to_double_jump" +delay_in_seconds = "0.0" + [node name="DoubleJumpEnabled" type="Node" parent="StateChart/Root/Movement/Airborne"] script = ExtResource("27_34snm") diff --git a/player_controller/Scripts/PlayerController.cs b/player_controller/Scripts/PlayerController.cs index 4a55351..46f4f1f 100644 --- a/player_controller/Scripts/PlayerController.cs +++ b/player_controller/Scripts/PlayerController.cs @@ -21,6 +21,7 @@ public partial class PlayerController : CharacterBody3D public TweenQueueSystem TweenQueueSystem; public Node3D WeaponRoot; public WeaponSystem WeaponSystem; + public WallHugSystem WallHugSystem; private bool _movementEnabled = true; @@ -57,6 +58,7 @@ public partial class PlayerController : CharacterBody3D private StateChartState _airborne; private StateChartState _coyoteEnabled; private StateChartState _jump; + private StateChartState _jumpFromWall; private StateChartState _doubleJumpEnabled; private StateChartState _doubleJump; private StateChartState _falling; @@ -92,6 +94,7 @@ public partial class PlayerController : CharacterBody3D MoveSystem = GetNode("MoveSystem"); DashSystem = GetNode("DashSystem"); StairsSystem = GetNode("StairsSystem"); + WallHugSystem = GetNode("WallHugSystem"); RayCast3D stairsBelowRayCast3D = GetNode("StairsBelowRayCast3D"); RayCast3D stairsAheadRayCast3D = GetNode("StairsAheadRayCast3D"); _headCollisionDetectors = new RayCast3D[NUM_OF_HEAD_COLLISION_DETECTORS]; @@ -117,10 +120,11 @@ public partial class PlayerController : CharacterBody3D _grounded = StateChartState.Of(GetNode("StateChart/Root/Movement/Grounded")); _mantling = StateChartState.Of(GetNode("StateChart/Root/Movement/Mantling")); _movHanging = StateChartState.Of(GetNode("StateChart/Root/Movement/Hanging")); - _wallHugging = StateChartState.Of(GetNode("StateChart/Root/Movement/WallHugging")); _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")); @@ -150,6 +154,7 @@ public partial class PlayerController : CharacterBody3D StairsSystem.Init(stairsBelowRayCast3D, stairsAheadRayCast3D, cameraSmooth); DashSystem.Init(HeadSystem, camera, TweenQueueSystem); WeaponSystem.Init(HeadSystem, camera); + WallHugSystem.Init(); // RPG Stuff HealthSystem.HealthSystemInitParams healthSystemParams = new HealthSystem.HealthSystemInitParams() @@ -179,6 +184,7 @@ public partial class PlayerController : CharacterBody3D _coyoteEnabled.StateEntered += StartCoyoteTime; _coyoteTimer.Timeout += CoyoteExpired; _jump.StateEntered += Jump; + _jumpFromWall.StateEntered += JumpFromWall; _doubleJump.StateEntered += DoubleJump; _mantling.StateEntered += Mantle; @@ -252,17 +258,27 @@ public partial class PlayerController : CharacterBody3D _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) + 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); + MoveSystem.Jump(isDoubleJump, jumpVector); } // Mantling @@ -363,6 +379,8 @@ public partial class PlayerController : CharacterBody3D { if (isOnFloorCustom()) _playerState.SendEvent("grounded"); + if (WallHugSystem.IsWallHugging() && Velocity.Y < 0) + _playerState.SendEvent("wall_hug"); } /////////////////////////// @@ -382,7 +400,8 @@ public partial class PlayerController : CharacterBody3D isOnFloorCustom(), HealthSystem.IsDead(), IsHeadTouchingCeiling(), - _actionHanging.Active); + _actionHanging.Active, + _wallHugging.Active); MoveSystem.MoveAround(moveAroundParams); } @@ -502,4 +521,5 @@ public partial class PlayerController : CharacterBody3D CameraModifications((float) delta); HandleStairs((float) delta); } + } diff --git a/systems/move/MoveSystem.cs b/systems/move/MoveSystem.cs index ac461ab..6411578 100644 --- a/systems/move/MoveSystem.cs +++ b/systems/move/MoveSystem.cs @@ -20,7 +20,8 @@ public partial class MoveSystem : Node3D bool IsOnFloor, bool IsDead, bool IsHeadTouchingCeiling, - bool isHanging + bool isHanging, + bool isWallHugging ); [Export(PropertyHint.Range, "0,20,0.1,or_greater")] @@ -38,6 +39,8 @@ public partial class MoveSystem : Node3D public float CrouchTransitionSpeed { get; set; } = 20.0f; [Export(PropertyHint.Range, "0,5,0.1,or_greater")] public float DoubleJumpSpeedFactor { get; set; } = 2f; + [Export(PropertyHint.Range, "0,1,0.01,or_greater")] + public float WallHugGravityReducingFactor { get; set; } = 0.1f; private Gravity _gravity; @@ -60,7 +63,7 @@ public partial class MoveSystem : Node3D public void MoveAround(MoveAroundParameters param) { - var (delta, movementDirection, isOnFloor, isDead, isHeadTouchingCeiling, isHanging) = param; + var (delta, movementDirection, isOnFloor, isDead, isHeadTouchingCeiling, isHanging, isWallHugging) = param; var doesCapsuleHaveCrouchingHeight = _capsuleCollider.IsCrouchingHeight(); var doesCapsuleHaveDefaultHeight = _capsuleCollider.IsDefaultHeight(); @@ -71,6 +74,14 @@ public partial class MoveSystem : Node3D _parent.MoveAndSlide(); return; } + if (isWallHugging) + { + _parent.Velocity = new Vector3( + x: _parent.Velocity.X, + y: _parent.Velocity.Y - _gravity.CalculateGravityForce() * (float)delta * WallHugGravityReducingFactor, + z: _parent.Velocity.Z); + return; + } // Adding the gravity if (!isOnFloor) @@ -167,16 +178,16 @@ public partial class MoveSystem : Node3D } } - public void Jump(bool isDoubleJump) + public void Jump(bool isDoubleJump, Vector3? jumpDirection = null) { + var effectiveJumpDirection = jumpDirection ?? Vector3.Up; var jumpForce = isDoubleJump ? _gravity.CalculateJumpForce() * DoubleJumpSpeedFactor : _gravity.CalculateJumpForce(); - _parent.Velocity = new Vector3( - x: _parent.Velocity.X, - y: jumpForce, - z: _parent.Velocity.Z); + var currentHorizontalVelocity = new Vector3(_parent.Velocity.X, 0, _parent.Velocity.Z); + var jumpVelocity = jumpForce * effectiveJumpDirection; + _parent.Velocity = currentHorizontalVelocity + jumpVelocity; } public bool CanMantle() diff --git a/systems/wall_hug/WallHugSystem.cs b/systems/wall_hug/WallHugSystem.cs new file mode 100644 index 0000000..bfc3817 --- /dev/null +++ b/systems/wall_hug/WallHugSystem.cs @@ -0,0 +1,45 @@ +using Godot; +using System; +using System.Collections.Generic; +using RustyOptions; + +namespace Movementtests.systems; + +public partial class WallHugSystem : Node3D +{ + + private List _raycasts; + + public void Init() + { + _raycasts = new List(); + _raycasts.Add(GetNode("front")); + _raycasts.Add(GetNode("back")); + _raycasts.Add(GetNode("left")); + _raycasts.Add(GetNode("right")); + } + + public bool IsWallHugging() + { + foreach (RayCast3D raycast in _raycasts) + { + if (raycast.IsColliding()) + { + return true; + } + } + return false; + } + + public Option GetWallNormal() + { + foreach (RayCast3D raycast in _raycasts) + { + if (raycast.IsColliding()) + { + return raycast.GetCollisionNormal().Some(); + } + } + return Option.None; + } +} diff --git a/systems/wall_hug/WallHugSystem.cs.uid b/systems/wall_hug/WallHugSystem.cs.uid new file mode 100644 index 0000000..ad40742 --- /dev/null +++ b/systems/wall_hug/WallHugSystem.cs.uid @@ -0,0 +1 @@ +uid://tjiji63wlom5