diff --git a/modules/behaviour_nodes/behaviour_action.cpp b/modules/behaviour_nodes/behaviour_action.cpp new file mode 100644 index 00000000..b517d276 --- /dev/null +++ b/modules/behaviour_nodes/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/modules/behaviour_nodes/behaviour_action.h b/modules/behaviour_nodes/behaviour_action.h new file mode 100644 index 00000000..8f61a5d0 --- /dev/null +++ b/modules/behaviour_nodes/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/modules/behaviour_nodes/behaviour_composite.cpp b/modules/behaviour_nodes/behaviour_composite.cpp new file mode 100644 index 00000000..009e6706 --- /dev/null +++ b/modules/behaviour_nodes/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/modules/behaviour_nodes/behaviour_composite.h b/modules/behaviour_nodes/behaviour_composite.h new file mode 100644 index 00000000..0b4de8ec --- /dev/null +++ b/modules/behaviour_nodes/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/modules/behaviour_nodes/behaviour_node.cpp b/modules/behaviour_nodes/behaviour_node.cpp new file mode 100644 index 00000000..642dceb2 --- /dev/null +++ b/modules/behaviour_nodes/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/modules/behaviour_nodes/behaviour_node.h b/modules/behaviour_nodes/behaviour_node.h new file mode 100644 index 00000000..cf865e4b --- /dev/null +++ b/modules/behaviour_nodes/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/modules/behaviour_nodes/behaviour_tree.cpp b/modules/behaviour_nodes/behaviour_tree.cpp new file mode 100644 index 00000000..235ede1a --- /dev/null +++ b/modules/behaviour_nodes/behaviour_tree.cpp @@ -0,0 +1,49 @@ +#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())); + print_line("new behaviour:", this->current->get_path()); + this->current->exit(); + this->current = next; + this->current->enter(); + return cast_to(this->current); + } +} diff --git a/modules/behaviour_nodes/behaviour_tree.h b/modules/behaviour_nodes/behaviour_tree.h new file mode 100644 index 00000000..f9593c15 --- /dev/null +++ b/modules/behaviour_nodes/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/modules/behaviour_nodes/control_nodes.cpp b/modules/behaviour_nodes/control_nodes.cpp new file mode 100644 index 00000000..ba0054b9 --- /dev/null +++ b/modules/behaviour_nodes/control_nodes.cpp @@ -0,0 +1,98 @@ +#include "control_nodes.h" +#include "behaviour_nodes/behaviour_node.h" + +void BehaviourSequence::_bind_methods() {} + +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()); +} + +void BehaviourSelector::_bind_methods() {} + +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()); +} + +void BehaviourRepeater::_bind_methods() {} + +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); +} diff --git a/modules/behaviour_nodes/control_nodes.h b/modules/behaviour_nodes/control_nodes.h new file mode 100644 index 00000000..ad76543b --- /dev/null +++ b/modules/behaviour_nodes/control_nodes.h @@ -0,0 +1,40 @@ +#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, BehaviourNode); + 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, BehaviourNode); + static void _bind_methods(); + +public: + PackedStringArray get_configuration_warnings() const override; + void execute() override; + BehaviourNode *get_next() override; +}; diff --git a/modules/behaviour_nodes/decorator_nodes.cpp b/modules/behaviour_nodes/decorator_nodes.cpp new file mode 100644 index 00000000..83207893 --- /dev/null +++ b/modules/behaviour_nodes/decorator_nodes.cpp @@ -0,0 +1,21 @@ +#include "decorator_nodes.h" +#include "behaviour_nodes/behaviour_node.h" + +void BehaviourAlwaysSuccess::_bind_methods() {} + +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/modules/behaviour_nodes/decorator_nodes.h b/modules/behaviour_nodes/decorator_nodes.h new file mode 100644 index 00000000..d3fd9ad8 --- /dev/null +++ b/modules/behaviour_nodes/decorator_nodes.h @@ -0,0 +1,13 @@ +#pragma once + +#include "behaviour_nodes/behaviour_composite.h" +#include "behaviour_nodes/behaviour_node.h" + +class BehaviourAlwaysSuccess : public BehaviourComposite { + GDCLASS(BehaviourAlwaysSuccess, BehaviourComposite); + static void _bind_methods(); + +public: + void execute() override; + BehaviourNode *get_next() override; +}; diff --git a/modules/behaviour_nodes/register_types.cpp b/modules/behaviour_nodes/register_types.cpp index c8b9cfaf..ff93e69e 100644 --- a/modules/behaviour_nodes/register_types.cpp +++ b/modules/behaviour_nodes/register_types.cpp @@ -1,9 +1,20 @@ #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 "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(); } void uninitialize_behaviour_nodes_module(ModuleInitializationLevel p_level) { diff --git a/modules/macros.h b/modules/macros.h index c139f1f0..dd949d22 100644 --- a/modules/macros.h +++ b/modules/macros.h @@ -24,6 +24,13 @@ void set_##m_property(m_type value) { \ this->m_property = value; \ } +#define GET_SET_REF_FNS(m_type, m_property) \ + m_type &get_##m_property() { \ + return this->m_property; \ + } \ + void set_##m_property(m_type &value) { \ + this->m_property = value; \ + } #define GET_SET_FNS_EX(m_type, m_property, m_ex) \ m_type get_##m_property() const { \ diff --git a/project/objects/party_member.tscn b/project/objects/party_member.tscn index 815bf819..1539dc1e 100644 --- a/project/objects/party_member.tscn +++ b/project/objects/party_member.tscn @@ -6,6 +6,40 @@ [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_4iifp"] +[sub_resource type="GDScript" id="GDScript_0torn"] +script/source = "extends BehaviourAction + +var done : bool = false + +func is_done(): + done = true + +func _ready(): + $Timer.timeout.connect(is_done) + +func _enter(): + $Timer.start(.1) + done = false + +func _execute() -> int: + return Success if done else Running; +" + +[sub_resource type="GDScript" id="GDScript_ilnb3"] +script/source = "extends BehaviourAction + +func _execute() -> int: + return Fail +" + +[sub_resource type="GDScript" id="GDScript_p8kq6"] +script/source = "extends BehaviourAction + +func _execute() -> int: + push_error(\"THIS SHOULDN'T BE HAPPENING\") + return Running +" + [node name="PartyMember" type="Character" unique_id=2124931928] data = ExtResource("1_0torn") @@ -16,9 +50,23 @@ skeleton = NodePath("../CollisionShape3D") [node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=705075802] shape = SubResource("CapsuleShape3D_4iifp") -[node name="PartyMemberFollow" type="PartyMemberFollow" parent="." unique_id=1261360781] -start_active = true - [node name="NavigationAgent3D" type="NavigationAgent3D" parent="." unique_id=1509674092] unique_name_in_owner = true avoidance_enabled = true + +[node name="BehaviourTree" type="BehaviourTree" parent="." unique_id=1234785753] + +[node name="BehaviourRepeater" type="BehaviourRepeater" parent="BehaviourTree" unique_id=144360337] + +[node name="BehaviourSelector" type="BehaviourSelector" parent="BehaviourTree/BehaviourRepeater" unique_id=1728256939] + +[node name="BehaviourSequence" type="BehaviourSequence" parent="BehaviourTree/BehaviourRepeater/BehaviourSelector" unique_id=698711477] + +[node name="BehaviourAction" type="BehaviourAction" parent="BehaviourTree/BehaviourRepeater/BehaviourSelector/BehaviourSequence" unique_id=1778836105] +script = SubResource("GDScript_0torn") + +[node name="BehaviourAction2" type="BehaviourAction" parent="BehaviourTree/BehaviourRepeater/BehaviourSelector/BehaviourSequence" unique_id=63024452] +script = SubResource("GDScript_ilnb3") + +[node name="BehaviourAction3" type="BehaviourAction" parent="BehaviourTree/BehaviourRepeater/BehaviourSelector/BehaviourSequence" unique_id=1490528246] +script = SubResource("GDScript_p8kq6")