From e5f70b1eb620b5007306ea668dcc0b158e51f03c Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 22 Mar 2024 00:14:15 +0100 Subject: [PATCH] feat: added state interface and state machine --- src/state.cpp | 5 ++++ src/state.hpp | 19 +++++++++++++ src/state_machine.cpp | 63 +++++++++++++++++++++++++++++++++++++++++++ src/state_machine.hpp | 30 +++++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 src/state.cpp create mode 100644 src/state.hpp create mode 100644 src/state_machine.cpp create mode 100644 src/state_machine.hpp diff --git a/src/state.cpp b/src/state.cpp new file mode 100644 index 0000000..b877058 --- /dev/null +++ b/src/state.cpp @@ -0,0 +1,5 @@ +#include "state.hpp" +#include "state_machine.hpp" + +namespace godot { +} diff --git a/src/state.hpp b/src/state.hpp new file mode 100644 index 0000000..90a5a8e --- /dev/null +++ b/src/state.hpp @@ -0,0 +1,19 @@ +#ifndef STATE_HPP +#define STATE_HPP + +#include "godot_cpp/core/defs.hpp" +#include + +namespace godot { +class StateMachine; + +class IState { + friend class StateMachine; +public: + virtual void init(Node *target, StateMachine *machine) = 0; + virtual StringName get_next() const = 0; + _ALWAYS_INLINE_ Node *as_node() { return dynamic_cast(this); } +}; +} + +#endif // !STATE_HPP diff --git a/src/state_machine.cpp b/src/state_machine.cpp new file mode 100644 index 0000000..15e9d89 --- /dev/null +++ b/src/state_machine.cpp @@ -0,0 +1,63 @@ +#include "state_machine.hpp" +#include "state.hpp" +#include "utils/godot_macros.h" +#include +#include +#include + +namespace godot { +void StateMachine::_bind_methods() { +#define CLASSNAME StateMachine + GDPROPERTY(initial_state, Variant::STRING_NAME); +} + +void StateMachine::_enter_tree() { GDGAMEONLY(); + this->override_state(this->initial_state); +} + +void StateMachine::_exit_tree() { GDGAMEONLY(); + for(KeyValue kvp : available) + if(!kvp.value->as_node()->is_inside_tree()) + kvp.value->as_node()->queue_free(); +} + +void StateMachine::_process(double delta_time) { GDGAMEONLY(); + UtilityFunctions::print(this->get_path(), " _process"); + if(this->state) + this->override_state(this->state->get_next()); +} + +void StateMachine::override_state(StringName next) { + // don't change state if target is current + if(this->state != nullptr && next == this->state->as_node()->get_class()) + return; + if(this->state) + this->remove_child(this->state->as_node()); + if(next.is_empty()) { + this->state = nullptr; + return; + } + // instantiate new state if this type is not yet available + if(!this->available.has(next)) { + IState *instance = dynamic_cast(Object::cast_to(ClassDB::instantiate(next))); + if(instance == nullptr) { + UtilityFunctions::push_error("Failure to instantiate state with class '", next, "'"); + return; + } + this->available.insert(next, instance); + instance->init(this->get_parent(), this); + instance->as_node()->set_process_mode(ProcessMode::PROCESS_MODE_INHERIT); + } + // set the new state and add it to the tree + this->state = this->available.get(next); + this->add_child(this->state->as_node()); +} + +void StateMachine::set_initial_state(StringName name) { + this->initial_state = name; +} + +StringName StateMachine::get_initial_state() const { + return this->initial_state; +} +} diff --git a/src/state_machine.hpp b/src/state_machine.hpp new file mode 100644 index 0000000..aa9f335 --- /dev/null +++ b/src/state_machine.hpp @@ -0,0 +1,30 @@ +#ifndef STATE_MACHINE_HPP +#define STATE_MACHINE_HPP + +#include +#include +#include + +namespace godot { +class IState; + +class StateMachine : public Node { + GDCLASS(StateMachine, Node); + static void _bind_methods(); +public: + virtual void _enter_tree() override; + virtual void _exit_tree() override; + virtual void _process(double delta_time) override; + + void override_state(StringName new_state_class); + + void set_initial_state(StringName name); + StringName get_initial_state() const; +private: + String initial_state{}; + IState *state{nullptr}; + HashMap available{}; +}; +} + +#endif // !STATE_MACHINE_HPP