diff --git a/modules/break_utopia/player_body.cpp b/modules/break_utopia/player_body.cpp index 4b05916f..a36056ad 100644 --- a/modules/break_utopia/player_body.cpp +++ b/modules/break_utopia/player_body.cpp @@ -92,13 +92,13 @@ void CharacterState::_notification(int what) { } } -void CharacterState::switch_state(String name) { +void CharacterState::switch_state(NodePath path) { this->set_state_active(false); - stack_state(name); + stack_state(path); } -void CharacterState::stack_state(String name) { - if (CharacterState * new_state{ cast_to(get_parent()->get_node(NodePath(name))) }) { +void CharacterState::stack_state(NodePath path) { + if (CharacterState * new_state{ cast_to(get_node(path)) }) { new_state->set_state_active(true); } } diff --git a/modules/break_utopia/player_body.h b/modules/break_utopia/player_body.h index a4ad4f4a..1c6d215f 100644 --- a/modules/break_utopia/player_body.h +++ b/modules/break_utopia/player_body.h @@ -30,18 +30,19 @@ class CharacterState : public Node { protected: void _notification(int what); + void switch_state(NodePath path); + inline void switch_state(String name) { switch_state(NodePath("../" + name)); } template - void switch_state(); + void switch_state() { switch_state(TNewState::get_class_static()); } + void stack_state(NodePath name); + inline void stack_state(String name) { stack_state(NodePath("../" + name)); } template - void stack_state(); - void switch_state(String name); - void stack_state(String name); + inline void stack_state() { stack_state(TStackState::get_class_static()); } virtual void state_entered(); virtual void state_exited(); public: void set_state_active(bool active); - PlayerBody *get_body() const { return cast_to(get_parent()); } private: bool active_state{ false }; @@ -50,16 +51,3 @@ public: static String const sig_state_entered; static String const sig_state_exited; }; - -template -void CharacterState::switch_state() { - this->set_state_active(false); - this->stack_state(); -} - -template -void CharacterState::stack_state() { - if (TStackState * state{ cast_to(get_parent()->get_node(NodePath(TStackState::get_class_static()))) }) { - state->set_state_active(true); - } -} diff --git a/modules/break_utopia/player_states.cpp b/modules/break_utopia/player_states.cpp index 58457ef2..609ffb55 100644 --- a/modules/break_utopia/player_states.cpp +++ b/modules/break_utopia/player_states.cpp @@ -1,5 +1,39 @@ #include "player_states.h" #include "scene/animation/animation_tree.h" +#include "scene/main/viewport.h" + +void PlayerState::_bind_methods() {} + +void PlayerState::ready() { + this->anim = cast_to(get_node(NodePath("%AnimationTree"))); + this->anim_fsm = this->anim->get("parameters/playback"); +} + +void PlayerState::_notification(int what) { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + switch (what) { + default: + return; + case NOTIFICATION_READY: + ready(); + return; + } +} + +void PlayerState::apply_root_motion(double delta) { + PlayerBody *body{ get_body() }; + Basis basis{ body->get_global_basis() }; + Vector3 root_motion{ this->anim->get_root_motion_position() }; + body->set_velocity((root_motion.x * basis.get_column(0) + root_motion.z * basis.get_column(2)) / delta); +} + +void PlayerState::input_trigger_entry_actions(Ref const &what) { + if (what->is_action_pressed("light_attack")) { + switch_state("LightAttack"); + } +} void PlayerIdleState::_bind_methods() {} @@ -10,10 +44,6 @@ void PlayerIdleState::_notification(int what) { switch (what) { default: return; - case NOTIFICATION_READY: - this->anim = cast_to(get_node(NodePath("%AnimationTree"))); - this->anim_fsm = this->anim->get("parameters/playback"); - return; case NOTIFICATION_PROCESS: get_body()->set_velocity(get_body()->get_velocity().move_toward(Vector3(), get_process_delta_time() * 50.f)); return; @@ -22,8 +52,8 @@ void PlayerIdleState::_notification(int what) { void PlayerIdleState::state_entered() { set_process_unhandled_input(true); - this->anim_fsm->travel("idle"); set_process(true); + get_anim_fsm()->travel("idle"); } void PlayerIdleState::state_exited() { @@ -34,6 +64,8 @@ void PlayerIdleState::state_exited() { void PlayerIdleState::unhandled_input(Ref const &what) { if (what->is_action("move_left") || what->is_action("move_right") || what->is_action("move_forward") || what->is_action("move_backward")) { switch_state(); + } else { + input_trigger_entry_actions(what); } } @@ -41,10 +73,7 @@ void PlayerRunState::_bind_methods() { } void PlayerRunState::process(double delta) { - PlayerBody *body{ get_body() }; - Basis basis{ body->get_global_basis() }; - Vector3 root_motion{ this->anim->get_root_motion_position() }; - body->set_velocity((root_motion.x * basis.get_column(0) + root_motion.z * basis.get_column(2)) / delta); + apply_root_motion(delta); if (get_body()->get_transformed_movement_input().is_zero_approx()) { switch_state(); } else { @@ -60,9 +89,59 @@ void PlayerRunState::_notification(int what) { switch (what) { default: return; - case NOTIFICATION_READY: - this->anim = cast_to(get_node(NodePath("%AnimationTree"))); - this->anim_fsm = this->anim->get("parameters/playback"); + case NOTIFICATION_PROCESS: + process(get_process_delta_time()); + return; + } +} + +void PlayerRunState::unhandled_input(Ref const &what) { + input_trigger_entry_actions(what); +} + +void PlayerRunState::state_entered() { + set_process(true); + get_anim_fsm()->travel("run"); +} + +void PlayerRunState::state_exited() { + set_process(false); +} + +void PlayerBasicAttackState::_bind_methods() { + BIND_PROPERTY(Variant::STRING, animation_name); + BIND_HPROPERTY(Variant::NODE_PATH, next_light_attack_state, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CharacterState"); + BIND_PROPERTY(Variant::FLOAT, trigger_next_margin); + BIND_PROPERTY(Variant::FLOAT, input_next_margin); +} + +void PlayerBasicAttackState::process(double delta) { + apply_root_motion(delta); + + if (!this->animation_started) { + if (get_anim_fsm()->get_current_node() == this->animation_name) { + this->animation_started = true; + this->anim_length = get_anim_fsm()->get_current_length(); + } else { + return; + } + } + delta_since_start += delta; + float const trigger_next_progress{ this->anim_length - this->trigger_next_margin }; + if (this->next_queued && this->delta_since_start > trigger_next_progress && !this->next_light_attack_state.is_empty()) { + switch_state(this->next_light_attack_state); + } + if (get_anim_fsm()->get_current_node() != this->animation_name) { + switch_state(); + } +} + +void PlayerBasicAttackState::_notification(int what) { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + switch (what) { + default: return; case NOTIFICATION_PROCESS: process(get_process_delta_time()); @@ -70,11 +149,26 @@ void PlayerRunState::_notification(int what) { } } -void PlayerRunState::state_entered() { - set_process(true); - this->anim_fsm->travel("run"); +void PlayerBasicAttackState::unhandled_input(Ref const &what) { + float const input_next_progress{ get_anim_fsm()->get_current_length() - this->input_next_margin }; + if (this->delta_since_start > input_next_progress && what->is_action_pressed("light_attack")) { + this->next_queued = true; + get_viewport()->set_input_as_handled(); + } } -void PlayerRunState::state_exited() { - set_process(false); +void PlayerBasicAttackState::state_entered() { + set_process(true); + set_process_unhandled_input(true); + + this->next_queued = false; + this->animation_started = false; + this->delta_since_start = 0.0; + + get_anim_fsm()->start(this->animation_name); +} + +void PlayerBasicAttackState::state_exited() { + set_process(false); + set_process_unhandled_input(false); } diff --git a/modules/break_utopia/player_states.h b/modules/break_utopia/player_states.h index 3d1d859d..80ae570b 100644 --- a/modules/break_utopia/player_states.h +++ b/modules/break_utopia/player_states.h @@ -5,8 +5,28 @@ #include "scene/animation/animation_node_state_machine.h" #include "scene/animation/animation_tree.h" -class PlayerIdleState : public CharacterState { - GDCLASS(PlayerIdleState, CharacterState); +class PlayerState : public CharacterState { + GDCLASS(PlayerState, CharacterState); + static void _bind_methods(); + void ready(); + +protected: + void _notification(int what); + void apply_root_motion(double delta); + PlayerBody *get_body() const { return cast_to(get_parent()); } + void input_trigger_entry_actions(Ref const &what); + +private: + AnimationTree *anim{ nullptr }; + Ref anim_fsm{}; + +protected: + GET_SET_FNS(AnimationTree *, anim); + GET_SET_FNS(Ref, anim_fsm); +}; + +class PlayerIdleState : public PlayerState { + GDCLASS(PlayerIdleState, PlayerState); static void _bind_methods(); protected: @@ -14,32 +34,46 @@ protected: void state_entered() override; void state_exited() override; void unhandled_input(Ref const &what) override; - -private: - AnimationTree *anim{ nullptr }; - Ref anim_fsm{}; }; -class PlayerRunState : public CharacterState { - GDCLASS(PlayerRunState, CharacterState); +class PlayerRunState : public PlayerState { + GDCLASS(PlayerRunState, PlayerState); static void _bind_methods(); void process(double delta); protected: void _notification(int what); + void unhandled_input(Ref const &what) override; + void state_entered() override; + void state_exited() override; +}; + +class PlayerBasicAttackState : public PlayerState { + GDCLASS(PlayerBasicAttackState, PlayerState); + static void _bind_methods(); + void process(double delta); + +protected: + void _notification(int what); + void unhandled_input(Ref const &what) override; void state_entered() override; void state_exited() override; private: - AnimationTree *anim{ nullptr }; - Ref anim_fsm{}; -}; + String animation_name{ "swing_1" }; + float trigger_next_margin{ 0.1 }; + float input_next_margin{ 0.2 }; + NodePath next_light_attack_state{}; -class PlayerBasicAttackState : public CharacterState { - GDCLASS(PlayerBasicAttackState, CharacterState); - static void _bind_methods(); + double delta_since_start{ 0.0 }; + float anim_length{}; -private: - String animation_name{}; - String next_light_attack{}; + bool next_queued{ false }; + bool animation_started{ false }; + +public: + GET_SET_FNS(String, animation_name); + GET_SET_FNS(NodePath, next_light_attack_state); + GET_SET_FNS(float, trigger_next_margin); + GET_SET_FNS(float, input_next_margin); }; diff --git a/modules/break_utopia/register_types.cpp b/modules/break_utopia/register_types.cpp index 0038590d..35a550d7 100644 --- a/modules/break_utopia/register_types.cpp +++ b/modules/break_utopia/register_types.cpp @@ -10,8 +10,10 @@ void initialize_break_utopia_module(ModuleInitializationLevel p_level) { } ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); } void uninitialize_break_utopia_module(ModuleInitializationLevel p_level) { diff --git a/project/assets/models/character.blend b/project/assets/models/character.blend index c1f6ccf8..36d498f9 100644 Binary files a/project/assets/models/character.blend and b/project/assets/models/character.blend differ diff --git a/project/assets/models/character.blend1 b/project/assets/models/character.blend1 index 5d460f72..f666cae1 100644 Binary files a/project/assets/models/character.blend1 and b/project/assets/models/character.blend1 differ diff --git a/project/objects/player_body.tscn b/project/objects/player_body.tscn index aacc74ea..2d1eb4e1 100644 --- a/project/objects/player_body.tscn +++ b/project/objects/player_body.tscn @@ -88,6 +88,16 @@ graph_offset = Vector2(-228.371, 13.6511) [node name="PlayerRunState" type="PlayerRunState" parent="."] +[node name="LightAttack" type="PlayerBasicAttackState" parent="."] +next_light_attack_state = NodePath("../LightAttack2") + +[node name="LightAttack2" type="PlayerBasicAttackState" parent="."] +animation_name = "swing_2" +next_light_attack_state = NodePath("../LightAttack3") + +[node name="LightAttack3" type="PlayerBasicAttackState" parent="."] +animation_name = "swing_3" + [node name="CollisionShape3D" type="CollisionShape3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.949463, 0) shape = SubResource("CapsuleShape3D_5j0w6") diff --git a/project/project.godot b/project/project.godot index 63bf1773..6c08f259 100644 --- a/project/project.godot +++ b/project/project.godot @@ -37,3 +37,8 @@ move_right={ "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) ] } +light_attack={ +"deadzone": 0.2, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(276.9, 17.7383),"global_position":Vector2(276.9, 48.7383),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null) +] +}