feat: backlog code

This commit is contained in:
Sara Gerretsen 2026-03-13 10:38:52 +01:00
commit 2a4e00e6f1
16 changed files with 487 additions and 0 deletions

3
SCsub Normal file
View file

@ -0,0 +1,3 @@
Import('env')
env.add_source_files(env.modules_sources, "*.cpp")

31
behaviour_action.cpp Normal file
View file

@ -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<BehaviourNode>(get_parent());
}
}

17
behaviour_action.h Normal file
View file

@ -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);
};

28
behaviour_composite.cpp Normal file
View file

@ -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<BehaviourNode>(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;
}
}

18
behaviour_composite.h Normal file
View file

@ -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<BehaviourNode *> child_behaviours{};
public:
Vector<BehaviourNode *> const &get_child_behaviours() const { return this->child_behaviours; }
GET_SET_REF_FNS(Vector<BehaviourNode *>, child_behaviours);
};

29
behaviour_node.cpp Normal file
View file

@ -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<BehaviourNode>(parent);
while (this->behaviour_tree == nullptr && parent != nullptr) {
if ((this->behaviour_tree = cast_to<BehaviourTree>(parent))) {
break;
}
parent = parent->get_parent();
}
return;
}
}

34
behaviour_node.h Normal file
View file

@ -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);

52
behaviour_tree.cpp Normal file
View file

@ -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<BehaviourNode>(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<BehaviourComposite>(this->current);
}
}

18
behaviour_tree.h Normal file
View file

@ -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 };
};

5
config.py Normal file
View file

@ -0,0 +1,5 @@
def can_build(env, platform):
return True;
def configure(env):
pass;

121
control_nodes.cpp Normal file
View file

@ -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<BehaviourNode>(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<BehaviourNode>(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<BehaviourNode>(get_parent());
}

50
control_nodes.h Normal file
View file

@ -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;
};

30
decorator_nodes.cpp Normal file
View file

@ -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<BehaviourNode>(get_parent());
}

15
decorator_nodes.h Normal file
View file

@ -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;
};

27
register_types.cpp Normal file
View file

@ -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<BehaviourTree>();
ClassDB::register_abstract_class<BehaviourNode>();
ClassDB::register_class<BehaviourSequence>();
ClassDB::register_class<BehaviourRepeater>();
ClassDB::register_class<BehaviourSelector>();
ClassDB::register_class<BehaviourAction>();
ClassDB::register_class<BehaviourRepeatUntilFail>();
ClassDB::register_class<BehaviourAlwaysSuccess>();
}
void uninitialize_behaviour_nodes_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}

9
register_types.h Normal file
View file

@ -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