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/decorators/*.cpp")
|
||||||
module_env.add_source_files(env.modules_sources, "bt/conditions/*.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, "bb_param/*.cpp")
|
||||||
|
module_env.add_source_files(env.modules_sources, "debugger/*.cpp")
|
||||||
if env.editor_build:
|
if env.editor_build:
|
||||||
module_env.add_source_files(env.modules_sources, "editor/*.cpp")
|
module_env.add_source_files(env.modules_sources, "editor/*.cpp")
|
||||||
|
|
|
@ -12,10 +12,16 @@
|
||||||
#include "core/os/memory.h"
|
#include "core/os/memory.h"
|
||||||
#include "core/variant/variant.h"
|
#include "core/variant/variant.h"
|
||||||
#include "modules/limboai/blackboard.h"
|
#include "modules/limboai/blackboard.h"
|
||||||
|
#include "modules/limboai/debugger/limbo_debugger.h"
|
||||||
|
|
||||||
VARIANT_ENUM_CAST(BTPlayer::UpdateMode);
|
VARIANT_ENUM_CAST(BTPlayer::UpdateMode);
|
||||||
|
|
||||||
void BTPlayer::_load_tree() {
|
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();
|
tree_instance.unref();
|
||||||
ERR_FAIL_COND_MSG(!behavior_tree.is_valid(), "BTPlayer: Needs a valid behavior tree.");
|
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.");
|
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);
|
blackboard->prefetch_nodepath_vars(this);
|
||||||
}
|
}
|
||||||
tree_instance = behavior_tree->instantiate(get_owner(), blackboard);
|
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) {
|
void BTPlayer::set_behavior_tree(const Ref<BehaviorTree> &p_tree) {
|
||||||
|
@ -52,6 +61,7 @@ void BTPlayer::update(double p_delta) {
|
||||||
}
|
}
|
||||||
if (active) {
|
if (active) {
|
||||||
last_status = tree_instance->execute(p_delta);
|
last_status = tree_instance->execute(p_delta);
|
||||||
|
emit_signal(LimboStringNames::get_singleton()->updated, last_status);
|
||||||
if (last_status == BTTask::SUCCESS || last_status == BTTask::FAILURE) {
|
if (last_status == BTTask::SUCCESS || last_status == BTTask::FAILURE) {
|
||||||
emit_signal(LimboStringNames::get_singleton()->behavior_tree_finished, last_status);
|
emit_signal(LimboStringNames::get_singleton()->behavior_tree_finished, last_status);
|
||||||
}
|
}
|
||||||
|
@ -81,6 +91,13 @@ void BTPlayer::_notification(int p_notification) {
|
||||||
set_active(active);
|
set_active(active);
|
||||||
}
|
}
|
||||||
} break;
|
} 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);
|
BIND_ENUM_CONSTANT(MANUAL);
|
||||||
|
|
||||||
ADD_SIGNAL(MethodInfo("behavior_tree_finished", PropertyInfo(Variant::INT, "p_status")));
|
ADD_SIGNAL(MethodInfo("behavior_tree_finished", PropertyInfo(Variant::INT, "p_status")));
|
||||||
|
ADD_SIGNAL(MethodInfo("updated", PropertyInfo(Variant::INT, "p_status")));
|
||||||
}
|
}
|
||||||
|
|
||||||
BTPlayer::BTPlayer() {
|
BTPlayer::BTPlayer() {
|
||||||
|
|
|
@ -5,11 +5,17 @@
|
||||||
#include "core/object/class_db.h"
|
#include "core/object/class_db.h"
|
||||||
#include "core/variant/variant.h"
|
#include "core/variant/variant.h"
|
||||||
#include "modules/limboai/bt/bt_task.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_state.h"
|
||||||
|
#include "modules/limboai/limbo_string_names.h"
|
||||||
|
|
||||||
void BTState::_setup() {
|
void BTState::_setup() {
|
||||||
ERR_FAIL_COND_MSG(behavior_tree.is_null(), "BTState: BehaviorTree is not assigned.");
|
ERR_FAIL_COND_MSG(behavior_tree.is_null(), "BTState: BehaviorTree is not assigned.");
|
||||||
tree_instance = behavior_tree->instantiate(get_agent(), get_blackboard());
|
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() {
|
void BTState::_exit() {
|
||||||
|
@ -20,6 +26,7 @@ void BTState::_exit() {
|
||||||
void BTState::_update(double p_delta) {
|
void BTState::_update(double p_delta) {
|
||||||
ERR_FAIL_COND(tree_instance == nullptr);
|
ERR_FAIL_COND(tree_instance == nullptr);
|
||||||
int status = tree_instance->execute(p_delta);
|
int status = tree_instance->execute(p_delta);
|
||||||
|
emit_signal(LimboStringNames::get_singleton()->updated, p_delta);
|
||||||
if (status == BTTask::SUCCESS) {
|
if (status == BTTask::SUCCESS) {
|
||||||
get_root()->dispatch(success_event, Variant());
|
get_root()->dispatch(success_event, Variant());
|
||||||
} else if (status == BTTask::FAILURE) {
|
} 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() {
|
void BTState::_bind_methods() {
|
||||||
ClassDB::bind_method(D_METHOD("set_behavior_tree", "p_value"), &BTState::set_behavior_tree);
|
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_behavior_tree"), &BTState::get_behavior_tree);
|
||||||
|
|
|
@ -36,6 +36,12 @@ public:
|
||||||
String get_failure_event() const { return failure_event; }
|
String get_failure_event() const { return failure_event; }
|
||||||
|
|
||||||
BTState();
|
BTState();
|
||||||
|
|
||||||
|
#ifdef DEBUG_ENABLED
|
||||||
|
protected:
|
||||||
|
void _notification(int p_notification);
|
||||||
|
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BT_STATE_H
|
#endif // BT_STATE_H
|
|
@ -140,6 +140,7 @@ int BTTask::execute(double p_delta) {
|
||||||
if (!GDVIRTUAL_CALL(_tick, p_delta, status)) {
|
if (!GDVIRTUAL_CALL(_tick, p_delta, status)) {
|
||||||
status = _tick(p_delta);
|
status = _tick(p_delta);
|
||||||
}
|
}
|
||||||
|
last_tick_usec = Engine::get_singleton()->get_frame_ticks();
|
||||||
|
|
||||||
if (status != RUNNING) {
|
if (status != RUNNING) {
|
||||||
if (!GDVIRTUAL_CALL(_exit)) {
|
if (!GDVIRTUAL_CALL(_exit)) {
|
||||||
|
@ -311,6 +312,7 @@ BTTask::BTTask() {
|
||||||
parent = nullptr;
|
parent = nullptr;
|
||||||
children = Vector<Ref<BTTask>>();
|
children = Vector<Ref<BTTask>>();
|
||||||
status = FRESH;
|
status = FRESH;
|
||||||
|
last_tick_usec = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
BTTask::~BTTask() {
|
BTTask::~BTTask() {
|
||||||
|
|
|
@ -33,6 +33,7 @@ private:
|
||||||
BTTask *parent;
|
BTTask *parent;
|
||||||
Vector<Ref<BTTask>> children;
|
Vector<Ref<BTTask>> children;
|
||||||
int status;
|
int status;
|
||||||
|
int last_tick_usec;
|
||||||
|
|
||||||
Array _get_children() const;
|
Array _get_children() const;
|
||||||
void _set_children(Array children);
|
void _set_children(Array children);
|
||||||
|
@ -75,6 +76,7 @@ public:
|
||||||
int execute(double p_delta);
|
int execute(double p_delta);
|
||||||
void cancel();
|
void cancel();
|
||||||
int get_status() const { return status; }
|
int get_status() const { return status; }
|
||||||
|
int get_last_tick_usec() const { return last_tick_usec; }
|
||||||
|
|
||||||
Ref<BTTask> get_child(int p_idx) const;
|
Ref<BTTask> get_child(int p_idx) const;
|
||||||
int get_child_count() 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_parallel.h"
|
||||||
#include "modules/limboai/bt/composites/bt_selector.h"
|
#include "modules/limboai/bt/composites/bt_selector.h"
|
||||||
#include "modules/limboai/bt/composites/bt_sequence.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/box_container.h"
|
||||||
#include "scene/gui/button.h"
|
#include "scene/gui/button.h"
|
||||||
#include "scene/gui/control.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);
|
Ref<BTTask> task = p_item->get_metadata(0);
|
||||||
ERR_FAIL_COND_MSG(!task.is_valid(), "Invalid task reference in metadata.");
|
ERR_FAIL_COND_MSG(!task.is_valid(), "Invalid task reference in metadata.");
|
||||||
p_item->set_text(0, task->get_task_name());
|
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()) {
|
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 {
|
} 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);
|
p_item->set_editable(0, false);
|
||||||
|
|
||||||
for (int i = 0; i < p_item->get_button_count(0); i++) {
|
for (int i = 0; i < p_item->get_button_count(0); i++) {
|
||||||
|
@ -455,7 +459,7 @@ void TaskPanel::refresh() {
|
||||||
|
|
||||||
TaskSection *sec = memnew(TaskSection(cat));
|
TaskSection *sec = memnew(TaskSection(cat));
|
||||||
for (String task_meta : tasks) {
|
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;
|
String tname;
|
||||||
if (task_meta.begins_with("res:")) {
|
if (task_meta.begins_with("res:")) {
|
||||||
tname = task_meta.get_file().get_basename().trim_prefix("BT").to_pascal_case();
|
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) {
|
void LimboAIEditor::_notification(int p_what) {
|
||||||
switch (p_what) {
|
switch (p_what) {
|
||||||
case NOTIFICATION_ENTER_TREE: {
|
case NOTIFICATION_ENTER_TREE: {
|
||||||
|
@ -1326,6 +1301,7 @@ LimboAIEditorPlugin::LimboAIEditorPlugin() {
|
||||||
limbo_ai_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
limbo_ai_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||||
EditorNode::get_singleton()->get_main_screen_control()->add_child(limbo_ai_editor);
|
EditorNode::get_singleton()->get_main_screen_control()->add_child(limbo_ai_editor);
|
||||||
limbo_ai_editor->hide();
|
limbo_ai_editor->hide();
|
||||||
|
add_debugger_plugin(memnew(LimboDebuggerPlugin));
|
||||||
}
|
}
|
||||||
|
|
||||||
LimboAIEditorPlugin::~LimboAIEditorPlugin() {
|
LimboAIEditorPlugin::~LimboAIEditorPlugin() {
|
||||||
|
|
|
@ -182,8 +182,6 @@ protected:
|
||||||
void _notification(int p_what);
|
void _notification(int p_what);
|
||||||
|
|
||||||
public:
|
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 edit_bt(Ref<BehaviorTree> p_behavior_tree, bool p_force_refresh = false);
|
||||||
|
|
||||||
void apply_changes();
|
void apply_changes();
|
||||||
|
|
|
@ -2,7 +2,11 @@
|
||||||
|
|
||||||
#include "limbo_utility.h"
|
#include "limbo_utility.h"
|
||||||
#include "bt/bt_task.h"
|
#include "bt/bt_task.h"
|
||||||
|
#include "core/io/image_loader.h"
|
||||||
#include "core/variant/variant.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;
|
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() {
|
void LimboUtility::_bind_methods() {
|
||||||
ClassDB::bind_method(D_METHOD("decorate_var", "p_variable"), &LimboUtility::decorate_var);
|
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_status_name", "p_status"), &LimboUtility::get_status_name);
|
||||||
|
ClassDB::bind_method(D_METHOD("get_task_icon"), &LimboUtility::get_task_icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
LimboUtility::LimboUtility() {
|
LimboUtility::LimboUtility() {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include "core/object/class_db.h"
|
#include "core/object/class_db.h"
|
||||||
#include "core/object/object.h"
|
#include "core/object/object.h"
|
||||||
|
#include "scene/resources/texture.h"
|
||||||
|
|
||||||
class LimboUtility : public Object {
|
class LimboUtility : public Object {
|
||||||
GDCLASS(LimboUtility, Object);
|
GDCLASS(LimboUtility, Object);
|
||||||
|
@ -18,6 +19,7 @@ public:
|
||||||
|
|
||||||
String decorate_var(String p_variable) const;
|
String decorate_var(String p_variable) const;
|
||||||
String get_status_name(int p_status) const;
|
String get_status_name(int p_status) const;
|
||||||
|
Ref<Texture2D> get_task_icon(String p_class_or_script_path) const;
|
||||||
|
|
||||||
LimboUtility();
|
LimboUtility();
|
||||||
~LimboUtility();
|
~LimboUtility();
|
||||||
|
|
|
@ -77,6 +77,7 @@
|
||||||
#include "limbo_state.h"
|
#include "limbo_state.h"
|
||||||
#include "limbo_string_names.h"
|
#include "limbo_string_names.h"
|
||||||
#include "limbo_utility.h"
|
#include "limbo_utility.h"
|
||||||
|
#include "modules/limboai/debugger/limbo_debugger.h"
|
||||||
|
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
#include "editor/limbo_ai_editor_plugin.h"
|
#include "editor/limbo_ai_editor_plugin.h"
|
||||||
|
@ -86,6 +87,8 @@ static LimboUtility *_limbo_utility = nullptr;
|
||||||
|
|
||||||
void initialize_limboai_module(ModuleInitializationLevel p_level) {
|
void initialize_limboai_module(ModuleInitializationLevel p_level) {
|
||||||
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
|
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||||
|
LimboDebugger::initialize();
|
||||||
|
|
||||||
GDREGISTER_CLASS(Blackboard);
|
GDREGISTER_CLASS(Blackboard);
|
||||||
|
|
||||||
GDREGISTER_CLASS(LimboState);
|
GDREGISTER_CLASS(LimboState);
|
||||||
|
@ -180,6 +183,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) {
|
||||||
|
|
||||||
void uninitialize_limboai_module(ModuleInitializationLevel p_level) {
|
void uninitialize_limboai_module(ModuleInitializationLevel p_level) {
|
||||||
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
|
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||||
|
LimboDebugger::deinitialize();
|
||||||
LimboStringNames::free();
|
LimboStringNames::free();
|
||||||
memdelete(_limbo_utility);
|
memdelete(_limbo_utility);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue