Skip to content

Commit

Permalink
Merge pull request #24 from limbonaut/expose-behavior-tree-view
Browse files Browse the repository at this point in the history
Expose `BehaviorTreeView` and `BehaviorTreeData` to use with custom in-game tools
  • Loading branch information
limbonaut authored Feb 3, 2024
2 parents 5bd8a90 + 3ce199f commit 74a3635
Show file tree
Hide file tree
Showing 20 changed files with 226 additions and 99 deletions.
2 changes: 2 additions & 0 deletions bt/bt_player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ void BTPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("restart"), &BTPlayer::restart);
ClassDB::bind_method(D_METHOD("get_last_status"), &BTPlayer::get_last_status);

ClassDB::bind_method(D_METHOD("get_tree_instance"), &BTPlayer::get_tree_instance);

ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "behavior_tree", PROPERTY_HINT_RESOURCE_TYPE, "BehaviorTree"), "set_behavior_tree", "get_behavior_tree");
ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "Idle,Physics,Manual"), "set_update_mode", "get_update_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "get_active");
Expand Down
2 changes: 2 additions & 0 deletions bt/bt_player.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ class BTPlayer : public Node {
void restart();
int get_last_status() const { return last_status; }

Ref<BTTask> get_tree_instance() { return tree_instance; }

BTPlayer();
~BTPlayer();

Expand Down
2 changes: 2 additions & 0 deletions bt/bt_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ void BTState::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_behavior_tree", "p_value"), &BTState::set_behavior_tree);
ClassDB::bind_method(D_METHOD("get_behavior_tree"), &BTState::get_behavior_tree);

ClassDB::bind_method(D_METHOD("get_tree_instance"), &BTState::get_tree_instance);

ClassDB::bind_method(D_METHOD("set_success_event", "p_event_name"), &BTState::set_success_event);
ClassDB::bind_method(D_METHOD("get_success_event"), &BTState::get_success_event);

Expand Down
2 changes: 2 additions & 0 deletions bt/bt_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class BTState : public LimboState {
void set_behavior_tree(const Ref<BehaviorTree> &p_value);
Ref<BehaviorTree> get_behavior_tree() const { return behavior_tree; }

Ref<BTTask> get_tree_instance() const { return tree_instance; }

void set_success_event(String p_success_event) { success_event = p_success_event; }
String get_success_event() const { return success_event; }

Expand Down
1 change: 1 addition & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def get_doc_classes():
"BBVector4",
"BBVector4i",
"BehaviorTree",
"BehaviorTreeData",
"BehaviorTreeView",
"Blackboard",
"BlackboardPlan",
Expand Down
6 changes: 6 additions & 0 deletions doc_classes/BTPlayer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
Returns the behavior tree's last execution status. See [enum BT.Status].
</description>
</method>
<method name="get_tree_instance">
<return type="BTTask" />
<description>
Returns the root task of the instantiated behavior tree.
</description>
</method>
<method name="restart">
<return type="void" />
<description>
Expand Down
8 changes: 8 additions & 0 deletions doc_classes/BTState.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_tree_instance" qualifiers="const">
<return type="BTTask" />
<description>
Returns the root task of the instantiated behavior tree.
</description>
</method>
</methods>
<members>
<member name="behavior_tree" type="BehaviorTree" setter="set_behavior_tree" getter="get_behavior_tree">
A [BehaviorTree] resource that defines state behavior.
Expand Down
22 changes: 22 additions & 0 deletions doc_classes/BehaviorTreeData.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="BehaviorTreeData" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Represents current state of a [BehaviorTree] instance.
</brief_description>
<description>
This class is used by the LimboAI debugger for the serialization and deserialization of [BehaviorTree] instance data.
Additionally, it can be used with [BehaviorTreeView] to visualize the current state of a [BehaviorTree] instance. It is meant to be utilized in custom in-game tools.
</description>
<tutorials>
</tutorials>
<methods>
<method name="create_from_tree_instance" qualifiers="static">
<return type="BehaviorTreeData" />
<param index="0" name="p_tree_instance" type="BTTask" />
<description>
Returns current state of the [param p_tree_instance] encoded as a [BehaviorTreeData], suitable for use with [BehaviorTreeView].
Behavior tree instance can be acquired with [method BTPlayer.get_tree_instance].
</description>
</method>
</methods>
</class>
20 changes: 20 additions & 0 deletions doc_classes/BehaviorTreeView.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="BehaviorTreeView" inherits="Control" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Visualizes the current state of a [BehaviorTree] instance.
</brief_description>
<description>
Visualizes the current state of a [BehaviorTree] instance. See also [BehaviorTreeData].
</description>
<tutorials>
</tutorials>
<methods>
<method name="update_tree">
<return type="void" />
<param index="0" name="p_behavior_tree_data" type="BehaviorTreeData" />
<description>
Updates the representation of a [BehaviorTree] instance. See also [BehaviorTreeData].
</description>
</method>
</methods>
</class>
110 changes: 72 additions & 38 deletions editor/debugger/behavior_tree_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,54 +17,78 @@

//**** BehaviorTreeData

void BehaviorTreeData::serialize(Array &p_arr) {
p_arr.push_back(bt_player_path);
p_arr.push_back(bt_resource_path);
for (const TaskData &td : tasks) {
p_arr.push_back(td.id);
p_arr.push_back(td.name);
p_arr.push_back(td.is_custom_name);
p_arr.push_back(td.num_children);
p_arr.push_back(td.status);
p_arr.push_back(td.elapsed_time);
p_arr.push_back(td.type_name);
p_arr.push_back(td.script_path);
Array BehaviorTreeData::serialize(const Ref<BTTask> &p_tree_instance, const NodePath &p_player_path, const String &p_bt_resource_path) {
Array arr;
arr.push_back(p_player_path);
arr.push_back(p_bt_resource_path);

// Flatten tree into list depth first
List<Ref<BTTask>> stack;
stack.push_back(p_tree_instance);
int id = 0;
while (stack.size()) {
Ref<BTTask> task = stack[0];
stack.pop_front();

int num_children = task->get_child_count();
for (int i = 0; i < num_children; i++) {
stack.push_front(task->get_child(num_children - 1 - i));
}

String script_path;
if (task->get_script()) {
Ref<Resource> s = task->get_script();
script_path = s->get_path();
}

arr.push_back(id);
arr.push_back(task->get_task_name());
arr.push_back(!task->get_custom_name().is_empty());
arr.push_back(num_children);
arr.push_back(task->get_status());
arr.push_back(task->get_elapsed_time());
arr.push_back(task->get_class());
arr.push_back(script_path);

id += 1;
}
}

void BehaviorTreeData::deserialize(const Array &p_arr) {
ERR_FAIL_COND(tasks.size() != 0);
ERR_FAIL_COND(p_arr.size() < 2);
return arr;
}

ERR_FAIL_COND(p_arr[0].get_type() != Variant::NODE_PATH);
bt_player_path = p_arr[0];
Ref<BehaviorTreeData> BehaviorTreeData::deserialize(const Array &p_array) {
ERR_FAIL_COND_V(p_array.size() < 2, nullptr);
ERR_FAIL_COND_V(p_array[0].get_type() != Variant::NODE_PATH, nullptr);
ERR_FAIL_COND_V(p_array[1].get_type() != Variant::STRING, nullptr);

ERR_FAIL_COND(p_arr[1].get_type() != Variant::STRING);
bt_resource_path = p_arr[1];
Ref<BehaviorTreeData> data = memnew(BehaviorTreeData);
data->bt_player_path = p_array[0];
data->bt_resource_path = p_array[1];

int idx = 2;
while (p_arr.size() > idx + 1) {
ERR_FAIL_COND(p_arr.size() < idx + 7);
ERR_FAIL_COND(p_arr[idx].get_type() != Variant::INT);
ERR_FAIL_COND(p_arr[idx + 1].get_type() != Variant::STRING);
ERR_FAIL_COND(p_arr[idx + 2].get_type() != Variant::BOOL);
ERR_FAIL_COND(p_arr[idx + 3].get_type() != Variant::INT);
ERR_FAIL_COND(p_arr[idx + 4].get_type() != Variant::INT);
ERR_FAIL_COND(p_arr[idx + 5].get_type() != Variant::FLOAT);
ERR_FAIL_COND(p_arr[idx + 6].get_type() != Variant::STRING);
ERR_FAIL_COND(p_arr[idx + 7].get_type() != Variant::STRING);
tasks.push_back(TaskData(p_arr[idx], p_arr[idx + 1], p_arr[idx + 2], p_arr[idx + 3], p_arr[idx + 4], p_arr[idx + 5], p_arr[idx + 6], p_arr[idx + 7]));
while (p_array.size() > idx + 1) {
ERR_FAIL_COND_V(p_array.size() < idx + 7, nullptr);
ERR_FAIL_COND_V(p_array[idx].get_type() != Variant::INT, nullptr);
ERR_FAIL_COND_V(p_array[idx + 1].get_type() != Variant::STRING, nullptr);
ERR_FAIL_COND_V(p_array[idx + 2].get_type() != Variant::BOOL, nullptr);
ERR_FAIL_COND_V(p_array[idx + 3].get_type() != Variant::INT, nullptr);
ERR_FAIL_COND_V(p_array[idx + 4].get_type() != Variant::INT, nullptr);
ERR_FAIL_COND_V(p_array[idx + 5].get_type() != Variant::FLOAT, nullptr);
ERR_FAIL_COND_V(p_array[idx + 6].get_type() != Variant::STRING, nullptr);
ERR_FAIL_COND_V(p_array[idx + 7].get_type() != Variant::STRING, nullptr);
data->tasks.push_back(TaskData(p_array[idx], p_array[idx + 1], p_array[idx + 2], p_array[idx + 3], p_array[idx + 4], p_array[idx + 5], p_array[idx + 6], p_array[idx + 7]));
idx += 8;
}

return data;
}

BehaviorTreeData::BehaviorTreeData(const Ref<BTTask> &p_instance, const NodePath &p_player_path, const String &p_bt_resource) {
bt_player_path = p_player_path;
bt_resource_path = p_bt_resource;
Ref<BehaviorTreeData> BehaviorTreeData::create_from_tree_instance(const Ref<BTTask> &p_tree_instance) {
Ref<BehaviorTreeData> data = memnew(BehaviorTreeData);

// Flatten tree into list depth first
List<Ref<BTTask>> stack;
stack.push_back(p_instance);
stack.push_back(p_tree_instance);
int id = 0;
while (stack.size()) {
Ref<BTTask> task = stack[0];
Expand All @@ -77,11 +101,11 @@ BehaviorTreeData::BehaviorTreeData(const Ref<BTTask> &p_instance, const NodePath

String script_path;
if (task->get_script()) {
Ref<Resource> script = task->get_script();
script_path = script->get_path();
Ref<Resource> s = task->get_script();
script_path = s->get_path();
}

tasks.push_back(TaskData(
data->tasks.push_back(TaskData(
id,
task->get_task_name(),
!task->get_custom_name().is_empty(),
Expand All @@ -92,4 +116,14 @@ BehaviorTreeData::BehaviorTreeData(const Ref<BTTask> &p_instance, const NodePath
script_path));
id += 1;
}
return data;
}

void BehaviorTreeData::_bind_methods() {
// ClassDB::bind_static_method("BehaviorTreeData", D_METHOD("serialize", "p_tree_instance", "p_player_path", "p_bt_resource_path"), &BehaviorTreeData::serialize);
// ClassDB::bind_static_method("BehaviorTreeData", D_METHOD("deserialize", "p_array"), &BehaviorTreeData::deserialize);
ClassDB::bind_static_method("BehaviorTreeData", D_METHOD("create_from_tree_instance", "p_tree_instance"), &BehaviorTreeData::create_from_tree_instance);
}

BehaviorTreeData::BehaviorTreeData() {
}
16 changes: 11 additions & 5 deletions editor/debugger/behavior_tree_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@

#include "../../bt/tasks/bt_task.h"

class BehaviorTreeData {
class BehaviorTreeData : public RefCounted {
GDCLASS(BehaviorTreeData, RefCounted);

protected:
static void _bind_methods();

public:
struct TaskData {
int id = 0;
Expand Down Expand Up @@ -44,11 +49,12 @@ class BehaviorTreeData {
NodePath bt_player_path;
String bt_resource_path;

void serialize(Array &p_arr);
void deserialize(const Array &p_arr);
public:
static Array serialize(const Ref<BTTask> &p_tree_instance, const NodePath &p_player_path, const String &p_bt_resource_path);
static Ref<BehaviorTreeData> deserialize(const Array &p_array);
static Ref<BehaviorTreeData> create_from_tree_instance(const Ref<BTTask> &p_tree_instance);

BehaviorTreeData(const Ref<BTTask> &p_instance, const NodePath &p_player_path, const String &p_bt_resource);
BehaviorTreeData() {}
BehaviorTreeData();
};

#endif // BEHAVIOR_TREE_DATA_H
27 changes: 19 additions & 8 deletions editor/debugger/behavior_tree_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,15 @@ void BehaviorTreeView::_item_collapsed(Object *p_obj) {
}
}

void BehaviorTreeView::update_tree(const BehaviorTreeData &p_data) {
double BehaviorTreeView::_get_editor_scale() const {
if (Engine::get_singleton()->is_editor_hint()) {
return EDSCALE;
} else {
return 0.0;
}
}

void BehaviorTreeView::update_tree(const Ref<BehaviorTreeData> &p_data) {
// Remember selected.
int selected_id = -1;
if (tree->get_selected()) {
Expand All @@ -71,7 +79,7 @@ void BehaviorTreeView::update_tree(const BehaviorTreeData &p_data) {
tree->clear();
TreeItem *parent = nullptr;
List<Pair<TreeItem *, int>> parents;
for (const BehaviorTreeData::TaskData &task_data : p_data.tasks) {
for (const BehaviorTreeData::TaskData &task_data : p_data->tasks) {
// Figure out parent.
parent = nullptr;
if (parents.size()) {
Expand Down Expand Up @@ -99,7 +107,7 @@ void BehaviorTreeView::update_tree(const BehaviorTreeData &p_data) {

String cors = (task_data.script_path.is_empty()) ? task_data.type_name : task_data.script_path;
item->set_icon(0, LimboUtility::get_singleton()->get_task_icon(cors));
item->set_icon_max_width(0, 16 * EDSCALE); // Force user icon size.
item->set_icon_max_width(0, 16 * _get_editor_scale()); // Force user icon size.

if (task_data.status == BTTask::SUCCESS) {
item->set_custom_draw(0, this, LW_NAME(_draw_success_status));
Expand Down Expand Up @@ -164,18 +172,20 @@ void BehaviorTreeView::_do_update_theme_item_cache() {
theme_cache.sbf_failure->set_border_width(SIDE_LEFT, 4.0);
theme_cache.sbf_failure->set_border_width(SIDE_RIGHT, 4.0);

double extra_spacing = EDITOR_GET("interface/theme/additional_spacing");
extra_spacing *= 2.0;
tree->set_column_custom_minimum_width(1, 18.0 * EDSCALE);
tree->set_column_custom_minimum_width(2, (50.0 + extra_spacing) * EDSCALE);
double extra_spacing = 0.0;
if (Engine::get_singleton()->is_editor_hint()) {
extra_spacing = EDITOR_GET("interface/theme/additional_spacing");
extra_spacing *= 2.0;
}
tree->set_column_custom_minimum_width(1, 18 * _get_editor_scale());
tree->set_column_custom_minimum_width(2, (50 + extra_spacing) * _get_editor_scale());
}

void BehaviorTreeView::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
tree->connect(LW_NAME(item_collapsed), callable_mp(this, &BehaviorTreeView::_item_collapsed));
} break;
case NOTIFICATION_POSTINITIALIZE:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
Expand All @@ -189,6 +199,7 @@ void BehaviorTreeView::_bind_methods() {
ClassDB::bind_method(D_METHOD("_draw_success_status"), &BehaviorTreeView::_draw_success_status);
ClassDB::bind_method(D_METHOD("_draw_failure_status"), &BehaviorTreeView::_draw_failure_status);
ClassDB::bind_method(D_METHOD("_item_collapsed"), &BehaviorTreeView::_item_collapsed);
ClassDB::bind_method(D_METHOD("update_tree", "p_behavior_tree_data"), &BehaviorTreeView::update_tree);
}

BehaviorTreeView::BehaviorTreeView() {
Expand Down
3 changes: 2 additions & 1 deletion editor/debugger/behavior_tree_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class BehaviorTreeView : public Control {
void _draw_running_status(Object *p_obj, Rect2 p_rect);
void _draw_failure_status(Object *p_obj, Rect2 p_rect);
void _item_collapsed(Object *p_obj);
double _get_editor_scale() const;

protected:
void _do_update_theme_item_cache();
Expand All @@ -63,7 +64,7 @@ class BehaviorTreeView : public Control {
static void _bind_methods();

public:
void update_tree(const BehaviorTreeData &p_data);
void update_tree(const Ref<BehaviorTreeData> &p_data);
void clear();

BehaviorTreeView();
Expand Down
6 changes: 2 additions & 4 deletions editor/debugger/limbo_debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,17 +184,15 @@ void LimboDebugger::_on_bt_updated(int _status, NodePath p_path) {
if (p_path != tracked_player) {
return;
}
Array arr;
BehaviorTreeData(active_trees.get(tracked_player), tracked_player, bt_resource_path).serialize(arr);
Array arr = BehaviorTreeData::serialize(active_trees.get(tracked_player), tracked_player, bt_resource_path);
EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr);
}

void LimboDebugger::_on_state_updated(float _delta, NodePath p_path) {
if (p_path != tracked_player) {
return;
}
Array arr;
BehaviorTreeData(active_trees.get(tracked_player), tracked_player, bt_resource_path).serialize(arr);
Array arr = BehaviorTreeData::serialize(active_trees.get(tracked_player), tracked_player, bt_resource_path);
EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr);
}

Expand Down
Loading

0 comments on commit 74a3635

Please sign in to comment.