From c5b92aa676ac40565d5b22194e570b75a352e071 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Sat, 3 Feb 2024 16:31:21 +0100 Subject: [PATCH] Expose BehaviorTreeView for tools and in-game use --- bt/bt_player.cpp | 2 + bt/bt_player.h | 2 + bt/bt_state.cpp | 2 + bt/bt_state.h | 2 + editor/debugger/behavior_tree_data.cpp | 130 ++++++++++++++-------- editor/debugger/behavior_tree_data.h | 16 ++- editor/debugger/behavior_tree_view.cpp | 26 +++-- editor/debugger/behavior_tree_view.h | 3 +- editor/debugger/limbo_debugger.cpp | 6 +- editor/debugger/limbo_debugger_plugin.cpp | 9 +- editor/debugger/limbo_debugger_plugin.h | 2 +- register_types.cpp | 3 +- util/limbo_utility.cpp | 82 ++++++++------ 13 files changed, 176 insertions(+), 109 deletions(-) diff --git a/bt/bt_player.cpp b/bt/bt_player.cpp index 32548dd..f01f3f2 100644 --- a/bt/bt_player.cpp +++ b/bt/bt_player.cpp @@ -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"); diff --git a/bt/bt_player.h b/bt/bt_player.h index 703596e..d0cbc86 100644 --- a/bt/bt_player.h +++ b/bt/bt_player.h @@ -77,6 +77,8 @@ public: void restart(); int get_last_status() const { return last_status; } + Ref get_tree_instance() { return tree_instance; } + BTPlayer(); ~BTPlayer(); diff --git a/bt/bt_state.cpp b/bt/bt_state.cpp index 75b2787..72fe994 100644 --- a/bt/bt_state.cpp +++ b/bt/bt_state.cpp @@ -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); diff --git a/bt/bt_state.h b/bt/bt_state.h index 3ebd9ba..62ee8f9 100644 --- a/bt/bt_state.h +++ b/bt/bt_state.h @@ -39,6 +39,8 @@ public: void set_behavior_tree(const Ref &p_value); Ref get_behavior_tree() const { return behavior_tree; } + Ref 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; } diff --git a/editor/debugger/behavior_tree_data.cpp b/editor/debugger/behavior_tree_data.cpp index ad978e8..47a7cab 100644 --- a/editor/debugger/behavior_tree_data.cpp +++ b/editor/debugger/behavior_tree_data.cpp @@ -17,54 +17,14 @@ //**** 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); - } -} - -void BehaviorTreeData::deserialize(const Array &p_arr) { - ERR_FAIL_COND(tasks.size() != 0); - ERR_FAIL_COND(p_arr.size() < 2); - - ERR_FAIL_COND(p_arr[0].get_type() != Variant::NODE_PATH); - bt_player_path = p_arr[0]; - - ERR_FAIL_COND(p_arr[1].get_type() != Variant::STRING); - bt_resource_path = p_arr[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])); - idx += 8; - } -} - -BehaviorTreeData::BehaviorTreeData(const Ref &p_instance, const NodePath &p_player_path, const String &p_bt_resource) { - bt_player_path = p_player_path; - bt_resource_path = p_bt_resource; +Array BehaviorTreeData::serialize(const Ref &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> stack; - stack.push_back(p_instance); + stack.push_back(p_tree_instance); int id = 0; while (stack.size()) { Ref task = stack[0]; @@ -77,11 +37,75 @@ BehaviorTreeData::BehaviorTreeData(const Ref &p_instance, const NodePath String script_path; if (task->get_script()) { - Ref script = task->get_script(); - script_path = script->get_path(); + Ref s = task->get_script(); + script_path = s->get_path(); } - tasks.push_back(TaskData( + 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; + } + + return arr; +} + +Ref 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); + + Ref data = memnew(BehaviorTreeData); + data->bt_player_path = p_array[0]; + data->bt_resource_path = p_array[1]; + + int idx = 2; + 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; +} + +Ref BehaviorTreeData::create_from_tree_instance(const Ref &p_tree_instance) { + Ref data = memnew(BehaviorTreeData); + + // Flatten tree into list depth first + List> stack; + stack.push_back(p_tree_instance); + int id = 0; + while (stack.size()) { + Ref 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 s = task->get_script(); + script_path = s->get_path(); + } + + data->tasks.push_back(TaskData( id, task->get_task_name(), !task->get_custom_name().is_empty(), @@ -92,4 +116,14 @@ BehaviorTreeData::BehaviorTreeData(const Ref &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() { } diff --git a/editor/debugger/behavior_tree_data.h b/editor/debugger/behavior_tree_data.h index 3628440..e5c2335 100644 --- a/editor/debugger/behavior_tree_data.h +++ b/editor/debugger/behavior_tree_data.h @@ -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; @@ -44,11 +49,12 @@ public: 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 &p_tree_instance, const NodePath &p_player_path, const String &p_bt_resource_path); + static Ref deserialize(const Array &p_array); + static Ref create_from_tree_instance(const Ref &p_tree_instance); - BehaviorTreeData(const Ref &p_instance, const NodePath &p_player_path, const String &p_bt_resource); - BehaviorTreeData() {} + BehaviorTreeData(); }; #endif // BEHAVIOR_TREE_DATA_H diff --git a/editor/debugger/behavior_tree_view.cpp b/editor/debugger/behavior_tree_view.cpp index 6e12a12..cf61278 100644 --- a/editor/debugger/behavior_tree_view.cpp +++ b/editor/debugger/behavior_tree_view.cpp @@ -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 &p_data) { // Remember selected. int selected_id = -1; if (tree->get_selected()) { @@ -71,7 +79,7 @@ void BehaviorTreeView::update_tree(const BehaviorTreeData &p_data) { tree->clear(); TreeItem *parent = nullptr; List> 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()) { @@ -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)); @@ -164,10 +172,13 @@ 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) { @@ -189,6 +200,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() { diff --git a/editor/debugger/behavior_tree_view.h b/editor/debugger/behavior_tree_view.h index ae9c3be..49205f7 100644 --- a/editor/debugger/behavior_tree_view.h +++ b/editor/debugger/behavior_tree_view.h @@ -54,6 +54,7 @@ private: 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(); @@ -63,7 +64,7 @@ protected: static void _bind_methods(); public: - void update_tree(const BehaviorTreeData &p_data); + void update_tree(const Ref &p_data); void clear(); BehaviorTreeView(); diff --git a/editor/debugger/limbo_debugger.cpp b/editor/debugger/limbo_debugger.cpp index c5593b1..110db7b 100644 --- a/editor/debugger/limbo_debugger.cpp +++ b/editor/debugger/limbo_debugger.cpp @@ -184,8 +184,7 @@ 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); } @@ -193,8 +192,7 @@ 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); } diff --git a/editor/debugger/limbo_debugger_plugin.cpp b/editor/debugger/limbo_debugger_plugin.cpp index 2a04380..1067669 100644 --- a/editor/debugger/limbo_debugger_plugin.cpp +++ b/editor/debugger/limbo_debugger_plugin.cpp @@ -90,8 +90,8 @@ String LimboDebuggerTab::get_selected_bt_player() { return bt_player_list->get_item_text(bt_player_list->get_selected_items()[0]); } -void LimboDebuggerTab::update_behavior_tree(const BehaviorTreeData &p_data) { - resource_header->set_text(p_data.bt_resource_path); +void LimboDebuggerTab::update_behavior_tree(const Ref &p_data) { + resource_header->set_text(p_data->bt_resource_path); resource_header->set_disabled(false); bt_view->update_tree(p_data); info_message->hide(); @@ -315,9 +315,8 @@ bool LimboDebuggerPlugin::_capture(const String &p_message, const Array &p_data, if (p_message == "limboai:active_bt_players") { tab->update_active_bt_players(p_data); } else if (p_message == "limboai:bt_update") { - BehaviorTreeData data = BehaviorTreeData(); - data.deserialize(p_data); - if (data.bt_player_path == NodePath(tab->get_selected_bt_player())) { + Ref data = BehaviorTreeData::deserialize(p_data); + if (data->bt_player_path == NodePath(tab->get_selected_bt_player())) { tab->update_behavior_tree(data); } } else { diff --git a/editor/debugger/limbo_debugger_plugin.h b/editor/debugger/limbo_debugger_plugin.h index e9341ca..4c95432 100644 --- a/editor/debugger/limbo_debugger_plugin.h +++ b/editor/debugger/limbo_debugger_plugin.h @@ -84,7 +84,7 @@ public: void update_active_bt_players(const Array &p_node_paths); BehaviorTreeView *get_behavior_tree_view() const { return bt_view; } String get_selected_bt_player(); - void update_behavior_tree(const BehaviorTreeData &p_data); + void update_behavior_tree(const Ref &p_data); void setup(Ref p_session, CompatWindowWrapper *p_wrapper); LimboDebuggerTab(); diff --git a/register_types.cpp b/register_types.cpp index 8a7a95a..42e220f 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -128,6 +128,8 @@ static LimboUtility *_limbo_utility = nullptr; void initialize_limboai_module(ModuleInitializationLevel p_level) { if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { + GDREGISTER_CLASS(BehaviorTreeData); + GDREGISTER_CLASS(BehaviorTreeView); #ifdef LIMBOAI_GDEXTENSION GDREGISTER_CLASS(LimboDebugger); #endif @@ -251,7 +253,6 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(CompatShortcutBin); GDREGISTER_CLASS(CompatScreenSelect); GDREGISTER_CLASS(CompatWindowWrapper); - GDREGISTER_CLASS(BehaviorTreeView); GDREGISTER_CLASS(LimboDebuggerTab); GDREGISTER_CLASS(LimboDebuggerPlugin); GDREGISTER_CLASS(BlackboardPlanEditor); diff --git a/util/limbo_utility.cpp b/util/limbo_utility.cpp index 73f35ef..edd1040 100644 --- a/util/limbo_utility.cpp +++ b/util/limbo_utility.cpp @@ -15,6 +15,7 @@ #include "../util/limbo_compat.h" #ifdef LIMBOAI_MODULE +#include "core/config/project_settings.h" #include "core/error/error_macros.h" #include "core/object/script_language.h" #include "core/os/os.h" @@ -71,48 +72,53 @@ String LimboUtility::get_status_name(int p_status) const { } Ref LimboUtility::get_task_icon(String p_class_or_script_path) const { -#if defined(TOOLS_ENABLED) && defined(LIMBOAI_MODULE) ERR_FAIL_COND_V_MSG(p_class_or_script_path.is_empty(), Variant(), "BTTask: script path or class cannot be empty."); - Ref theme = EditorNode::get_singleton()->get_editor_theme(); - ERR_FAIL_COND_V(theme.is_null(), nullptr); + // * Using editor theme +#if defined(TOOLS_ENABLED) && defined(LIMBOAI_MODULE) + if (Engine::get_singleton()->is_editor_hint()) { + Ref theme = EditorNode::get_singleton()->get_editor_theme(); + ERR_FAIL_COND_V(theme.is_null(), nullptr); - if (p_class_or_script_path.begins_with("res:")) { - Ref