From c3ff41e1e6c9c03b4557465272f638d563bc0a56 Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 26 Dec 2025 00:35:36 +0100 Subject: [PATCH] feat: rethought behaviour separation and states --- modules/authority/character.cpp | 79 +++++++++++++++++++++- modules/authority/character.h | 31 +++++++++ modules/authority/player_character.cpp | 43 ------------ modules/authority/player_character.h | 21 ------ modules/authority/player_states.cpp | 26 +++++++ modules/authority/player_states.h | 32 +++++++++ modules/authority/register_types.cpp | 3 +- project/data/default_player_character.tres | 4 ++ project/icon.svg.import | 6 ++ project/objects/player_character.tscn | 16 +++++ project/project.godot | 40 ++++++++++- project/scenes/test_world.tscn | 41 +++++++++++ 12 files changed, 274 insertions(+), 68 deletions(-) delete mode 100644 modules/authority/player_character.cpp delete mode 100644 modules/authority/player_character.h create mode 100644 modules/authority/player_states.cpp create mode 100644 modules/authority/player_states.h create mode 100644 project/data/default_player_character.tres create mode 100644 project/objects/player_character.tscn create mode 100644 project/scenes/test_world.tscn diff --git a/modules/authority/character.cpp b/modules/authority/character.cpp index 805da5ba..890dc836 100644 --- a/modules/authority/character.cpp +++ b/modules/authority/character.cpp @@ -20,7 +20,7 @@ void Character::physics_process(double delta) { } void Character::_notification(int what) { - if (Engine::get_singleton()->is_editor_hint()) { + if (Engine::get_singleton()->is_editor_hint() || !this->data.is_valid()) { return; } switch (what) { @@ -38,3 +38,80 @@ void Character::_notification(int what) { void Character::set_movement(Vector2 movement) { this->world_movement_direction = movement; } + +bool Character::is_moving() const { + return !this->world_movement_direction.is_zero_approx(); +} + +void CharacterState::_bind_methods() { + BIND_PROPERTY(Variant::BOOL, start_active); +} + +void CharacterState::_notification(int what) { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + switch (what) { + default: + return; + case NOTIFICATION_ENTER_TREE: + this->character = cast_to(get_parent()); + return; + case NOTIFICATION_READY: + if (start_active) { + set_state_active(true); + } + return; + } +} + +void CharacterState::switch_to_state(String value) { + if (this->state_active) { + print_error(vformat("Attempt to switch from inactive state %s to new state %s", get_path(), value)); + return; + } + set_state_active(false); + stack_state_independent(value); +} + +void CharacterState::stack_state_dependent(String value) { + if (this->state_active) { + print_error(vformat("Attempt to stack dependent state %s from inactive state %s", value, get_path())); + return; + } + Node *node{ get_parent()->get_node(value) }; + if (CharacterState * state{ cast_to(node) }) { + state->depending_state = this; + this->dependent_states.insert(state); + state->set_state_active(true); + } +} + +void CharacterState::stack_state_independent(String value) { + if (this->state_active) { + print_error(vformat("Attempt to stack state %s from inactive state %s", value, get_path())); + return; + } + Node *node{ get_parent()->get_node(value) }; + if (CharacterState * state{ cast_to(node) }) { + state->set_state_active(true); + } else { + print_error(vformat("Attempt to stack nonexistent state %s from %s", value, get_path())); + } +} + +void CharacterState::set_state_active(bool active) { + if (this->state_active == active) { + this->state_active = active; + if (active) { + state_entered(); + } else { + state_exited(); + this->depending_state = nullptr; + for (CharacterState *state : this->dependent_states) { + state->set_state_active(false); + } + this->dependent_states.clear(); + } + } +} diff --git a/modules/authority/character.h b/modules/authority/character.h index 4b58ab5a..c329f179 100644 --- a/modules/authority/character.h +++ b/modules/authority/character.h @@ -2,6 +2,7 @@ #include "authority/macros.h" #include "core/io/resource.h" +#include "core/templates/hash_set.h" #include "scene/3d/physics/character_body_3d.h" class CharacterData : public Resource { @@ -25,6 +26,7 @@ protected: public: void set_movement(Vector2 movement); + bool is_moving() const; private: Ref data{}; @@ -33,3 +35,32 @@ private: public: GET_SET_FNS(Ref, data); }; + +class CharacterState : public Node { + GDCLASS(CharacterState, Node); + static void _bind_methods(); + +protected: + void _notification(int what); + void switch_to_state(String state); + void stack_state_dependent(String state); + void stack_state_independent(String state); + virtual void state_entered() {} + virtual void state_exited() {} + +public: + void set_state_active(bool active); + bool get_state_active() const; + Character *get_character() const; + +private: + bool start_active{ false }; + bool state_active{ false }; + + Character *character{ nullptr }; + HashSet dependent_states{}; + CharacterState *depending_state{ nullptr }; + +public: + GET_SET_FNS(bool, start_active); +}; diff --git a/modules/authority/player_character.cpp b/modules/authority/player_character.cpp deleted file mode 100644 index c2bba15e..00000000 --- a/modules/authority/player_character.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "player_character.h" -#include "core/input/input.h" -#include "scene/main/viewport.h" - -void PlayerCharacter::_bind_methods() {} - -void PlayerCharacter::process(double delta) { - Basis const basis{ get_viewport()->get_camera_3d()->get_global_basis() }; - Vector2 backward{ basis.get_column(2).x, basis.get_column(2).z }; - Vector2 right{ basis.get_column(0).x, basis.get_column(0).z }; - set_movement({ backward.normalized() * this->last_movement_input.x + right.normalized() * this->last_movement_input }); -} - -void PlayerCharacter::_notification(int what) { - if (Engine::get_singleton()->is_editor_hint()) { - return; - } - switch (what) { - default: - return; - case NOTIFICATION_ENTER_TREE: - set_process(true); - set_process_unhandled_input(true); - return; - case NOTIFICATION_PROCESS: - process(get_process_delta_time()); - } -} - -void PlayerCharacter::unhandled_input(Ref const &what) { - if (what->is_action(input_move_left) || what->is_action(input_move_forward) || what->is_action(input_move_right) || what->is_action(input_move_backward)) { - this->last_movement_input = { - Input::get_singleton()->get_axis(input_move_left, input_move_right), - Input::get_singleton()->get_axis(input_move_backward, input_move_forward) - }; - get_viewport()->set_input_as_handled(); - } -} - -String const PlayerCharacter::input_move_left{ "move_left" }; -String const PlayerCharacter::input_move_right{ "move_right" }; -String const PlayerCharacter::input_move_forward{ "move_forward" }; -String const PlayerCharacter::input_move_backward{ "move_backward" }; diff --git a/modules/authority/player_character.h b/modules/authority/player_character.h deleted file mode 100644 index 4efb7b75..00000000 --- a/modules/authority/player_character.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "authority/character.h" - -class PlayerCharacter : public Character { - GDCLASS(PlayerCharacter, Character); - static void _bind_methods(); - -protected: - void process(double delta); - void _notification(int what); - virtual void unhandled_input(Ref const &what) override; - -private: - Vector2 last_movement_input{ 0, 0 }; - - static String const input_move_left; - static String const input_move_right; - static String const input_move_forward; - static String const input_move_backward; -}; diff --git a/modules/authority/player_states.cpp b/modules/authority/player_states.cpp new file mode 100644 index 00000000..0ee37561 --- /dev/null +++ b/modules/authority/player_states.cpp @@ -0,0 +1,26 @@ +#include "player_states.h" + +void PlayerInputState::_bind_methods() {} + +void PlayerInputState::process(double delta) {} + +void PlayerInputState::_notification(int what) { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + switch (what) { + default: + return; + case NOTIFICATION_PROCESS: + process(get_process_delta_time()); + return; + } +} + +void PlayerInputState::state_entered() { + set_process(true); +} + +void PlayerInputState::state_exited() { + set_process(false); +} diff --git a/modules/authority/player_states.h b/modules/authority/player_states.h new file mode 100644 index 00000000..426e68b7 --- /dev/null +++ b/modules/authority/player_states.h @@ -0,0 +1,32 @@ +#pragma once + +#include "authority/character.h" + +class PlayerInputState : public CharacterState { + GDCLASS(PlayerInputState, CharacterState); + static void _bind_methods(); + void process(double delta); + +protected: + void _notification(int what); + void unhandled_input(Ref const &event) override; + void state_entered() override; + void state_exited() override; + +public: + Vector2 input{}; +}; + +class PlayerMovementState : public CharacterState { + GDCLASS(PlayerMovementState, CharacterState); + static void _bind_methods(); + void ready(); + void process(double delta); + +protected: + void state_entered() override; + void state_exited() override; + +private: + Vector2 movement{}; +}; diff --git a/modules/authority/register_types.cpp b/modules/authority/register_types.cpp index 5c43d34d..d0bc0944 100644 --- a/modules/authority/register_types.cpp +++ b/modules/authority/register_types.cpp @@ -1,7 +1,6 @@ #include "register_types.h" #include "authority/character.h" -#include "authority/player_character.h" #include "core/object/class_db.h" void initialize_authority_module(ModuleInitializationLevel p_level) { @@ -10,7 +9,7 @@ void initialize_authority_module(ModuleInitializationLevel p_level) { } ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_class(); + ClassDB::register_class(); } void uninitialize_authority_module(ModuleInitializationLevel p_level) { diff --git a/project/data/default_player_character.tres b/project/data/default_player_character.tres new file mode 100644 index 00000000..bba096d2 --- /dev/null +++ b/project/data/default_player_character.tres @@ -0,0 +1,4 @@ +[gd_resource type="CharacterData" format=3 uid="uid://bmudhddb0vedg"] + +[resource] +speed = 1.0 diff --git a/project/icon.svg.import b/project/icon.svg.import index 869157e0..f082c774 100644 --- a/project/icon.svg.import +++ b/project/icon.svg.import @@ -18,6 +18,8 @@ dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.cte compress/mode=0 compress/high_quality=false compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 @@ -25,6 +27,10 @@ mipmaps/generate=false mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 process/fix_alpha_border=true process/premult_alpha=false process/normal_map_invert_y=false diff --git a/project/objects/player_character.tscn b/project/objects/player_character.tscn new file mode 100644 index 00000000..73cf8ee3 --- /dev/null +++ b/project/objects/player_character.tscn @@ -0,0 +1,16 @@ +[gd_scene format=3 uid="uid://dcqd0wo5y5a1g"] + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_vcg8s"] + +[sub_resource type="CylinderMesh" id="CylinderMesh_5kd2n"] + +[node name="PlayerCharacter" type="PlayerCharacter" unique_id=1435471129] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=511026275] +shape = SubResource("CapsuleShape3D_vcg8s") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=957221075] +mesh = SubResource("CylinderMesh_5kd2n") + +[node name="Camera3D" type="Camera3D" parent="." unique_id=932811285] +transform = Transform3D(1, 0, 0, 0, 0.90373915, 0.42808357, 0, -0.42808357, 0.90373915, 0, 2.1840205, 3.7269862) diff --git a/project/project.godot b/project/project.godot index 8cd51d29..52af6690 100644 --- a/project/project.godot +++ b/project/project.godot @@ -8,8 +8,46 @@ config_version=5 +[animation] + +compatibility/default_parent_skeleton_in_mesh_instance_3d=true + [application] config/name="authority" -config/features=PackedStringArray("4.4", "Forward Plus") +run/main_scene="uid://cv0ub3llm3jew" +config/features=PackedStringArray("4.6", "Forward Plus") config/icon="res://icon.svg" + +[display] + +window/size/viewport_width=1920 +window/size/viewport_height=1080 +window/size/mode=3 + +[input] + +move_left={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) +] +} +move_right={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) +] +} +move_forward={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +] +} +move_backward={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +] +} + +[physics] + +3d/physics_engine="Jolt Physics" diff --git a/project/scenes/test_world.tscn b/project/scenes/test_world.tscn new file mode 100644 index 00000000..e8bf9677 --- /dev/null +++ b/project/scenes/test_world.tscn @@ -0,0 +1,41 @@ +[gd_scene format=3 uid="uid://cv0ub3llm3jew"] + +[ext_resource type="PackedScene" uid="uid://dcqd0wo5y5a1g" path="res://objects/player_character.tscn" id="1_kyfjp"] +[ext_resource type="CharacterData" uid="uid://bmudhddb0vedg" path="res://data/default_player_character.tres" id="2_amxg5"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_kyfjp"] +sky_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1) +ground_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1) + +[sub_resource type="Sky" id="Sky_amxg5"] +sky_material = SubResource("ProceduralSkyMaterial_kyfjp") + +[sub_resource type="Environment" id="Environment_3263u"] +background_mode = 2 +sky = SubResource("Sky_amxg5") +tonemap_mode = 2 +glow_enabled = true + +[node name="TestWorld" type="Node3D" unique_id=262419127] + +[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1185961481] +environment = SubResource("Environment_3263u") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1382994887] +transform = Transform3D(-0.8660254, -0.43301278, 0.25, 0, 0.49999997, 0.86602545, -0.50000006, 0.75, -0.43301266, 0, 0, 0) +shadow_enabled = true + +[node name="PlayerCharacter" parent="." unique_id=1435471129 instance=ExtResource("1_kyfjp")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) +data = ExtResource("2_amxg5") + +[node name="CSGCombiner3D" type="CSGCombiner3D" parent="." unique_id=885387983] +use_collision = true + +[node name="CSGBox3D" type="CSGBox3D" parent="CSGCombiner3D" unique_id=1853081325] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0) +size = Vector3(100, 1, 100) + +[node name="CSGBox3D2" type="CSGBox3D" parent="CSGCombiner3D" unique_id=40055740] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.293318, 1.1054688, -6.6544046) +size = Vector3(5.5302734, 2.2109375, 3.6298828)