From e9d6124d100d8293a2dbb2382f8a5037b0fc1922 Mon Sep 17 00:00:00 2001 From: Sara Date: Tue, 28 Apr 2026 14:31:49 +0200 Subject: [PATCH] feat: wall jumps --- modules/viscosity/player_action.cpp | 21 ++++++ modules/viscosity/player_action.h | 21 ++++++ modules/viscosity/player_movement_action.cpp | 13 ++-- modules/viscosity/player_movement_action.h | 8 +-- modules/viscosity/register_types.cpp | 2 + project/objects/player.tscn | 69 ++++++++++++++++---- 6 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 modules/viscosity/player_action.cpp create mode 100644 modules/viscosity/player_action.h diff --git a/modules/viscosity/player_action.cpp b/modules/viscosity/player_action.cpp new file mode 100644 index 00000000..5863b80e --- /dev/null +++ b/modules/viscosity/player_action.cpp @@ -0,0 +1,21 @@ +#include "player_action.h" +#include "macros.h" + +void PlayerAction::_bind_methods() { + BIND_HPROPERTY(Variant::OBJECT, blackboard, PROPERTY_HINT_NODE_TYPE, "PlayerBehaviourTree", PROPERTY_USAGE_READ_ONLY); + BIND_HPROPERTY(Variant::OBJECT, body, PROPERTY_HINT_NODE_TYPE, "PlayerBody", PROPERTY_USAGE_READ_ONLY); +} + +void PlayerAction::_notification(int what) { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + switch (what) { + default: + return; + case NOTIFICATION_ENTER_TREE: + this->blackboard = cast_to(get_behaviour_tree()); + this->body = this->blackboard->get_player_body(); + ERR_FAIL_COND_EDMSG(this->blackboard == nullptr, "BehaviourTree of PlayerAction is not a PlayerBehaviourTree"); + } +} diff --git a/modules/viscosity/player_action.h b/modules/viscosity/player_action.h new file mode 100644 index 00000000..6156d27c --- /dev/null +++ b/modules/viscosity/player_action.h @@ -0,0 +1,21 @@ +#pragma once + +#include "behaviour_nodes/behaviour_action.h" +#include "viscosity/player_behaviour_tree.h" +#include "viscosity/player_body.h" + +class PlayerAction : public BehaviourAction { + GDCLASS(PlayerAction, BehaviourAction); + static void _bind_methods(); + +protected: + void _notification(int what); + +private: + PlayerBehaviourTree *blackboard{ nullptr }; + PlayerBody *body{ nullptr }; + +public: + GET_SET_FNS(PlayerBehaviourTree *, blackboard); + GET_SET_FNS(PlayerBody *, body); +}; diff --git a/modules/viscosity/player_movement_action.cpp b/modules/viscosity/player_movement_action.cpp index 6940a791..071cffd7 100644 --- a/modules/viscosity/player_movement_action.cpp +++ b/modules/viscosity/player_movement_action.cpp @@ -9,13 +9,10 @@ void PlayerMovementAction::_bind_methods() { } void PlayerMovementAction::process_movement() { - if (this->blackboard == nullptr) { - this->blackboard = cast_to(get_behaviour_tree()); - } - ERR_FAIL_COND_EDMSG(this->blackboard == nullptr, "Parent behaviour tree of PlayerMovementAction is not a PlayerBehaviourTree"); + ERR_FAIL_COND_EDMSG(get_blackboard() == nullptr, "Parent behaviour tree of PlayerMovementAction is not a PlayerBehaviourTree"); double const delta{ get_process_delta_time() }; - Vector3 target_velocity{ this->blackboard->get_world_move_input().normalized() * this->target_speed }; - Vector3 velocity{ this->blackboard->get_player_body()->get_velocity() }; + Vector3 target_velocity{ get_blackboard()->get_world_move_input().normalized() * this->target_speed }; + Vector3 velocity{ get_blackboard()->get_player_body()->get_velocity() }; float actual_acceleration{ target_velocity.normalized().dot(velocity.normalized()) > 0.8f ? this->acceleration @@ -24,7 +21,7 @@ void PlayerMovementAction::process_movement() { target_velocity.y = velocity.y; velocity = velocity.move_toward(target_velocity, delta * actual_acceleration); if (!velocity.is_zero_approx()) { - this->blackboard->get_character()->look_at(this->blackboard->get_character()->get_global_position() + velocity); + get_blackboard()->get_character()->look_at(get_blackboard()->get_character()->get_global_position() + velocity); } - this->blackboard->get_player_body()->set_velocity(velocity); + get_blackboard()->get_player_body()->set_velocity(velocity); } diff --git a/modules/viscosity/player_movement_action.h b/modules/viscosity/player_movement_action.h index 3ec33467..ac10da8c 100644 --- a/modules/viscosity/player_movement_action.h +++ b/modules/viscosity/player_movement_action.h @@ -1,11 +1,10 @@ #pragma once -#include "behaviour_nodes/behaviour_action.h" #include "macros.h" -#include "viscosity/player_behaviour_tree.h" +#include "viscosity/player_action.h" -class PlayerMovementAction : public BehaviourAction { - GDCLASS(PlayerMovementAction, BehaviourAction); +class PlayerMovementAction : public PlayerAction { + GDCLASS(PlayerMovementAction, PlayerAction); static void _bind_methods(); public: @@ -15,7 +14,6 @@ private: float target_speed{ 5.f }; float acceleration{ 10.f }; float deceleration{ 20.f }; - PlayerBehaviourTree *blackboard{ nullptr }; public: GET_SET_FNS(float, target_speed); diff --git a/modules/viscosity/register_types.cpp b/modules/viscosity/register_types.cpp index fe5f7a8c..465507c8 100644 --- a/modules/viscosity/register_types.cpp +++ b/modules/viscosity/register_types.cpp @@ -1,6 +1,7 @@ #include "register_types.h" #include "core/object/class_db.h" +#include "viscosity/player_action.h" #include "viscosity/player_behaviour_tree.h" #include "viscosity/player_body.h" #include "viscosity/player_camera.h" @@ -13,6 +14,7 @@ void initialize_viscosity_module(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); } diff --git a/project/objects/player.tscn b/project/objects/player.tscn index 8bc97ec8..4bd7d04e 100644 --- a/project/objects/player.tscn +++ b/project/objects/player.tscn @@ -8,15 +8,49 @@ radius = 0.6972656 [sub_resource type="BoxMesh" id="BoxMesh_bxedw"] size = Vector3(0.2, 0.2, 0.2) -[sub_resource type="GDScript" id="GDScript_ykyjo"] -resource_name = "FallingAction" -script/source = "extends BehaviourAction - -@onready var behaviour_tree := get_behaviour_tree() as PlayerBehaviourTree +[sub_resource type="GDScript" id="GDScript_f46kd"] +resource_name = "WallJumpAction" +script/source = "extends PlayerAction func _enter() -> int: - if not behaviour_tree.player_body.is_on_floor(): - behaviour_tree.player_body.velocity += behaviour_tree.player_body.get_gravity() * get_process_delta_time() + if body.is_on_wall_only() and Input.is_action_just_pressed(\"jump\"): + var wall := body.get_wall_normal() + if wall.dot(blackboard.get_world_move_input().normalized()) > 0.75: + body.velocity = body.get_wall_normal() * 4 + body.velocity.y = 6 + else: + body.velocity += body.get_wall_normal() * 3 + body.velocity.y = 4 + blackboard.character.look_at(blackboard.character.global_position + Vector3(body.velocity.x, 0, body.velocity.y)) + return Success + else: + return Fail +" + +[sub_resource type="GDScript" id="GDScript_nmc1l"] +resource_name = "WallSlideAction" +script/source = "extends PlayerAction + +@export var wall_drag : float + +func _enter() -> int: + if body.is_on_wall_only(): + var delta := get_process_delta_time() + var target_velocity := Vector3(0, min(body.velocity.y, 0.0), 0) + body.velocity = body.velocity.move_toward(target_velocity, wall_drag * delta) + (body.get_gravity() * 0.2 * delta) + blackboard.character.look_at(blackboard.character.global_position + body.get_wall_normal()) + return Success + else: + return Fail +" + +[sub_resource type="GDScript" id="GDScript_ykyjo"] +resource_name = "FallingAction" +script/source = "extends PlayerAction + +func _enter() -> int: + if not body.is_on_floor(): + body.velocity += body.get_gravity() * get_process_delta_time() return Success else: return Fail @@ -24,15 +58,13 @@ func _enter() -> int: [sub_resource type="GDScript" id="GDScript_eqqp1"] resource_name = "JumpAction" -script/source = "extends BehaviourAction +script/source = "extends PlayerAction @export var vertical_jump_velocity : float -@onready var behaviour_tree := get_behaviour_tree() as PlayerBehaviourTree - func _enter() -> int: if Input.is_action_just_pressed(\"jump\"): - behaviour_tree.player_body.velocity.y = vertical_jump_velocity + body.velocity.y = vertical_jump_velocity return Success else: return Fail @@ -60,6 +92,8 @@ func _enter() -> int: " [node name="Player" type="PlayerBody" unique_id=591139793] +slide_on_ceiling = false +wall_min_slide_angle = 1.0471976 [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1143027860] shape = SubResource("CapsuleShape3D_bxedw") @@ -84,15 +118,22 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.3841858e-07, 1.3299754, 4. [node name="ActionSelector" type="BehaviourSelector" parent="PlayerBehaviourTree/BehaviourRepeater" unique_id=433229675] -[node name="FallingAction" type="BehaviourAction" parent="PlayerBehaviourTree/BehaviourRepeater/ActionSelector" unique_id=740542779] +[node name="WallJumpAction" type="PlayerAction" parent="PlayerBehaviourTree/BehaviourRepeater/ActionSelector" unique_id=1070446491] +script = SubResource("GDScript_f46kd") + +[node name="WallSlideAction" type="PlayerAction" parent="PlayerBehaviourTree/BehaviourRepeater/ActionSelector" unique_id=85039173] +script = SubResource("GDScript_nmc1l") +wall_drag = 5.0 + +[node name="FallingAction" type="PlayerAction" parent="PlayerBehaviourTree/BehaviourRepeater/ActionSelector" unique_id=570179125] script = SubResource("GDScript_ykyjo") -[node name="JumpAction" type="BehaviourAction" parent="PlayerBehaviourTree/BehaviourRepeater/ActionSelector" unique_id=35567373] +[node name="JumpAction" type="PlayerAction" parent="PlayerBehaviourTree/BehaviourRepeater/ActionSelector" unique_id=553747039] script = SubResource("GDScript_eqqp1") vertical_jump_velocity = 5.0 [node name="RunningAction" type="PlayerMovementAction" parent="PlayerBehaviourTree/BehaviourRepeater/ActionSelector" unique_id=2080183164] -target_speed = 15.0 +target_speed = 10.0 script = SubResource("GDScript_bxedw") [node name="WalkAction" type="PlayerMovementAction" parent="PlayerBehaviourTree/BehaviourRepeater/ActionSelector" unique_id=1371791141]