From 2e2df4ff50a96c912305066a676a569966657821 Mon Sep 17 00:00:00 2001 From: Minimata Date: Wed, 17 Dec 2025 16:39:10 +0100 Subject: [PATCH] new broken mantle system and fixed lots of gamefeel regarding wall hugs and jumps and coyote time --- maps/GYMs/metrics.tscn | 204 +++++++++++++++++- player_controller/PlayerController.tscn | 42 +++- player_controller/Scripts/PlayerController.cs | 134 +++++++----- systems/dash/dash_system.tscn | 3 + systems/mantle/MantleSystem.cs | 89 +++++++- systems/mantle/find_wall_shape.tres | 4 + systems/mantle/mantle_system.tscn | 71 +++++- 7 files changed, 478 insertions(+), 69 deletions(-) create mode 100644 systems/mantle/find_wall_shape.tres diff --git a/maps/GYMs/metrics.tscn b/maps/GYMs/metrics.tscn index c641b8b4..11dbfe1d 100644 --- a/maps/GYMs/metrics.tscn +++ b/maps/GYMs/metrics.tscn @@ -50,7 +50,7 @@ collision_layer = 3 collision_mask = 5 [node name="CSGBox3D" type="CSGBox3D" parent="Greybox"] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.09619, -0.472656, -46.3293) +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.096, -0.5, -46.329) use_collision = true size = Vector3(100, 1, 190.741) material = ExtResource("3_vvhq3") @@ -400,3 +400,205 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12, 4.5, -11.5) use_collision = true size = Vector3(1, 0.5, 5) material = ExtResource("3_vvhq3") + +[node name="Mantles" type="CSGCombiner3D" parent="Greybox"] + +[node name="Label3D22" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, -5.5, 0.1, 11) +text = "0.5m" + +[node name="Label3D35" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, -8.5, 0.1, 11) +text = "0.25m" + +[node name="Label3D23" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, -2, 0.1, 11) +text = "1m" + +[node name="Label3D27" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, -11, 0.1, 12.5) +text = "1m" + +[node name="Label3D28" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, 13, 0.1, 12.5) +text = "1m" + +[node name="Label3D29" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, 13, 0.1, 21) +text = "2m" + +[node name="Label3D30" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, 13, 0.1, 28.5) +text = "3m" + +[node name="Label3D31" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, -11, 0.1, 21) +text = "2m" + +[node name="Label3D32" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, -11, 0.1, 28.5) +text = "3m" + +[node name="Label3D24" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, 2, 0.1, 11) +text = "1.5m" + +[node name="Label3D25" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, 6, 0.1, 11) +text = "2m" + +[node name="Label3D26" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, 10, 0.1, 11) +text = "4m" + +[node name="CSGBox3D33" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0.5, 12.5) +use_collision = true +size = Vector3(4, 1, 1) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D34" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.5, 0.5, 12.25) +use_collision = true +size = Vector3(3, 1, 0.5) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D35" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0.5, 12.75) +use_collision = true +size = Vector3(4, 1, 1.5) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D36" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6, 0.5, 13) +use_collision = true +size = Vector3(4, 1, 2) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D37" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, 0.5, 14) +use_collision = true +size = Vector3(4, 1, 4) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D38" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 1, 20.5) +use_collision = true +size = Vector3(4, 2, 1) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D39" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.5, 1, 20.25) +use_collision = true +size = Vector3(3, 2, 0.5) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D40" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 1, 20.75) +use_collision = true +size = Vector3(4, 2, 1.5) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D41" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6, 1, 21) +use_collision = true +size = Vector3(4, 2, 2) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D42" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, 1, 22) +use_collision = true +size = Vector3(4, 2, 4) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D43" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 1.5, 28.5) +use_collision = true +size = Vector3(4, 3, 1) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D44" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.5, 1.5, 28.25) +use_collision = true +size = Vector3(3, 3, 0.5) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D45" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 1.5, 28.75) +use_collision = true +size = Vector3(4, 3, 1.5) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D46" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6, 1.5, 29) +use_collision = true +size = Vector3(4, 3, 2) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D47" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, 1.5, 30) +use_collision = true +size = Vector3(4, 3, 4) +material = ExtResource("3_vvhq3") + +[node name="Label3D33" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, 13, 0.1, 36.5) +text = "4m" + +[node name="Label3D34" type="Label3D" parent="Greybox/Mantles"] +transform = Transform3D(-5, 4.3711395e-07, 1.9106861e-14, 0, -2.18557e-07, 5, 4.3711395e-07, 5, 2.18557e-07, -11, 0.1, 36.5) +text = "4m" + +[node name="CSGBox3D48" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 2, 36.5) +use_collision = true +size = Vector3(4, 4, 1) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D49" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.5, 2, 36.25) +use_collision = true +size = Vector3(3, 4, 0.5) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D53" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.5, 0.5, 12.125) +use_collision = true +size = Vector3(3, 1, 0.25) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D54" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.5, 1, 20.125) +use_collision = true +size = Vector3(3, 2, 0.25) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D55" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.5, 1.5, 28.125) +use_collision = true +size = Vector3(3, 3, 0.25) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D56" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.5, 2, 36.125) +use_collision = true +size = Vector3(3, 4, 0.25) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D50" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 2, 36.75) +use_collision = true +size = Vector3(4, 4, 1.5) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D51" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6, 2, 37) +use_collision = true +size = Vector3(4, 4, 2) +material = ExtResource("3_vvhq3") + +[node name="CSGBox3D52" type="CSGBox3D" parent="Greybox/Mantles"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10, 2, 38) +use_collision = true +size = Vector3(4, 4, 4) +material = ExtResource("3_vvhq3") diff --git a/player_controller/PlayerController.tscn b/player_controller/PlayerController.tscn index b5814ab4..9867d443 100644 --- a/player_controller/PlayerController.tscn +++ b/player_controller/PlayerController.tscn @@ -133,6 +133,11 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6, 0) CameraInclineAcceleration = 20.0 GroundedCameraIncline = 3.0 +[node name="MantleSystem" parent="HeadSystem" instance=ExtResource("8_qu4wy")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.6, 0) +MantleEndLocationDistanceFromWall = 0.3 +MantleHeightCastStart = 2.5 + [node name="StairsSystem" type="Node3D" parent="."] script = ExtResource("7_bmt5a") @@ -143,10 +148,6 @@ target_position = Vector3(0, -0.55, 0) [node name="StairsBelowRayCast3D" type="RayCast3D" parent="."] target_position = Vector3(0, -0.75, 0) -[node name="MantleSystem" parent="." instance=ExtResource("8_qu4wy")] -MantleEndLocationDistanceFromWall = 0.3 -MantleHeightCastStart = 2.0 - [node name="Bobbing" type="Node3D" parent="."] script = ExtResource("10_7wk1w") @@ -154,6 +155,7 @@ script = ExtResource("10_7wk1w") script = ExtResource("12_m2mxi") [node name="HeadCollisionDetectors" type="Node3D" parent="."] +visible = false [node name="HeadCollisionDetector0" type="RayCast3D" parent="HeadCollisionDetectors"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.4, -0.210707) @@ -174,6 +176,7 @@ target_position = Vector3(0, 1, 0) [node name="TweenQueueSystem" parent="." instance=ExtResource("22_rpwev")] [node name="WallHugSystem" type="Node3D" parent="."] +visible = false script = ExtResource("27_n7qhm") [node name="back" type="RayCast3D" parent="WallHugSystem"] @@ -243,7 +246,6 @@ one_shot = true ignore_time_scale = true [node name="StateChartDebugger" parent="." instance=ExtResource("24_q5h8a")] -visible = false offset_left = 1524.0 offset_top = 1.0 offset_right = -8.0 @@ -602,12 +604,6 @@ initial_state = NodePath("CoyoteEnabled") script = ExtResource("41_ruloh") default_state = NodePath("../CoyoteEnabled") -[node name="OnWallHug" type="Node" parent="StateChart/Root/Movement/Airborne"] -script = ExtResource("28_n7qhm") -to = NodePath("../../OnWall/HuggingCoyoteEnabled") -event = &"wall_hug" -delay_in_seconds = "0.0" - [node name="OnWallRun" type="Node" parent="StateChart/Root/Movement/Airborne"] script = ExtResource("28_n7qhm") to = NodePath("../../OnWall/RunningCoyoteEnabled") @@ -650,6 +646,12 @@ delay_in_seconds = "0.0" [node name="DoubleJumpEnabled" type="Node" parent="StateChart/Root/Movement/Airborne"] script = ExtResource("27_34snm") +[node name="OnWallHug" type="Node" parent="StateChart/Root/Movement/Airborne/DoubleJumpEnabled"] +script = ExtResource("28_n7qhm") +to = NodePath("../../../OnWall/HuggingCoyoteEnabled") +event = &"wall_hug" +delay_in_seconds = "0.0" + [node name="OnMegajump" type="Node" parent="StateChart/Root/Movement/Airborne/DoubleJumpEnabled"] script = ExtResource("28_n7qhm") to = NodePath("../../../Jump/MegaJump") @@ -665,6 +667,12 @@ delay_in_seconds = "0.0" [node name="Falling" type="Node" parent="StateChart/Root/Movement/Airborne"] script = ExtResource("27_34snm") +[node name="OnWallHug" type="Node" parent="StateChart/Root/Movement/Airborne/Falling"] +script = ExtResource("28_n7qhm") +to = NodePath("../../../OnWall/HuggingCoyoteEnabled") +event = &"wall_hug" +delay_in_seconds = "0.0" + [node name="ToDoubleJump" type="Node" parent="StateChart/Root/Movement/Airborne/Falling"] script = ExtResource("28_n7qhm") to = NodePath("../../DoubleJumpEnabled") @@ -708,6 +716,12 @@ to = NodePath("../../Hugging") event = &"coyote_expired" delay_in_seconds = "0.0" +[node name="OnJump" type="Node" parent="StateChart/Root/Movement/OnWall/HuggingCoyoteEnabled"] +script = ExtResource("28_n7qhm") +to = NodePath("../../../Jump/DoubleJump") +event = &"jump" +delay_in_seconds = "0.0" + [node name="Hugging" type="Node" parent="StateChart/Root/Movement/OnWall"] script = ExtResource("27_34snm") @@ -729,6 +743,12 @@ delay_in_seconds = "0.0" [node name="RunningCoyoteEnabled" type="Node" parent="StateChart/Root/Movement/OnWall"] script = ExtResource("27_34snm") +[node name="OnJump" type="Node" parent="StateChart/Root/Movement/OnWall/RunningCoyoteEnabled"] +script = ExtResource("28_n7qhm") +to = NodePath("../../../Jump/DoubleJump") +event = &"jump" +delay_in_seconds = "0.0" + [node name="OnExpiration" type="Node" parent="StateChart/Root/Movement/OnWall/RunningCoyoteEnabled"] script = ExtResource("28_n7qhm") to = NodePath("../../Running") diff --git a/player_controller/Scripts/PlayerController.cs b/player_controller/Scripts/PlayerController.cs index 1ae5d315..fc100337 100644 --- a/player_controller/Scripts/PlayerController.cs +++ b/player_controller/Scripts/PlayerController.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Godot; using GodotStateCharts; using Movementtests.addons.godot_state_charts.csharp; @@ -15,7 +14,7 @@ public partial class PlayerController : CharacterBody3D MoveCamera, None, } - private bool _isUsingGamepad = false; + private bool _isUsingGamepad; // User API to important child nodes. public HeadSystem HeadSystem; @@ -38,9 +37,11 @@ public partial class PlayerController : CharacterBody3D private bool _movementEnabled = true; + private Vector3 _dashDirection = Vector3.Zero; + private bool _shouldMantle; private Vector3 _mantleLocation = Vector3.Zero; - private Vector3 _dashDirection = Vector3.Zero; + private Vector3 _preMantleVelocity = Vector3.Zero; private float _lastFrameWasOnFloor = -Mathf.Inf; @@ -196,9 +197,11 @@ public partial class PlayerController : CharacterBody3D private StateChartState _grounded; private StateChartState _airborne; private StateChartState _coyoteEnabled; + private StateChartState _doubleJumpEnabled; private StateChartState _simpleJump; private StateChartState _doubleJump; private StateChartState _megaJump; + private StateChartState _mantling; private StateChartState _simpleDash; private StateChartState _poweredDash; private StateChartState _aimedDash; @@ -208,7 +211,9 @@ public partial class PlayerController : CharacterBody3D private StateChartState _onWallHanging; private StateChartState _onWallRunning; private StateChartState _onWallRunningCoyoteEnabled; - + + private Transition _onJumpFromWallCoyote; + private Transition _onJumpFromWallRunCoyote; private Transition _onJumpFromWall1; private Transition _onJumpFromWall2; private Transition _onJumpFromWall3; @@ -252,9 +257,9 @@ public partial class PlayerController : CharacterBody3D Node3D cameraSmooth = GetNode("HeadSystem/CameraSmooth"); // Movement stuff - WeaponRoot = GetNode("WeaponRoot"); + WeaponRoot = GetNode("WeaponRoot"); WeaponSystem = GetNode("WeaponRoot/WeaponSystem"); - MantleSystem = GetNode("MantleSystem"); + MantleSystem = GetNode("HeadSystem/MantleSystem"); CapsuleCollider = GetNode("CapsuleCollider"); DashSystem = GetNode("DashSystem"); StairsSystem = GetNode("StairsSystem"); @@ -284,14 +289,18 @@ public partial class PlayerController : CharacterBody3D _empowerOn = StateChartState.Of(GetNode("StateChart/Root/Empower/On")); _powerExpired = StateChartState.Of(GetNode("StateChart/Root/PowerReserve/Expired")); _powerRecharging = StateChartState.Of(GetNode("StateChart/Root/PowerReserve/AtLeastOneCharge")); - _powerFull = StateChartState.Of(GetNode("StateChart/Root/PowerReserve/Full")); + _powerFull = StateChartState.Of(GetNode("StateChart/Root/PowerReserve/Full")); _grounded = StateChartState.Of(GetNode("StateChart/Root/Movement/Grounded")); _airborne = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne")); _coyoteEnabled = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/CoyoteEnabled")); + _doubleJumpEnabled = StateChartState.Of(GetNode("StateChart/Root/Movement/Airborne/DoubleJumpEnabled")); _simpleJump = StateChartState.Of(GetNode("StateChart/Root/Movement/Jump/SimpleJump")); _doubleJump = StateChartState.Of(GetNode("StateChart/Root/Movement/Jump/DoubleJump")); _megaJump = StateChartState.Of(GetNode("StateChart/Root/Movement/Jump/MegaJump")); + _mantling = StateChartState.Of(GetNode("StateChart/Root/Movement/Mantling")); + _onJumpFromWallCoyote = Transition.Of(GetNode("StateChart/Root/Movement/OnWall/HuggingCoyoteEnabled/OnJump")); + _onJumpFromWallRunCoyote = Transition.Of(GetNode("StateChart/Root/Movement/OnWall/RunningCoyoteEnabled/OnJump")); _onJumpFromWall1 = Transition.Of(GetNode("StateChart/Root/Movement/OnWall/Hugging/OnJump")); _onJumpFromWall2 = Transition.Of(GetNode("StateChart/Root/Movement/OnWall/Hanging/OnJump")); _onJumpFromWall3 = Transition.Of(GetNode("StateChart/Root/Movement/OnWall/Running/OnJump")); @@ -362,6 +371,9 @@ public partial class PlayerController : CharacterBody3D _megaJump.StateEntered += OnMegaJumpStarted; _megaJump.StatePhysicsProcessing += HandleMegaJump; + _mantling.StateEntered += OnMantleStarted; + _mantling.StatePhysicsProcessing += HandleMantling; + _simpleDash.StateEntered += OnSimpleDashStarted; _simpleDash.StatePhysicsProcessing += HandleSimpleDash; @@ -383,7 +395,9 @@ public partial class PlayerController : CharacterBody3D _onWallRunningCoyoteEnabled.StateEntered += OnWallRunningStarted; _onWallRunningCoyoteEnabled.StatePhysicsProcessing += HandleWallRunning; _onWallRunning.StatePhysicsProcessing += HandleWallRunning; - + + _onJumpFromWallCoyote.Taken += OnJumpFromWallCoyote; + _onJumpFromWallRunCoyote.Taken += OnJumpFromWallCoyote; _onJumpFromWall1.Taken += OnJumpFromWall; _onJumpFromWall2.Taken += OnJumpFromWall; _onJumpFromWall3.Taken += OnJumpFromWall; @@ -478,7 +492,9 @@ public partial class PlayerController : CharacterBody3D // If all else fail and we go down, we hug if (Velocity.Y < 0 && !_coyoteEnabled.Active) + { _playerState.SendEvent("wall_hug"); + } } public void OnWallHuggingStarted() @@ -509,6 +525,9 @@ public partial class PlayerController : CharacterBody3D public void OnWallStarted() { + if (!WallHugSystem.IsWallHugging()) + return; + _wallHugStartNormal = WallHugSystem.WallHugNormal.UnwrapOr(Vector3.Up); _currentWallContactPoint = WallHugSystem.WallHugLocation.UnwrapOr(Vector3.Zero); _wallHugStartLocation = _currentWallContactPoint + _wallHugStartNormal * _playerRadius; @@ -517,10 +536,6 @@ public partial class PlayerController : CharacterBody3D public void OnWallStopped() { - _wallHugStartLocation = Vector3.Zero; - _currentWallContactPoint = Vector3.Zero; - _wallHugStartNormal = Vector3.Zero; - _wallHugStartProjectedVelocity = Vector3.Zero; } public void HandleWallHugging(float delta) @@ -577,17 +592,12 @@ public partial class PlayerController : CharacterBody3D GlobalPosition = _wallHugStartLocation; } - private Option _plannedMantleLocation = Option.None; - // Jump public void OnInputJumpStarted() { - if (CanMantle()) + if (MantleSystem.IsMantlePossible) { - var location = _plannedMantleLocation.UnwrapOr(Vector3.Zero); - if (location == Vector3.Zero) - return; // For some reason CanMantle can return an invalid location so fuck off I guess - MantleToLocation(location); + _playerState.SendEvent("mantle"); return; } @@ -596,7 +606,11 @@ public partial class PlayerController : CharacterBody3D _playerState.SendEvent("megajump"); return; } - + + if (_onWallHuggingCoyoteEnabled.Active || _onWallRunningCoyoteEnabled.Active) + { + if (!_isWallJumpAvailable) return; + } _playerState.SendEvent("jump"); } @@ -643,8 +657,14 @@ public partial class PlayerController : CharacterBody3D var currentHorizontalVelocity = new Vector2(Velocity.X, Velocity.Z); var wallJumpHorizontalVelocity = new Vector2(jumpVector.X, jumpVector.Z); - SetHorizontalVelocity(currentHorizontalVelocity + wallJumpHorizontalVelocity);; + SetHorizontalVelocity(currentHorizontalVelocity + wallJumpHorizontalVelocity); } + + public void OnJumpFromWallCoyote() + { + _isWallJumpAvailable = false; + } + public void OnJumpFromWall() { ComputeJumpFromWallHSpeed(WallJumpStartVelocity); @@ -705,6 +725,36 @@ public partial class PlayerController : CharacterBody3D z: velocity.Y); } + public void OnMantleStarted() + { + HeadSystem.OnMantle(); + + _preMantleVelocity = Velocity; + + var tween = GetTree().CreateTween(); + tween.SetTrans(Tween.TransitionType.Cubic); + tween.SetEase(Tween.EaseType.InOut); + var inbetweenDuration = MantleTime / MantleSystem.NumberOfValidPofiles; + for (int i = 0; i < MantleSystem.NumberOfValidPofiles; i += 1) + { + var position = MantleSystem.WallProfilePositions[i]; + tween.TweenProperty(this, "global_position", position, inbetweenDuration); + } + + tween.Finished += MantleFinished; + } + + public void HandleMantling(float delta) + { + + } + + public void MantleFinished() + { + Velocity = _preMantleVelocity; + _playerState.SendEvent("grounded"); + } + public void HandleJump(float delta, float gravityFactor, int hangFrames) { // Update horizontal velocity @@ -925,6 +975,11 @@ public partial class PlayerController : CharacterBody3D if (WeaponSystem.IsPlantedUnderPlatform()) dashLocation += Vector3.Down * _playerHeight; + _wallHugStartNormal = WeaponSystem.PlantNormal; + _currentWallContactPoint = WeaponSystem.PlantLocation; + _wallHugStartLocation = dashLocation; + _wallHugStartProjectedVelocity = Velocity.Slide(_wallHugStartNormal); + var dashTween = CreatePositionTween(dashLocation, AimedDashTime); dashTween.Finished += DashToPlantedWeaponTweenEnded; } @@ -946,7 +1001,7 @@ public partial class PlayerController : CharacterBody3D RecoverWeapon(); - var resultingEvent = shouldDashToHanging ? "to_planted" : "dash_finished"; + var resultingEvent = shouldDashToHanging ? "dash_to_planted" : "dash_finished"; _playerState.SendEvent(resultingEvent); } @@ -1017,9 +1072,13 @@ public partial class PlayerController : CharacterBody3D { var postDashVelocity = _preDashVelocity.Length() > PostDashSpeed ? PostDashSpeed : _preDashVelocity.Length(); Velocity = _dashDirection * postDashVelocity; - + if (_shouldMantleOnDashEnded) - MantleToLocation(_mantleLocation); + { + // TODO update post dash mantle + // MantleToLocation(_mantleLocation); + GD.Print("update post dash mantle"); + } } public void OnSimpleDashStarted() @@ -1059,9 +1118,6 @@ public partial class PlayerController : CharacterBody3D public void OnPoweredDashFinished() { - // Try mantling but don't know if this is useful - // if (CanMantle()) - // MantleToLocation(MantleSystem.FindMantleForHeadRotation(HeadSystem.Rotation.Y).Unwrap()); } public void FinishPoweredDash() @@ -1099,28 +1155,6 @@ public partial class PlayerController : CharacterBody3D _playerState.SendEvent("coyote_expired"); } - // Mantling - public bool CanMantle() - { - var mantleLocationResult = MantleSystem.FindMantleForHeadRotation(HeadSystem.Rotation.Y); - return mantleLocationResult.IsSome(out _); - } - - private Vector3 _preMantleVelocity = Vector3.Zero; - public void MantleToLocation(Vector3 location) - { - HeadSystem.OnMantle(); - - _preMantleVelocity = Velocity; - var mantleTween = CreatePositionTween(location, MantleTime); - mantleTween.Finished += MantleFinished; - } - public void MantleFinished() - { - Velocity = _preMantleVelocity; - _playerState.SendEvent("grounded"); - } - /////////////////////////// // Stateless logic //////// /////////////////////////// @@ -1273,7 +1307,7 @@ public partial class PlayerController : CharacterBody3D LookAround(delta); CameraModifications((float) delta); HandleStairs((float) delta); - _plannedMantleLocation = MantleSystem.FindMantleForHeadRotation(HeadSystem.Rotation.Y); + MantleSystem.ProcessMantle(_grounded.Active); if (WeaponSystem.InHandState.Active) RotateWeaponWithPlayer(); diff --git a/systems/dash/dash_system.tscn b/systems/dash/dash_system.tscn index 98768cc9..954759c8 100644 --- a/systems/dash/dash_system.tscn +++ b/systems/dash/dash_system.tscn @@ -33,6 +33,7 @@ debug_shape_custom_color = Color(0.863327, 0.636844, 0, 1) [node name="DashCast3D" type="ShapeCast3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6, 0) +visible = false shape = SubResource("SphereShape3D_jngg2") target_position = Vector3(0, 0, -12) max_results = 1 @@ -41,6 +42,7 @@ debug_shape_custom_color = Color(0.911631, 0.11884, 0.656218, 1) [node name="DashCastDrop" type="ShapeCast3D" parent="."] transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, 1.6, 0) +visible = false shape = SubResource("SphereShape3D_jngg2") target_position = Vector3(0, 0, -50) max_results = 1 @@ -52,6 +54,7 @@ mesh = SubResource("SphereMesh_qu4wy") surface_material_override/0 = SubResource("StandardMaterial3D_v31n3") [node name="MantleSystem" parent="." instance=ExtResource("2_pff7b")] +visible = false MantleEndLocationDistanceFromWall = 0.25 MantleHeightCastStart = 2.0 diff --git a/systems/mantle/MantleSystem.cs b/systems/mantle/MantleSystem.cs index b68c0e3f..3893eb8a 100644 --- a/systems/mantle/MantleSystem.cs +++ b/systems/mantle/MantleSystem.cs @@ -14,21 +14,98 @@ public partial class MantleSystem: Node3D private ShapeCast3D _wallInFrontCast3D; private ShapeCast3D _mantleCast3D; - private RayCast3D _mantleCheckCast3D; + + private ShapeCast3D _inAirWallDetect; + private ShapeCast3D _groundedWallDetect; + + public bool IsMantlePossible { get; private set; } = false; + public int NumberOfValidPofiles { get; private set; } = -1; + public const int WallProfileCastCount = 7; + + private ShapeCast3D[] _wallProfileShapecasts = new ShapeCast3D[WallProfileCastCount]; + public Vector3[] WallProfilePositions { get; private set; } = new Vector3[WallProfileCastCount]; + public Vector3[] WallProfileNormals { get; private set; } = new Vector3[WallProfileCastCount]; public void Init() { _wallInFrontCast3D = GetNode("WallInFrontCast3D"); _mantleCast3D = GetNode("MantleCast3D"); + + _inAirWallDetect = GetNode("InAirWallDetect"); + _groundedWallDetect = GetNode("GroundedWallDetect"); + for (int i = 0; i < _wallProfileShapecasts.Length; i++) + { + _wallProfileShapecasts[i] = GetNode($"WallProfileShapeCasts/ShapeCast{i + 1}"); + } } - public Option FindMantleForHeadRotation(float rotation) + private void SetCastsEnabled(bool enabled) { - _wallInFrontCast3D.SetRotation(new Vector3( - _wallInFrontCast3D.Rotation.X, - rotation, - _wallInFrontCast3D.Rotation.Z)); + foreach (var wallProfileShapecast in _wallProfileShapecasts) + { + wallProfileShapecast.SetEnabled(enabled); + } + } + public void ProcessMantle(bool isGrounded) + { + _inAirWallDetect.SetEnabled(!isGrounded); + _groundedWallDetect.SetEnabled(isGrounded); + var isColliding = _inAirWallDetect.IsColliding() || _groundedWallDetect.IsColliding(); + SetCastsEnabled(isColliding); + + // Reset state + NumberOfValidPofiles = -1; + IsMantlePossible = false; + if (!isColliding) + { + return; + } + + // Check if collide with wall + var collisionNormal = isGrounded ? _groundedWallDetect.GetCollisionNormal(0) : _inAirWallDetect.GetCollisionNormal(0); + if (collisionNormal.Y > 0.8f) + { + return; + } + + NumberOfValidPofiles = 0; + var hasFirstProfileHit = false; + foreach (var wallProfileShapecast in _wallProfileShapecasts) + { + // Haven't met the wall yet + if (!wallProfileShapecast.IsColliding() && !hasFirstProfileHit) continue; + + var globalTargetPosition = wallProfileShapecast.GlobalPosition + wallProfileShapecast.TargetPosition; + + // Got to the other side of the wall, we stop there + if (!wallProfileShapecast.IsColliding()) + { + WallProfilePositions[NumberOfValidPofiles] = globalTargetPosition; + WallProfileNormals[NumberOfValidPofiles] = Vector3.Zero; + NumberOfValidPofiles += 1; + break; + } + + var profilePoint = wallProfileShapecast.GetCollisionPoint(0); + var profileNormal = wallProfileShapecast.GetCollisionNormal(0); + + // Check if we collided parallel to a wall + var isCollisionSameAsTarget = globalTargetPosition.IsEqualApprox(profilePoint); + var isCollidingWithWall = profileNormal.Y < 0.9f; + if (isCollisionSameAsTarget || isCollidingWithWall) continue; + + // We have a valid collision + WallProfilePositions[NumberOfValidPofiles] = profilePoint; + WallProfileNormals[NumberOfValidPofiles] = profileNormal; + hasFirstProfileHit = true; + NumberOfValidPofiles += 1; + } + IsMantlePossible = NumberOfValidPofiles > 0; // Should always be true + } + + public Option FindMantle() + { if (!_wallInFrontCast3D.IsColliding()) { return Option.None; diff --git a/systems/mantle/find_wall_shape.tres b/systems/mantle/find_wall_shape.tres new file mode 100644 index 00000000..b667c9e5 --- /dev/null +++ b/systems/mantle/find_wall_shape.tres @@ -0,0 +1,4 @@ +[gd_resource type="SphereShape3D" format=3 uid="uid://dp2p8v7demb5j"] + +[resource] +radius = 0.25 diff --git a/systems/mantle/mantle_system.tscn b/systems/mantle/mantle_system.tscn index b1283f07..abeea388 100644 --- a/systems/mantle/mantle_system.tscn +++ b/systems/mantle/mantle_system.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=4 format=3 uid="uid://wq1okogkhc5l"] +[gd_scene load_steps=6 format=3 uid="uid://wq1okogkhc5l"] [ext_resource type="Script" uid="uid://bja6tis1vaysu" path="res://systems/mantle/MantleSystem.cs" id="1_2oobp"] +[ext_resource type="Shape3D" uid="uid://dp2p8v7demb5j" path="res://systems/mantle/find_wall_shape.tres" id="2_i32qj"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4coqe"] height = 1.7 @@ -8,12 +9,16 @@ height = 1.7 [sub_resource type="SphereShape3D" id="SphereShape3D_2oobp"] radius = 0.75 +[sub_resource type="SphereShape3D" id="SphereShape3D_i32qj"] +radius = 0.125 + [node name="MantleSystem" type="Node3D"] script = ExtResource("1_2oobp") MantleEndLocationDistanceFromWall = 0.2 MantleHeightCastStart = 3.0 [node name="MantleCast3D" type="ShapeCast3D" parent="."] +visible = false shape = SubResource("CapsuleShape3D_4coqe") target_position = Vector3(0, 0, 0) max_results = 1 @@ -22,8 +27,72 @@ debug_shape_custom_color = Color(1, 0, 0, 1) [node name="WallInFrontCast3D" type="ShapeCast3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) +visible = false shape = SubResource("SphereShape3D_2oobp") target_position = Vector3(0, 0, -1.5) max_results = 1 collision_mask = 2 debug_shape_custom_color = Color(0.911631, 0.11884, 0.656218, 1) + +[node name="InAirWallDetect" type="ShapeCast3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.26, 0) +shape = ExtResource("2_i32qj") +target_position = Vector3(0, 0, -2) +collision_mask = 2 + +[node name="GroundedWallDetect" type="ShapeCast3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.76, 0) +shape = ExtResource("2_i32qj") +target_position = Vector3(0, 0, -2) +collision_mask = 2 + +[node name="WallProfileShapeCasts" type="Node3D" parent="."] + +[node name="ShapeCast1" type="ShapeCast3D" parent="WallProfileShapeCasts"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, -0.5) +enabled = false +shape = SubResource("SphereShape3D_i32qj") +target_position = Vector3(0, -2.375, 0) +collision_mask = 2 + +[node name="ShapeCast2" type="ShapeCast3D" parent="WallProfileShapeCasts"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, -0.75) +enabled = false +shape = SubResource("SphereShape3D_i32qj") +target_position = Vector3(0, -2.375, 0) +collision_mask = 2 + +[node name="ShapeCast3" type="ShapeCast3D" parent="WallProfileShapeCasts"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, -1) +enabled = false +shape = SubResource("SphereShape3D_i32qj") +target_position = Vector3(0, -2.375, 0) +collision_mask = 2 + +[node name="ShapeCast4" type="ShapeCast3D" parent="WallProfileShapeCasts"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, -1.25) +enabled = false +shape = SubResource("SphereShape3D_i32qj") +target_position = Vector3(0, -2.375, 0) +collision_mask = 2 + +[node name="ShapeCast5" type="ShapeCast3D" parent="WallProfileShapeCasts"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, -1.5) +enabled = false +shape = SubResource("SphereShape3D_i32qj") +target_position = Vector3(0, -2.375, 0) +collision_mask = 2 + +[node name="ShapeCast6" type="ShapeCast3D" parent="WallProfileShapeCasts"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, -1.75) +enabled = false +shape = SubResource("SphereShape3D_i32qj") +target_position = Vector3(0, -2.375, 0) +collision_mask = 2 + +[node name="ShapeCast7" type="ShapeCast3D" parent="WallProfileShapeCasts"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, -2) +enabled = false +shape = SubResource("SphereShape3D_i32qj") +target_position = Vector3(0, -2.375, 0) +collision_mask = 2