Implement basics of behavior tree debugger
This commit is contained in:
parent
9344bce9b1
commit
9abfeadc21
1
SCsub
1
SCsub
|
@ -12,5 +12,6 @@ module_env.add_source_files(env.modules_sources, "bt/actions/*.cpp")
|
|||
module_env.add_source_files(env.modules_sources, "bt/decorators/*.cpp")
|
||||
module_env.add_source_files(env.modules_sources, "bt/conditions/*.cpp")
|
||||
module_env.add_source_files(env.modules_sources, "bb_param/*.cpp")
|
||||
module_env.add_source_files(env.modules_sources, "debugger/*.cpp")
|
||||
if env.editor_build:
|
||||
module_env.add_source_files(env.modules_sources, "editor/*.cpp")
|
||||
|
|
|
@ -12,10 +12,16 @@
|
|||
#include "core/os/memory.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "modules/limboai/blackboard.h"
|
||||
#include "modules/limboai/debugger/limbo_debugger.h"
|
||||
|
||||
VARIANT_ENUM_CAST(BTPlayer::UpdateMode);
|
||||
|
||||
void BTPlayer::_load_tree() {
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (tree_instance.is_valid()) {
|
||||
LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path());
|
||||
}
|
||||
#endif
|
||||
tree_instance.unref();
|
||||
ERR_FAIL_COND_MSG(!behavior_tree.is_valid(), "BTPlayer: Needs a valid behavior tree.");
|
||||
ERR_FAIL_COND_MSG(!behavior_tree->get_root_task().is_valid(), "BTPlayer: Behavior tree has no valid root task.");
|
||||
|
@ -23,6 +29,9 @@ void BTPlayer::_load_tree() {
|
|||
blackboard->prefetch_nodepath_vars(this);
|
||||
}
|
||||
tree_instance = behavior_tree->instantiate(get_owner(), blackboard);
|
||||
#ifdef DEBUG_ENABLED
|
||||
LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path());
|
||||
#endif
|
||||
}
|
||||
|
||||
void BTPlayer::set_behavior_tree(const Ref<BehaviorTree> &p_tree) {
|
||||
|
@ -52,6 +61,7 @@ void BTPlayer::update(double p_delta) {
|
|||
}
|
||||
if (active) {
|
||||
last_status = tree_instance->execute(p_delta);
|
||||
emit_signal(LimboStringNames::get_singleton()->updated, last_status);
|
||||
if (last_status == BTTask::SUCCESS || last_status == BTTask::FAILURE) {
|
||||
emit_signal(LimboStringNames::get_singleton()->behavior_tree_finished, last_status);
|
||||
}
|
||||
|
@ -81,6 +91,13 @@ void BTPlayer::_notification(int p_notification) {
|
|||
set_active(active);
|
||||
}
|
||||
} break;
|
||||
#ifdef DEBUG_ENABLED
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
if (tree_instance.is_valid()) {
|
||||
LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path());
|
||||
}
|
||||
} break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,6 +132,7 @@ void BTPlayer::_bind_methods() {
|
|||
BIND_ENUM_CONSTANT(MANUAL);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("behavior_tree_finished", PropertyInfo(Variant::INT, "p_status")));
|
||||
ADD_SIGNAL(MethodInfo("updated", PropertyInfo(Variant::INT, "p_status")));
|
||||
}
|
||||
|
||||
BTPlayer::BTPlayer() {
|
||||
|
|
|
@ -5,11 +5,17 @@
|
|||
#include "core/object/class_db.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "modules/limboai/bt/bt_task.h"
|
||||
#include "modules/limboai/debugger/limbo_debugger.h"
|
||||
#include "modules/limboai/limbo_state.h"
|
||||
#include "modules/limboai/limbo_string_names.h"
|
||||
|
||||
void BTState::_setup() {
|
||||
ERR_FAIL_COND_MSG(behavior_tree.is_null(), "BTState: BehaviorTree is not assigned.");
|
||||
tree_instance = behavior_tree->instantiate(get_agent(), get_blackboard());
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path());
|
||||
#endif
|
||||
}
|
||||
|
||||
void BTState::_exit() {
|
||||
|
@ -20,6 +26,7 @@ void BTState::_exit() {
|
|||
void BTState::_update(double p_delta) {
|
||||
ERR_FAIL_COND(tree_instance == nullptr);
|
||||
int status = tree_instance->execute(p_delta);
|
||||
emit_signal(LimboStringNames::get_singleton()->updated, p_delta);
|
||||
if (status == BTTask::SUCCESS) {
|
||||
get_root()->dispatch(success_event, Variant());
|
||||
} else if (status == BTTask::FAILURE) {
|
||||
|
@ -27,6 +34,16 @@ void BTState::_update(double p_delta) {
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void BTState::_notification(int p_notification) {
|
||||
switch (p_notification) {
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path());
|
||||
} break;
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
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);
|
||||
|
|
|
@ -36,6 +36,12 @@ public:
|
|||
String get_failure_event() const { return failure_event; }
|
||||
|
||||
BTState();
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
protected:
|
||||
void _notification(int p_notification);
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // BT_STATE_H
|
|
@ -140,6 +140,7 @@ int BTTask::execute(double p_delta) {
|
|||
if (!GDVIRTUAL_CALL(_tick, p_delta, status)) {
|
||||
status = _tick(p_delta);
|
||||
}
|
||||
last_tick_usec = Engine::get_singleton()->get_frame_ticks();
|
||||
|
||||
if (status != RUNNING) {
|
||||
if (!GDVIRTUAL_CALL(_exit)) {
|
||||
|
@ -311,6 +312,7 @@ BTTask::BTTask() {
|
|||
parent = nullptr;
|
||||
children = Vector<Ref<BTTask>>();
|
||||
status = FRESH;
|
||||
last_tick_usec = 0;
|
||||
}
|
||||
|
||||
BTTask::~BTTask() {
|
||||
|
|
|
@ -33,6 +33,7 @@ private:
|
|||
BTTask *parent;
|
||||
Vector<Ref<BTTask>> children;
|
||||
int status;
|
||||
int last_tick_usec;
|
||||
|
||||
Array _get_children() const;
|
||||
void _set_children(Array children);
|
||||
|
@ -75,6 +76,7 @@ public:
|
|||
int execute(double p_delta);
|
||||
void cancel();
|
||||
int get_status() const { return status; }
|
||||
int get_last_tick_usec() const { return last_tick_usec; }
|
||||
|
||||
Ref<BTTask> get_child(int p_idx) const;
|
||||
int get_child_count() const;
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/* behavior_tree_data.cpp */
|
||||
|
||||
#include "behavior_tree_data.h"
|
||||
#include "core/templates/list.h"
|
||||
|
||||
//// BehaviorTreeData
|
||||
|
||||
BehaviorTreeData::BehaviorTreeData(const Ref<BTTask> &p_instance) {
|
||||
// Flatten tree into list depth first
|
||||
List<Ref<BTTask>> stack;
|
||||
stack.push_back(p_instance);
|
||||
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));
|
||||
}
|
||||
|
||||
tasks.push_back(TaskData(
|
||||
task->get_task_name(),
|
||||
num_children,
|
||||
task->get_status(),
|
||||
task->get_last_tick_usec(),
|
||||
task->get_class()));
|
||||
}
|
||||
}
|
||||
|
||||
void BehaviorTreeData::serialize(Array &p_arr) {
|
||||
for (const TaskData &td : tasks) {
|
||||
p_arr.push_back(td.name);
|
||||
p_arr.push_back(td.num_children);
|
||||
p_arr.push_back(td.status);
|
||||
p_arr.push_back(td.last_tick_usec);
|
||||
p_arr.push_back(td.type_name);
|
||||
}
|
||||
}
|
||||
|
||||
void BehaviorTreeData::deserialize(const Array &p_arr) {
|
||||
ERR_FAIL_COND(tasks.size() != 0);
|
||||
|
||||
int idx = 0;
|
||||
while (p_arr.size() > idx) {
|
||||
ERR_FAIL_COND(p_arr.size() < 5);
|
||||
ERR_FAIL_COND(p_arr[idx].get_type() != Variant::STRING);
|
||||
ERR_FAIL_COND(p_arr[idx + 1].get_type() != Variant::INT);
|
||||
ERR_FAIL_COND(p_arr[idx + 2].get_type() != Variant::INT);
|
||||
ERR_FAIL_COND(p_arr[idx + 3].get_type() != Variant::INT);
|
||||
ERR_FAIL_COND(p_arr[idx + 4].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]));
|
||||
idx += 5;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/* behavior_tree_data.h */
|
||||
|
||||
#ifndef BEHAVIOR_TREE_DATA_H
|
||||
#define BEHAVIOR_TREE_DATA_H
|
||||
|
||||
#include "core/object/object.h"
|
||||
#include "modules/limboai/bt/bt_task.h"
|
||||
|
||||
class BehaviorTreeData {
|
||||
public:
|
||||
struct TaskData {
|
||||
String name;
|
||||
// ObjectID object_id;
|
||||
int num_children = 0;
|
||||
int status = 0;
|
||||
int last_tick_usec = 0;
|
||||
String type_name;
|
||||
// String script_path;
|
||||
// String resource_path;
|
||||
|
||||
TaskData(const String &p_name, int p_num_children, int p_status, int p_last_tick_usec, const String &p_type_name) {
|
||||
name = p_name;
|
||||
num_children = p_num_children;
|
||||
status = p_status;
|
||||
last_tick_usec = p_last_tick_usec;
|
||||
type_name = p_type_name;
|
||||
}
|
||||
|
||||
TaskData() {}
|
||||
};
|
||||
|
||||
List<TaskData> tasks;
|
||||
|
||||
void serialize(Array &p_arr);
|
||||
void deserialize(const Array &p_arr);
|
||||
|
||||
BehaviorTreeData(const Ref<BTTask> &p_instance);
|
||||
BehaviorTreeData() {}
|
||||
};
|
||||
|
||||
#endif // BEHAVIOR_TREE_DATA
|
|
@ -0,0 +1,65 @@
|
|||
/* behavior_tree_view.cpp */
|
||||
|
||||
#include "behavior_tree_view.h"
|
||||
#include "behavior_tree_data.h"
|
||||
#include "core/math/color.h"
|
||||
#include "modules/limboai/bt/bt_task.h"
|
||||
#include "modules/limboai/limbo_utility.h"
|
||||
|
||||
void BehaviorTreeView::update_tree(const BehaviorTreeData &p_data) {
|
||||
tree->clear();
|
||||
int recent_tick_usec = -1;
|
||||
TreeItem *parent = nullptr;
|
||||
List<Pair<TreeItem *, int>> parents;
|
||||
for (const BehaviorTreeData::TaskData &task_data : p_data.tasks) {
|
||||
// Figure out parent.
|
||||
parent = nullptr;
|
||||
if (parents.size()) {
|
||||
Pair<TreeItem *, int> &p = parents[0];
|
||||
parent = p.first;
|
||||
if (!(--p.second)) {
|
||||
// No children left, remove it.
|
||||
parents.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
TreeItem *item = tree->create_item(parent);
|
||||
item->set_text(0, task_data.name);
|
||||
item->set_icon(0, LimboUtility::get_singleton()->get_task_icon(task_data.type_name));
|
||||
|
||||
// First task in list is the root task and has most recent tick time.
|
||||
if (recent_tick_usec == -1) {
|
||||
recent_tick_usec = task_data.last_tick_usec;
|
||||
}
|
||||
// Item BG color depends on age of the task status.
|
||||
int timediff = recent_tick_usec - task_data.last_tick_usec;
|
||||
if (timediff <= 300000) {
|
||||
float alpha = (300000 - timediff) / 300000.0;
|
||||
if (task_data.status == BTTask::SUCCESS) {
|
||||
item->set_custom_bg_color(0, Color(0, 0.5, 0, alpha));
|
||||
} else if (task_data.status == BTTask::FAILURE) {
|
||||
item->set_custom_bg_color(0, Color(0.5, 0, 0, alpha));
|
||||
} else if (task_data.status == BTTask::RUNNING) {
|
||||
item->set_custom_bg_color(0, Color(0.5, 0.5, 0, alpha));
|
||||
}
|
||||
}
|
||||
|
||||
// Add in front of parents stack if it expects children.
|
||||
if (task_data.num_children) {
|
||||
parents.push_front(Pair<TreeItem *, int>(item, task_data.num_children));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BehaviorTreeView::clear() {
|
||||
tree->clear();
|
||||
}
|
||||
|
||||
BehaviorTreeView::BehaviorTreeView() {
|
||||
tree = memnew(Tree);
|
||||
add_child(tree);
|
||||
tree->set_columns(1);
|
||||
tree->set_column_expand(0, true);
|
||||
tree->set_anchor(SIDE_RIGHT, ANCHOR_END);
|
||||
tree->set_anchor(SIDE_BOTTOM, ANCHOR_END);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/* behavior_tree_view.h */
|
||||
|
||||
#ifndef BEHAVIOR_TREE_VIEW_H
|
||||
#define BEHAVIOR_TREE_VIEW_H
|
||||
|
||||
#include "behavior_tree_data.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/object.h"
|
||||
#include "scene/gui/control.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class BehaviorTreeView : public Control {
|
||||
GDCLASS(BehaviorTreeView, Control);
|
||||
|
||||
private:
|
||||
Tree *tree;
|
||||
|
||||
public:
|
||||
BehaviorTreeView();
|
||||
|
||||
void update_tree(const BehaviorTreeData &p_data);
|
||||
void clear();
|
||||
};
|
||||
|
||||
#endif // BEHAVIOR_TREE_VIEW
|
|
@ -0,0 +1,144 @@
|
|||
/* limbo_debugger.cpp */
|
||||
|
||||
#include "limbo_debugger.h"
|
||||
#include "behavior_tree_data.h"
|
||||
#include "core/debugger/engine_debugger.h"
|
||||
#include "core/error/error_macros.h"
|
||||
#include "core/string/node_path.h"
|
||||
#include "modules/limboai/bt/bt_task.h"
|
||||
#include "scene/main/scene_tree.h"
|
||||
#include "scene/main/window.h"
|
||||
|
||||
//// LimboDebugger
|
||||
|
||||
LimboDebugger *LimboDebugger::singleton = nullptr;
|
||||
LimboDebugger *LimboDebugger::get_singleton() {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
LimboDebugger::LimboDebugger() {
|
||||
singleton = this;
|
||||
#ifdef DEBUG_ENABLED
|
||||
EngineDebugger::register_message_capture("limboai", EngineDebugger::Capture(nullptr, LimboDebugger::parse_message));
|
||||
#endif
|
||||
}
|
||||
|
||||
LimboDebugger::~LimboDebugger() {
|
||||
singleton = nullptr;
|
||||
}
|
||||
|
||||
void LimboDebugger::initialize() {
|
||||
if (EngineDebugger::is_active()) {
|
||||
memnew(LimboDebugger);
|
||||
}
|
||||
}
|
||||
|
||||
void LimboDebugger::deinitialize() {
|
||||
if (singleton) {
|
||||
memdelete(singleton);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
Error LimboDebugger::parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) {
|
||||
r_captured = true;
|
||||
if (p_msg == "track_bt_updates") {
|
||||
singleton->_track_tree(p_args[0]);
|
||||
} else if (p_msg == "untrack_bt_updates") {
|
||||
// unregister bt for updates
|
||||
} else if (p_msg == "start_session") {
|
||||
singleton->session_active = true;
|
||||
singleton->_send_active_behavior_trees();
|
||||
} else if (p_msg == "stop_session") {
|
||||
singleton->session_active = false;
|
||||
} else {
|
||||
r_captured = false;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
void LimboDebugger::register_bt_instance(Ref<BTTask> p_instance, NodePath p_path) {
|
||||
ERR_FAIL_COND(active_trees.has(p_path));
|
||||
|
||||
active_trees.insert(p_path, p_instance);
|
||||
if (session_active) {
|
||||
_send_active_behavior_trees();
|
||||
}
|
||||
}
|
||||
|
||||
void LimboDebugger::unregister_bt_instance(Ref<BTTask> p_instance, NodePath p_path) {
|
||||
ERR_FAIL_COND(!active_trees.has(p_path));
|
||||
|
||||
if (tracked_tree == p_path) {
|
||||
_untrack_tree();
|
||||
}
|
||||
active_trees.erase(p_path);
|
||||
|
||||
if (session_active) {
|
||||
_send_active_behavior_trees();
|
||||
}
|
||||
}
|
||||
|
||||
void LimboDebugger::_track_tree(NodePath p_path) {
|
||||
ERR_FAIL_COND(!active_trees.has(p_path));
|
||||
|
||||
if (!tracked_tree.is_empty()) {
|
||||
_untrack_tree();
|
||||
}
|
||||
|
||||
Node *node = SceneTree::get_singleton()->get_root()->get_node(p_path);
|
||||
ERR_FAIL_COND(node == nullptr);
|
||||
|
||||
tracked_tree = p_path;
|
||||
if (node->is_class("BTPlayer")) {
|
||||
node->connect(SNAME("updated"), callable_mp(this, &LimboDebugger::_on_bt_updated).bind(p_path));
|
||||
} else if (node->is_class("BTState")) {
|
||||
node->connect(SNAME("updated"), callable_mp(this, &LimboDebugger::_on_state_updated).bind(p_path));
|
||||
}
|
||||
}
|
||||
|
||||
void LimboDebugger::_untrack_tree() {
|
||||
if (tracked_tree.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NodePath was_tracking = tracked_tree;
|
||||
tracked_tree = NodePath();
|
||||
|
||||
Node *node = SceneTree::get_singleton()->get_root()->get_node(was_tracking);
|
||||
ERR_FAIL_COND(node == nullptr);
|
||||
|
||||
if (node->is_class("BTPlayer")) {
|
||||
node->disconnect(SNAME("updated"), callable_mp(this, &LimboDebugger::_on_bt_updated));
|
||||
} else if (node->is_class("BTState")) {
|
||||
node->disconnect(SNAME("updated"), callable_mp(this, &LimboDebugger::_on_state_updated));
|
||||
}
|
||||
}
|
||||
|
||||
void LimboDebugger::_send_active_behavior_trees() {
|
||||
Array arr;
|
||||
for (KeyValue<NodePath, Ref<BTTask>> kv : active_trees) {
|
||||
arr.append(kv.key);
|
||||
}
|
||||
EngineDebugger::get_singleton()->send_message("limboai:active_behavior_trees", arr);
|
||||
}
|
||||
|
||||
void LimboDebugger::_on_bt_updated(int _status, NodePath p_path) {
|
||||
if (p_path != tracked_tree) {
|
||||
return;
|
||||
}
|
||||
Array arr;
|
||||
BehaviorTreeData(active_trees.get(tracked_tree)).serialize(arr);
|
||||
EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr);
|
||||
}
|
||||
|
||||
void LimboDebugger::_on_state_updated(float _delta, NodePath p_path) {
|
||||
if (p_path != tracked_tree) {
|
||||
return;
|
||||
}
|
||||
Array arr;
|
||||
BehaviorTreeData(active_trees.get(tracked_tree)).serialize(arr);
|
||||
EngineDebugger::get_singleton()->send_message("limboai:bt_update", arr);
|
||||
}
|
||||
|
||||
#endif // DEBUG_ENABLED
|
|
@ -0,0 +1,47 @@
|
|||
/* limbo_debugger.h */
|
||||
|
||||
#ifndef LIMBO_DEBUGGER_H
|
||||
#define LIMBO_DEBUGGER_H
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/object.h"
|
||||
#include "core/string/node_path.h"
|
||||
#include "modules/limboai/bt/bt_task.h"
|
||||
|
||||
class LimboDebugger : public Object {
|
||||
GDCLASS(LimboDebugger, Object);
|
||||
|
||||
private:
|
||||
static LimboDebugger *singleton;
|
||||
|
||||
LimboDebugger();
|
||||
|
||||
public:
|
||||
static void initialize();
|
||||
static void deinitialize();
|
||||
static LimboDebugger *get_singleton();
|
||||
|
||||
~LimboDebugger();
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
private:
|
||||
HashMap<NodePath, Ref<BTTask>> active_trees;
|
||||
NodePath tracked_tree;
|
||||
bool session_active = false;
|
||||
|
||||
void _track_tree(NodePath p_path);
|
||||
void _untrack_tree();
|
||||
void _send_active_behavior_trees();
|
||||
|
||||
void _on_bt_updated(int status, NodePath p_path);
|
||||
void _on_state_updated(float _delta, NodePath p_path);
|
||||
|
||||
public:
|
||||
static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured);
|
||||
|
||||
void register_bt_instance(Ref<BTTask> p_instance, NodePath p_path);
|
||||
|
||||
void unregister_bt_instance(Ref<BTTask> p_instance, NodePath p_path);
|
||||
#endif // DEBUG_ENABLED
|
||||
};
|
||||
#endif // LIMBO_DEBUGGER
|
|
@ -0,0 +1,152 @@
|
|||
/* limbo_debugger_view.h */
|
||||
|
||||
#include "limbo_debugger_plugin.h"
|
||||
#include "core/debugger/engine_debugger.h"
|
||||
#include "core/math/math_defs.h"
|
||||
#include "core/object/callable_method_pointer.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "core/string/print_string.h"
|
||||
#include "core/variant/array.h"
|
||||
#include "editor/editor_scale.h"
|
||||
#include "editor/plugins/editor_debugger_plugin.h"
|
||||
#include "limbo_debugger.h"
|
||||
#include "modules/limboai/debugger/behavior_tree_data.h"
|
||||
#include "modules/limboai/debugger/behavior_tree_view.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/control.h"
|
||||
#include "scene/gui/item_list.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
|
||||
/////////////////////// LimboDebuggerTab
|
||||
|
||||
void LimboDebuggerTab::start_session() {
|
||||
bt_list->clear();
|
||||
bt_view->clear();
|
||||
info_box->hide();
|
||||
hsc->show();
|
||||
message->hide();
|
||||
session->send_message("limboai:start_session", Array());
|
||||
}
|
||||
|
||||
void LimboDebuggerTab::stop_session() {
|
||||
hsc->hide();
|
||||
message->show();
|
||||
session->send_message("limboai:stop_session", Array());
|
||||
}
|
||||
|
||||
void LimboDebuggerTab::update_bt_list(const Array &p_items) {
|
||||
// Remember selected item.
|
||||
String selected_bt = "";
|
||||
if (bt_list->is_anything_selected()) {
|
||||
selected_bt = bt_list->get_item_text(bt_list->get_selected_items().get(0));
|
||||
}
|
||||
|
||||
bt_list->clear();
|
||||
int select_idx = -1;
|
||||
for (int i = 0; i < p_items.size(); i++) {
|
||||
bt_list->add_item(p_items[i]);
|
||||
if (p_items[i] == selected_bt) {
|
||||
select_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore selected item.
|
||||
if (select_idx > -1) {
|
||||
bt_list->select(select_idx);
|
||||
} else if (!selected_bt.is_empty()) {
|
||||
_set_info_message(TTR("Node instance is gone"));
|
||||
}
|
||||
}
|
||||
|
||||
void LimboDebuggerTab::_set_info_message(const String &p_message) {
|
||||
info_message->set_text(p_message);
|
||||
info_icon->set_texture(get_theme_icon(SNAME("NodeInfo"), SNAME("EditorIcons")));
|
||||
info_box->set_visible(!p_message.is_empty());
|
||||
}
|
||||
|
||||
void LimboDebuggerTab::_bt_selected(int p_idx) {
|
||||
info_box->hide();
|
||||
bt_view->clear();
|
||||
NodePath path = bt_list->get_item_text(p_idx);
|
||||
Array data;
|
||||
data.push_back(path);
|
||||
session->send_message("limboai:track_bt_updates", data);
|
||||
}
|
||||
|
||||
LimboDebuggerTab::LimboDebuggerTab(Ref<EditorDebuggerSession> p_session) {
|
||||
session = p_session;
|
||||
|
||||
hsc = memnew(HSplitContainer);
|
||||
add_child(hsc);
|
||||
|
||||
bt_list = memnew(ItemList);
|
||||
hsc->add_child(bt_list);
|
||||
bt_list->set_custom_minimum_size(Size2(280.0 * EDSCALE, 0.0));
|
||||
bt_list->connect(SNAME("item_selected"), callable_mp(this, &LimboDebuggerTab::_bt_selected));
|
||||
|
||||
view_box = memnew(VBoxContainer);
|
||||
{
|
||||
hsc->add_child(view_box);
|
||||
|
||||
bt_view = memnew(BehaviorTreeView);
|
||||
view_box->add_child(bt_view);
|
||||
bt_view->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
bt_view->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
|
||||
info_box = memnew(HBoxContainer);
|
||||
view_box->add_child(info_box);
|
||||
info_box->hide();
|
||||
|
||||
info_icon = memnew(TextureRect);
|
||||
info_box->add_child(info_icon);
|
||||
info_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
|
||||
|
||||
info_message = memnew(Label);
|
||||
info_box->add_child(info_message);
|
||||
info_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
|
||||
}
|
||||
|
||||
message = memnew(Label);
|
||||
add_child(message);
|
||||
message->set_text(TTR("Run project to start debugging"));
|
||||
message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
||||
message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
|
||||
message->set_anchors_preset(Control::PRESET_CENTER);
|
||||
|
||||
stop_session();
|
||||
}
|
||||
|
||||
//////////////////////// LimboDebuggerPlugin
|
||||
|
||||
void LimboDebuggerPlugin::setup_session(int p_idx) {
|
||||
Ref<EditorDebuggerSession> session = get_session(p_idx);
|
||||
tab = memnew(LimboDebuggerTab(session));
|
||||
tab->set_name("LimboAI");
|
||||
session->connect(SNAME("started"), callable_mp(tab, &LimboDebuggerTab::start_session));
|
||||
session->connect(SNAME("stopped"), callable_mp(tab, &LimboDebuggerTab::stop_session));
|
||||
session->add_session_tab(tab);
|
||||
}
|
||||
|
||||
bool LimboDebuggerPlugin::capture(const String &p_message, const Array &p_data, int p_session) {
|
||||
bool captured = true;
|
||||
if (p_message == "limboai:active_behavior_trees") {
|
||||
tab->update_bt_list(p_data);
|
||||
} else if (p_message == "limboai:bt_update") {
|
||||
BehaviorTreeData data = BehaviorTreeData();
|
||||
data.deserialize(p_data);
|
||||
tab->get_behavior_tree_view()->update_tree(data);
|
||||
} else {
|
||||
captured = false;
|
||||
}
|
||||
return captured;
|
||||
}
|
||||
|
||||
bool LimboDebuggerPlugin::has_capture(const String &p_capture) const {
|
||||
return p_capture == "limboai";
|
||||
}
|
||||
|
||||
LimboDebuggerPlugin::LimboDebuggerPlugin() {
|
||||
tab = nullptr;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/* limbo_debugger_plugin.h */
|
||||
|
||||
#ifndef LIMBO_DEBUGGER_PLUGIN_H
|
||||
#define LIMBO_DEBUGGER_PLUGIN_H
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/object.h"
|
||||
#include "core/typedefs.h"
|
||||
#include "editor/plugins/editor_debugger_plugin.h"
|
||||
#include "modules/limboai/debugger/behavior_tree_view.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/item_list.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
|
||||
class LimboDebuggerTab : public PanelContainer {
|
||||
GDCLASS(LimboDebuggerTab, PanelContainer);
|
||||
|
||||
private:
|
||||
Ref<EditorDebuggerSession> session;
|
||||
HSplitContainer *hsc;
|
||||
Label *message;
|
||||
ItemList *bt_list;
|
||||
BehaviorTreeView *bt_view;
|
||||
VBoxContainer *view_box;
|
||||
HBoxContainer *info_box;
|
||||
TextureRect *info_icon;
|
||||
Label *info_message;
|
||||
|
||||
_FORCE_INLINE_ void _set_info_message(const String &p_message);
|
||||
void _bt_selected(int p_idx);
|
||||
|
||||
protected:
|
||||
// void _notification(int p_notification);
|
||||
|
||||
public:
|
||||
void start_session();
|
||||
void stop_session();
|
||||
void update_bt_list(const Array &p_items);
|
||||
BehaviorTreeView *get_behavior_tree_view() { return bt_view; }
|
||||
|
||||
LimboDebuggerTab(Ref<EditorDebuggerSession> p_session);
|
||||
};
|
||||
|
||||
class LimboDebuggerPlugin : public EditorDebuggerPlugin {
|
||||
GDCLASS(LimboDebuggerPlugin, EditorDebuggerPlugin);
|
||||
|
||||
private:
|
||||
LimboDebuggerTab *tab;
|
||||
|
||||
public:
|
||||
void setup_session(int p_idx) override;
|
||||
bool has_capture(const String &p_capture) const override;
|
||||
bool capture(const String &p_message, const Array &p_data, int p_session) override;
|
||||
|
||||
LimboDebuggerPlugin();
|
||||
};
|
||||
|
||||
#endif // LIMBO_DEBUGGER_PLUGIN
|
|
@ -47,6 +47,8 @@
|
|||
#include "modules/limboai/bt/composites/bt_parallel.h"
|
||||
#include "modules/limboai/bt/composites/bt_selector.h"
|
||||
#include "modules/limboai/bt/composites/bt_sequence.h"
|
||||
#include "modules/limboai/debugger/limbo_debugger_plugin.h"
|
||||
#include "modules/limboai/limbo_utility.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/control.h"
|
||||
|
@ -82,11 +84,13 @@ void TaskTree::_update_item(TreeItem *p_item) {
|
|||
Ref<BTTask> task = p_item->get_metadata(0);
|
||||
ERR_FAIL_COND_MSG(!task.is_valid(), "Invalid task reference in metadata.");
|
||||
p_item->set_text(0, task->get_task_name());
|
||||
String type_arg;
|
||||
if (task->get_script_instance() && !task->get_script_instance()->get_script()->get_path().is_empty()) {
|
||||
p_item->set_icon(0, LimboAIEditor::get_task_icon(task->get_script_instance()->get_script()->get_path()));
|
||||
type_arg = task->get_script_instance()->get_script()->get_path();
|
||||
} else {
|
||||
p_item->set_icon(0, LimboAIEditor::get_task_icon(task->get_class()));
|
||||
type_arg = task->get_class();
|
||||
}
|
||||
p_item->set_icon(0, LimboUtility::get_singleton()->get_task_icon(type_arg));
|
||||
p_item->set_editable(0, false);
|
||||
|
||||
for (int i = 0; i < p_item->get_button_count(0); i++) {
|
||||
|
@ -455,7 +459,7 @@ void TaskPanel::refresh() {
|
|||
|
||||
TaskSection *sec = memnew(TaskSection(cat));
|
||||
for (String task_meta : tasks) {
|
||||
Ref<Texture> icon = LimboAIEditor::get_task_icon(task_meta);
|
||||
Ref<Texture> icon = LimboUtility::get_singleton()->get_task_icon(task_meta);
|
||||
String tname;
|
||||
if (task_meta.begins_with("res:")) {
|
||||
tname = task_meta.get_file().get_basename().trim_prefix("BT").to_pascal_case();
|
||||
|
@ -1019,35 +1023,6 @@ void LimboAIEditor::apply_changes() {
|
|||
}
|
||||
}
|
||||
|
||||
Ref<Texture> LimboAIEditor::get_task_icon(String p_script_path_or_class) {
|
||||
ERR_FAIL_COND_V_MSG(p_script_path_or_class.is_empty(), Variant(), "BTTask: script path or class cannot be empty.");
|
||||
|
||||
String base_type = p_script_path_or_class;
|
||||
if (p_script_path_or_class.begins_with("res:")) {
|
||||
Ref<Script> script = ResourceLoader::load(p_script_path_or_class, "Script");
|
||||
Ref<Script> base_script = script;
|
||||
while (base_script.is_valid()) {
|
||||
StringName name = EditorNode::get_editor_data().script_class_get_name(base_script->get_path());
|
||||
String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name);
|
||||
if (!icon_path.is_empty()) {
|
||||
Ref<Image> img = memnew(Image);
|
||||
Error err = ImageLoader::load_image(icon_path, img);
|
||||
if (err == OK) {
|
||||
Ref<ImageTexture> icon = memnew(ImageTexture);
|
||||
img->resize(16 * EDSCALE, 16 * EDSCALE, Image::INTERPOLATE_LANCZOS);
|
||||
icon->create_from_image(img);
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
base_script = base_script->get_base_script();
|
||||
}
|
||||
base_type = script->get_instance_base_type();
|
||||
}
|
||||
|
||||
// TODO: Walk inheritance tree until icon is found.
|
||||
return EditorNode::get_singleton()->get_class_icon(base_type, "BTTask");
|
||||
}
|
||||
|
||||
void LimboAIEditor::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
|
@ -1326,6 +1301,7 @@ LimboAIEditorPlugin::LimboAIEditorPlugin() {
|
|||
limbo_ai_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
EditorNode::get_singleton()->get_main_screen_control()->add_child(limbo_ai_editor);
|
||||
limbo_ai_editor->hide();
|
||||
add_debugger_plugin(memnew(LimboDebuggerPlugin));
|
||||
}
|
||||
|
||||
LimboAIEditorPlugin::~LimboAIEditorPlugin() {
|
||||
|
|
|
@ -182,8 +182,6 @@ protected:
|
|||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
static Ref<Texture> get_task_icon(String p_script_path_or_class);
|
||||
|
||||
void edit_bt(Ref<BehaviorTree> p_behavior_tree, bool p_force_refresh = false);
|
||||
|
||||
void apply_changes();
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
#include "limbo_utility.h"
|
||||
#include "bt/bt_task.h"
|
||||
#include "core/io/image_loader.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_scale.h"
|
||||
#include "scene/resources/texture.h"
|
||||
|
||||
LimboUtility *LimboUtility::singleton = nullptr;
|
||||
|
||||
|
@ -34,9 +38,39 @@ String LimboUtility::get_status_name(int p_status) const {
|
|||
}
|
||||
}
|
||||
|
||||
Ref<Texture2D> LimboUtility::get_task_icon(String p_class_or_script_path) const {
|
||||
ERR_FAIL_COND_V_MSG(p_class_or_script_path.is_empty(), Variant(), "BTTask: script path or class cannot be empty.");
|
||||
|
||||
String base_type = p_class_or_script_path;
|
||||
if (p_class_or_script_path.begins_with("res:")) {
|
||||
Ref<Script> script = ResourceLoader::load(p_class_or_script_path, "Script");
|
||||
Ref<Script> base_script = script;
|
||||
while (base_script.is_valid()) {
|
||||
StringName name = EditorNode::get_editor_data().script_class_get_name(base_script->get_path());
|
||||
String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name);
|
||||
if (!icon_path.is_empty()) {
|
||||
Ref<Image> img = memnew(Image);
|
||||
Error err = ImageLoader::load_image(icon_path, img);
|
||||
if (err == OK) {
|
||||
Ref<ImageTexture> icon = memnew(ImageTexture);
|
||||
img->resize(16 * EDSCALE, 16 * EDSCALE, Image::INTERPOLATE_LANCZOS);
|
||||
icon->create_from_image(img);
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
base_script = base_script->get_base_script();
|
||||
}
|
||||
base_type = script->get_instance_base_type();
|
||||
}
|
||||
|
||||
// TODO: Walk inheritance tree until icon is found.
|
||||
return EditorNode::get_singleton()->get_class_icon(base_type, "BTTask");
|
||||
}
|
||||
|
||||
void LimboUtility::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("decorate_var", "p_variable"), &LimboUtility::decorate_var);
|
||||
ClassDB::bind_method(D_METHOD("get_status_name", "p_status"), &LimboUtility::get_status_name);
|
||||
ClassDB::bind_method(D_METHOD("get_task_icon"), &LimboUtility::get_task_icon);
|
||||
}
|
||||
|
||||
LimboUtility::LimboUtility() {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/object.h"
|
||||
#include "scene/resources/texture.h"
|
||||
|
||||
class LimboUtility : public Object {
|
||||
GDCLASS(LimboUtility, Object);
|
||||
|
@ -18,6 +19,7 @@ public:
|
|||
|
||||
String decorate_var(String p_variable) const;
|
||||
String get_status_name(int p_status) const;
|
||||
Ref<Texture2D> get_task_icon(String p_class_or_script_path) const;
|
||||
|
||||
LimboUtility();
|
||||
~LimboUtility();
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
#include "limbo_state.h"
|
||||
#include "limbo_string_names.h"
|
||||
#include "limbo_utility.h"
|
||||
#include "modules/limboai/debugger/limbo_debugger.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "editor/limbo_ai_editor_plugin.h"
|
||||
|
@ -86,6 +87,8 @@ static LimboUtility *_limbo_utility = nullptr;
|
|||
|
||||
void initialize_limboai_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
LimboDebugger::initialize();
|
||||
|
||||
GDREGISTER_CLASS(Blackboard);
|
||||
|
||||
GDREGISTER_CLASS(LimboState);
|
||||
|
@ -180,6 +183,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) {
|
|||
|
||||
void uninitialize_limboai_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
LimboDebugger::deinitialize();
|
||||
LimboStringNames::free();
|
||||
memdelete(_limbo_utility);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue