feat: implemented behaviour tree
This commit is contained in:
parent
fc11921776
commit
c2aa6690c3
15 changed files with 465 additions and 3 deletions
31
modules/behaviour_nodes/behaviour_action.cpp
Normal file
31
modules/behaviour_nodes/behaviour_action.cpp
Normal 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
modules/behaviour_nodes/behaviour_action.h
Normal file
17
modules/behaviour_nodes/behaviour_action.h
Normal 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
modules/behaviour_nodes/behaviour_composite.cpp
Normal file
28
modules/behaviour_nodes/behaviour_composite.cpp
Normal 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
modules/behaviour_nodes/behaviour_composite.h
Normal file
18
modules/behaviour_nodes/behaviour_composite.h
Normal 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
modules/behaviour_nodes/behaviour_node.cpp
Normal file
29
modules/behaviour_nodes/behaviour_node.cpp
Normal 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
modules/behaviour_nodes/behaviour_node.h
Normal file
34
modules/behaviour_nodes/behaviour_node.h
Normal 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);
|
||||
49
modules/behaviour_nodes/behaviour_tree.cpp
Normal file
49
modules/behaviour_nodes/behaviour_tree.cpp
Normal file
|
|
@ -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<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()));
|
||||
print_line("new behaviour:", this->current->get_path());
|
||||
this->current->exit();
|
||||
this->current = next;
|
||||
this->current->enter();
|
||||
return cast_to<BehaviourComposite>(this->current);
|
||||
}
|
||||
}
|
||||
18
modules/behaviour_nodes/behaviour_tree.h
Normal file
18
modules/behaviour_nodes/behaviour_tree.h
Normal 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 };
|
||||
};
|
||||
98
modules/behaviour_nodes/control_nodes.cpp
Normal file
98
modules/behaviour_nodes/control_nodes.cpp
Normal file
|
|
@ -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<BehaviourNode>(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<BehaviourNode>(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);
|
||||
}
|
||||
40
modules/behaviour_nodes/control_nodes.h
Normal file
40
modules/behaviour_nodes/control_nodes.h
Normal file
|
|
@ -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;
|
||||
};
|
||||
21
modules/behaviour_nodes/decorator_nodes.cpp
Normal file
21
modules/behaviour_nodes/decorator_nodes.cpp
Normal file
|
|
@ -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<BehaviourNode>(get_parent());
|
||||
}
|
||||
13
modules/behaviour_nodes/decorator_nodes.h
Normal file
13
modules/behaviour_nodes/decorator_nodes.h
Normal file
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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<BehaviourTree>();
|
||||
ClassDB::register_abstract_class<BehaviourNode>();
|
||||
ClassDB::register_class<BehaviourSequence>();
|
||||
ClassDB::register_class<BehaviourRepeater>();
|
||||
ClassDB::register_class<BehaviourSelector>();
|
||||
ClassDB::register_class<BehaviourAction>();
|
||||
}
|
||||
|
||||
void uninitialize_behaviour_nodes_module(ModuleInitializationLevel p_level) {
|
||||
|
|
|
|||
|
|
@ -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 { \
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue