From 2a4e00e6f1f3dfe9956fdc8f921dd08d1fcae94b Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 13 Mar 2026 10:38:52 +0100 Subject: [PATCH] feat: backlog code --- SCsub | 3 + behaviour_action.cpp | 31 ++++++++++ behaviour_action.h | 17 ++++++ behaviour_composite.cpp | 28 ++++++++++ behaviour_composite.h | 18 ++++++ behaviour_node.cpp | 29 ++++++++++ behaviour_node.h | 34 +++++++++++ behaviour_tree.cpp | 52 +++++++++++++++++ behaviour_tree.h | 18 ++++++ config.py | 5 ++ control_nodes.cpp | 121 ++++++++++++++++++++++++++++++++++++++++ control_nodes.h | 50 +++++++++++++++++ decorator_nodes.cpp | 30 ++++++++++ decorator_nodes.h | 15 +++++ register_types.cpp | 27 +++++++++ register_types.h | 9 +++ 16 files changed, 487 insertions(+) create mode 100644 SCsub create mode 100644 behaviour_action.cpp create mode 100644 behaviour_action.h create mode 100644 behaviour_composite.cpp create mode 100644 behaviour_composite.h create mode 100644 behaviour_node.cpp create mode 100644 behaviour_node.h create mode 100644 behaviour_tree.cpp create mode 100644 behaviour_tree.h create mode 100644 config.py create mode 100644 control_nodes.cpp create mode 100644 control_nodes.h create mode 100644 decorator_nodes.cpp create mode 100644 decorator_nodes.h create mode 100644 register_types.cpp create mode 100644 register_types.h diff --git a/SCsub b/SCsub new file mode 100644 index 0000000..2760ab7 --- /dev/null +++ b/SCsub @@ -0,0 +1,3 @@ +Import('env') + +env.add_source_files(env.modules_sources, "*.cpp") diff --git a/behaviour_action.cpp b/behaviour_action.cpp new file mode 100644 index 0000000..b517d27 --- /dev/null +++ b/behaviour_action.cpp @@ -0,0 +1,31 @@ +#include "behaviour_action.h" +#include "core/object/object.h" + +void BehaviourAction::_bind_methods() { + ClassDB::add_virtual_method(get_class_static(), _gdvirtual__execute_get_method_info()); + ClassDB::add_virtual_method(get_class_static(), _gdvirtual__exit_get_method_info()); + ClassDB::add_virtual_method(get_class_static(), _gdvirtual__enter_get_method_info()); +} + +void BehaviourAction::enter() { + GDVIRTUAL_CALL(_enter); +} + +void BehaviourAction::execute() { + Status out_status{ Fail }; + GDVIRTUAL_CALL(_execute, out_status); + set_status(out_status); +} + +void BehaviourAction::exit() { + GDVIRTUAL_CALL(_exit); +} + +BehaviourNode *BehaviourAction::get_next() { + switch (get_status()) { + case Running: + return this; + default: + return cast_to(get_parent()); + } +} diff --git a/behaviour_action.h b/behaviour_action.h new file mode 100644 index 0000000..8f61a5d --- /dev/null +++ b/behaviour_action.h @@ -0,0 +1,17 @@ +#pragma once + +#include "behaviour_nodes/behaviour_node.h" + +class BehaviourAction : public BehaviourNode { + GDCLASS(BehaviourAction, BehaviourNode); + static void _bind_methods(); + +public: + void enter() override; + void execute() override; + void exit() override; + BehaviourNode *get_next() override; + GDVIRTUAL0R_REQUIRED(Status, _execute); + GDVIRTUAL0(_enter); + GDVIRTUAL0(_exit); +}; diff --git a/behaviour_composite.cpp b/behaviour_composite.cpp new file mode 100644 index 0000000..009e670 --- /dev/null +++ b/behaviour_composite.cpp @@ -0,0 +1,28 @@ +#include "behaviour_composite.h" + +void BehaviourComposite::_bind_methods() {} + +void BehaviourComposite::child_order_changed() { + this->child_behaviours.clear(); + for (Variant var : get_children()) { + if (BehaviourNode * node{ cast_to(var) }) { + this->child_behaviours.push_back(node); + } + } +} + +void BehaviourComposite::_notification(int what) { + switch (what) { + default: + return; + case NOTIFICATION_READY: + child_order_changed(); + set_leaf(get_child_behaviours().is_empty()); + return; + case NOTIFICATION_CHILD_ORDER_CHANGED: + if (is_ready()) { + child_order_changed(); + } + return; + } +} diff --git a/behaviour_composite.h b/behaviour_composite.h new file mode 100644 index 0000000..0b4de8e --- /dev/null +++ b/behaviour_composite.h @@ -0,0 +1,18 @@ +#pragma once +#include "behaviour_nodes/behaviour_node.h" + +class BehaviourComposite : public BehaviourNode { + GDCLASS(BehaviourComposite, BehaviourNode); + static void _bind_methods(); + void child_order_changed(); + +protected: + void _notification(int what); + +private: + Vector child_behaviours{}; + +public: + Vector const &get_child_behaviours() const { return this->child_behaviours; } + GET_SET_REF_FNS(Vector, child_behaviours); +}; diff --git a/behaviour_node.cpp b/behaviour_node.cpp new file mode 100644 index 0000000..642dceb --- /dev/null +++ b/behaviour_node.cpp @@ -0,0 +1,29 @@ +#include "behaviour_node.h" +#include "behaviour_nodes/behaviour_tree.h" +#include "core/config/engine.h" + +void BehaviourNode::_bind_methods() { + BIND_ENUM_CONSTANT(Fail); + BIND_ENUM_CONSTANT(Running); + BIND_ENUM_CONSTANT(Success); +} + +void BehaviourNode::_notification(int what) { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + switch (what) { + default: + return; + case NOTIFICATION_ENTER_TREE: + Node *parent{ get_parent() }; + this->parent = cast_to(parent); + while (this->behaviour_tree == nullptr && parent != nullptr) { + if ((this->behaviour_tree = cast_to(parent))) { + break; + } + parent = parent->get_parent(); + } + return; + } +} diff --git a/behaviour_node.h b/behaviour_node.h new file mode 100644 index 0000000..cf865e4 --- /dev/null +++ b/behaviour_node.h @@ -0,0 +1,34 @@ +#pragma once + +#include "macros.h" +#include "scene/main/node.h" + +class BehaviourNode : public Node { + GDCLASS(BehaviourNode, Node); + static void _bind_methods(); + +public: + GDENUM(Status, Fail, Running, Success); + +protected: + void _notification(int what); + +public: + virtual void enter() {} + virtual void execute() {} + virtual void exit() {} + virtual BehaviourNode *get_next() { return this; } + +private: + class BehaviourTree *behaviour_tree{ nullptr }; + BehaviourNode *parent{ nullptr }; + Status status{}; + bool leaf{ false }; + +public: + GET_SET_FNS(class BehaviourTree *, behaviour_tree); + GET_SET_FNS(Status, status); + GET_SET_FNS(bool, leaf); +}; + +MAKE_TYPE_INFO(BehaviourNode::Status, Variant::INT); diff --git a/behaviour_tree.cpp b/behaviour_tree.cpp new file mode 100644 index 0000000..7807183 --- /dev/null +++ b/behaviour_tree.cpp @@ -0,0 +1,52 @@ +#include "behaviour_tree.h" +#include "behaviour_nodes/behaviour_node.h" +#include "behaviour_nodes/control_nodes.h" +#include "core/config/engine.h" + +void BehaviourTree::_bind_methods() {} + +void BehaviourTree::process() { + this->current->execute(); + while (execute_next()) { + this->current->execute(); + } +} + +void BehaviourTree::_notification(int what) { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + switch (what) { + default: + return; + case NOTIFICATION_READY: + for (Variant var : get_children()) { + if ((this->current = cast_to(var))) { + break; + } + } + ERR_FAIL_COND_EDMSG(this->current == nullptr, "No valid BehaviourNode in BehaviourTree"); + set_process(true); + return; + case NOTIFICATION_PROCESS: + process(); + return; + } +} + +bool BehaviourTree::execute_next() { + BehaviourNode *next{ this->current->get_next() }; + if (next == this->current) { + return false; + } else { + ERR_FAIL_COND_V_EDMSG(next == nullptr, false, vformat("%s::get_next returned a nullptr, repeating last node", this->current->get_class())); + if (this->current != next->get_parent()) { + this->current->exit(); + } + if (this->current->get_parent() != next) { + next->enter(); + } + this->current = next; + return cast_to(this->current); + } +} diff --git a/behaviour_tree.h b/behaviour_tree.h new file mode 100644 index 0000000..f9593c1 --- /dev/null +++ b/behaviour_tree.h @@ -0,0 +1,18 @@ +#pragma once + +#include "scene/main/node.h" + +class BehaviourTree : public Node { + GDCLASS(BehaviourTree, Node); + static void _bind_methods(); + void process(); + +protected: + void _notification(int what); + +public: + bool execute_next(); + +private: + class BehaviourNode *current{ nullptr }; +}; diff --git a/config.py b/config.py new file mode 100644 index 0000000..58c88bf --- /dev/null +++ b/config.py @@ -0,0 +1,5 @@ +def can_build(env, platform): + return True; + +def configure(env): + pass; diff --git a/control_nodes.cpp b/control_nodes.cpp new file mode 100644 index 0000000..a8eefbd --- /dev/null +++ b/control_nodes.cpp @@ -0,0 +1,121 @@ +#include "control_nodes.h" +#include "behaviour_nodes/behaviour_node.h" + +PackedStringArray BehaviourSequence::get_configuration_warnings() const { + PackedStringArray warnings{ super_type::get_configuration_warnings() }; + if (get_child_behaviours().is_empty()) { + warnings.push_back("Sequence cannot have zero children"); + } + return warnings; +} + +void BehaviourSequence::execute() { + if (get_child_behaviours().is_empty()) { + set_status(Fail); + ERR_FAIL_EDMSG("BehaviourSequence executed with no children."); + } else if (get_status() == Running && this->current >= 0 && this->current < get_child_behaviours().size()) { + switch (get_child_behaviours().get(this->current)->get_status()) { + case Running: + set_status(Running); + break; + case Fail: + set_status(Fail); + break; + case Success: + ++this->current; + set_status(this->current >= get_child_behaviours().size() + ? Success + : Running); + break; + } + } else { + set_status(Running); + this->current = 0; + } +} + +BehaviourNode *BehaviourSequence::get_next() { + return get_status() == Running + ? get_child_behaviours().get(this->current) + : cast_to(get_parent()); +} + +PackedStringArray BehaviourSelector::get_configuration_warnings() const { + PackedStringArray warnings{ super_type::get_configuration_warnings() }; + if (get_child_behaviours().is_empty()) { + warnings.push_back("Selector cannot have zero children"); + } + return warnings; +} + +void BehaviourSelector::execute() { + if (get_child_behaviours().is_empty()) { + set_status(Fail); + ERR_FAIL_EDMSG("BehaviourSelector execution with no children."); + } else if (get_status() == Running && this->current >= 0 && this->current < get_child_behaviours().size()) { + switch (get_child_behaviours().get(this->current)->get_status()) { + case Running: + set_status(Running); + break; + case Fail: + ++this->current; + set_status(this->current >= get_child_behaviours().size() + ? Fail + : Running); + break; + case Success: + set_status(Success); + break; + } + } else { + set_status(Running); + this->current = 0; + } +} + +BehaviourNode *BehaviourSelector::get_next() { + return get_status() == Running + ? get_child_behaviours().get(this->current) + : cast_to(get_parent()); +} + +PackedStringArray BehaviourRepeater::get_configuration_warnings() const { + PackedStringArray warnings{ super_type::get_configuration_warnings() }; + if (get_child_behaviours().size() != 1) { + warnings.push_back(vformat("Repeater should have exactly one BehaviourNode child, has %d", get_child_behaviours().size())); + } + return warnings; +} + +void BehaviourRepeater::execute() { + set_status(Running); +} + +BehaviourNode *BehaviourRepeater::get_next() { + return get_child_behaviours().get(0); +} + +PackedStringArray BehaviourRepeatUntilFail::get_configuration_warnings() const { + PackedStringArray warnings{ super_type::get_configuration_warnings() }; + if (get_child_behaviours().size() != 1) { + warnings.push_back("RepeatUntilFailure should have exactly one BehaviourNode child"); + } + return warnings; +} + +void BehaviourRepeatUntilFail::execute() { + if (get_child_behaviours().is_empty()) { + set_status(Fail); + ERR_FAIL_EDMSG("BehaviourRepeatUntilFail execution with no child"); + } else { + set_status(get_child_behaviours().get(0)->get_status() == Fail + ? Success + : Running); + } +} + +BehaviourNode *BehaviourRepeatUntilFail::get_next() { + return get_status() == Running + ? get_child_behaviours().get(0) + : cast_to(get_parent()); +} diff --git a/control_nodes.h b/control_nodes.h new file mode 100644 index 0000000..94549a8 --- /dev/null +++ b/control_nodes.h @@ -0,0 +1,50 @@ +#pragma once +#include "behaviour_nodes/behaviour_composite.h" +#include "behaviour_nodes/behaviour_node.h" +#include "core/variant/variant.h" + +class BehaviourSequence : public BehaviourComposite { + GDCLASS(BehaviourSequence, BehaviourComposite); + static void _bind_methods() {} + +public: + PackedStringArray get_configuration_warnings() const override; + void execute() override; + BehaviourNode *get_next() override; + +private: + int current{ -1 }; +}; + +class BehaviourSelector : public BehaviourComposite { + GDCLASS(BehaviourSelector, BehaviourComposite); + static void _bind_methods() {} + +public: + PackedStringArray get_configuration_warnings() const override; + void execute() override; + BehaviourNode *get_next() override; + +private: + int current{ -1 }; +}; + +class BehaviourRepeater : public BehaviourComposite { + GDCLASS(BehaviourRepeater, BehaviourComposite); + static void _bind_methods() {} + +public: + PackedStringArray get_configuration_warnings() const override; + void execute() override; + BehaviourNode *get_next() override; +}; + +class BehaviourRepeatUntilFail : public BehaviourComposite { + GDCLASS(BehaviourRepeatUntilFail, BehaviourComposite); + static void _bind_methods() {} + +public: + PackedStringArray get_configuration_warnings() const override; + void execute() override; + BehaviourNode *get_next() override; +}; diff --git a/decorator_nodes.cpp b/decorator_nodes.cpp new file mode 100644 index 0000000..00bb62d --- /dev/null +++ b/decorator_nodes.cpp @@ -0,0 +1,30 @@ +#include "decorator_nodes.h" +#include "behaviour_nodes/behaviour_node.h" +#include "core/variant/variant.h" + +void BehaviourAlwaysSuccess::_bind_methods() {} + +PackedStringArray BehaviourAlwaysSuccess::get_configuration_warnings() const { + PackedStringArray warnings{ super_type::get_configuration_warnings() }; + if (get_child_behaviours().size() != 1) { + warnings.push_back("BehaviourAlwaysSuccess should have exactly one child"); + } + return warnings; +} + +void BehaviourAlwaysSuccess::execute() { + if (get_child_behaviours().is_empty()) { + set_status(Fail); + ERR_FAIL_EDMSG("BehaviourSequence executed with no children."); + } else if (get_status() == Running) { + set_status(get_child_behaviours().get(0)->get_status() == Running ? Running : Success); + } else { + set_status(Running); + } +} + +BehaviourNode *BehaviourAlwaysSuccess::get_next() { + return get_status() == Running + ? get_child_behaviours().get(0) + : cast_to(get_parent()); +} diff --git a/decorator_nodes.h b/decorator_nodes.h new file mode 100644 index 0000000..fc95249 --- /dev/null +++ b/decorator_nodes.h @@ -0,0 +1,15 @@ +#pragma once + +#include "behaviour_nodes/behaviour_composite.h" +#include "behaviour_nodes/behaviour_node.h" +#include "core/variant/variant.h" + +class BehaviourAlwaysSuccess : public BehaviourComposite { + GDCLASS(BehaviourAlwaysSuccess, BehaviourComposite); + static void _bind_methods(); + +public: + PackedStringArray get_configuration_warnings() const override; + void execute() override; + BehaviourNode *get_next() override; +}; diff --git a/register_types.cpp b/register_types.cpp new file mode 100644 index 0000000..ffb0aea --- /dev/null +++ b/register_types.cpp @@ -0,0 +1,27 @@ +#include "behaviour_nodes/register_types.h" +#include "behaviour_nodes/behaviour_action.h" +#include "behaviour_nodes/behaviour_node.h" +#include "behaviour_nodes/behaviour_tree.h" +#include "behaviour_nodes/control_nodes.h" +#include "behaviour_nodes/decorator_nodes.h" +#include "core/object/class_db.h" + +void initialize_behaviour_nodes_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + ClassDB::register_class(); + ClassDB::register_abstract_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); +} + +void uninitialize_behaviour_nodes_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } +} diff --git a/register_types.h b/register_types.h new file mode 100644 index 0000000..cfb5629 --- /dev/null +++ b/register_types.h @@ -0,0 +1,9 @@ +#ifndef BEHAVIOUR_NODES_REGISTER_TYPES_H +#define BEHAVIOUR_NODES_REGISTER_TYPES_H + +#include "modules/register_module_types.h" + +void initialize_behaviour_nodes_module(ModuleInitializationLevel p_level); +void uninitialize_behaviour_nodes_module(ModuleInitializationLevel p_level); + +#endif // !BEHAVIOUR_NODES_REGISTER_TYPES_H