Merge pull request #20 from limbonaut/blackboard-improvements

Rework `Blackboard` API and introduce `BlackboardPlan` resource and editor.

- `BBVariable` object holds the value of a blackboard variable and its metadata (not exposed to the API).
- `BlackboardPlan` resource stores and manages a collection of variables, and is used to construct new `Blackboard` instances.
  - Each `BehaviorTree` resource has its own `BlackboardPlan` resource that acts as a blueprint.
  - `BTPlayer` also has its own `BlackboardPlan` which extends the behavior tree `BlackboardPlan` resource, i.e. variables from BehaviorTree are overridden in the BTPlayer node.
- Editor for the `BlackboardPlan` resources
  - Accessed with "Manage" button in the inspector.
  - Rename, reposition, and change type and hint of the blackboard variables.
  - Edit default variable values directly in the inspector.
- Define variables in the `BehaviorTree` blackboard plan and override those variables in the `BTPlayer`'s plan.
- Fully compatible with both module and GDExtension builds!
This commit is contained in:
Serhii Snitsaruk 2024-01-26 11:14:25 +01:00 committed by GitHub
commit 662738e6bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 1906 additions and 173 deletions

153
blackboard/bb_variable.cpp Normal file
View File

@ -0,0 +1,153 @@
/**
* bb_variable.cpp
* =============================================================================
* Copyright 2021-2024 Serhii Snitsaruk
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* =============================================================================
*/
#include "bb_variable.h"
#include "../util/limbo_compat.h"
void BBVariable::unref() {
if (data && data->refcount.unref()) {
memdelete(data);
}
data = nullptr;
}
// void BBVariable::init_ref() {
// if (data) {
// unref();
// }
// data = memnew(Data);
// data->refcount.init();
// }
void BBVariable::set_value(const Variant &p_value) {
data->value = p_value;
}
Variant BBVariable::get_value() const {
return data->value;
}
void BBVariable::set_type(Variant::Type p_type) {
data->type = p_type;
data->value = VARIANT_DEFAULT(p_type);
}
Variant::Type BBVariable::get_type() const {
return data->type;
}
void BBVariable::set_hint(PropertyHint p_hint) {
data->hint = p_hint;
}
PropertyHint BBVariable::get_hint() const {
return data->hint;
}
void BBVariable::set_hint_string(const String &p_hint_string) {
data->hint_string = p_hint_string;
}
String BBVariable::get_hint_string() const {
return data->hint_string;
}
BBVariable BBVariable::duplicate() const {
BBVariable var;
var.data->hint = data->hint;
var.data->hint_string = data->hint_string;
var.data->type = data->type;
var.data->value = data->value;
return var;
}
bool BBVariable::is_same_prop_info(const BBVariable &p_other) const {
if (data->type != p_other.data->type) {
return false;
}
if (data->hint != p_other.data->hint) {
return false;
}
if (data->hint_string != p_other.data->hint_string) {
return false;
}
return true;
}
void BBVariable::copy_prop_info(const BBVariable &p_other) {
data->type = p_other.data->type;
data->hint = p_other.data->hint;
data->hint_string = p_other.data->hint_string;
}
bool BBVariable::operator==(const BBVariable &p_var) const {
if (data == p_var.data) {
return true;
}
if (!data || !p_var.data) {
return false;
}
if (data->type != p_var.data->type) {
return false;
}
if (data->hint != p_var.data->hint) {
return false;
}
if (data->value != p_var.data->value) {
return false;
}
if (data->hint_string != p_var.data->hint_string) {
return false;
}
return true;
}
bool BBVariable::operator!=(const BBVariable &p_var) const {
return !(*this == p_var);
}
void BBVariable::operator=(const BBVariable &p_var) {
if (this == &p_var) {
return;
}
unref();
if (p_var.data && p_var.data->refcount.ref()) {
data = p_var.data;
}
}
BBVariable::BBVariable(const BBVariable &p_var) {
if (p_var.data && p_var.data->refcount.ref()) {
data = p_var.data;
}
}
BBVariable::BBVariable(Variant::Type p_type, PropertyHint p_hint, const String &p_hint_string) {
data = memnew(Data);
data->refcount.init();
set_type(p_type);
data->hint = p_hint;
data->hint_string = p_hint_string;
}
BBVariable::~BBVariable() {
unref();
}

77
blackboard/bb_variable.h Normal file
View File

@ -0,0 +1,77 @@
/**
* bb_variable.h
* =============================================================================
* Copyright 2021-2024 Serhii Snitsaruk
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* =============================================================================
*/
#ifndef BB_VARIABLE_H
#define BB_VARIABLE_H
#ifdef LIMBOAI_MODULE
#include "core/object/object.h"
#include "core/templates/safe_refcount.h"
#include "core/variant/variant.h"
#endif // LIMBOAI_MODULE
#ifdef LIMBOAI_GDEXTENSION
#include "godot_cpp/core/defs.hpp"
#include "godot_cpp/templates/safe_refcount.hpp"
#include "godot_cpp/variant/variant.hpp"
using namespace godot;
#endif // LIMBOAI_GDEXTENSION
class BBVariable {
private:
struct Data {
SafeRefCount refcount;
Variant value;
Variant::Type type = Variant::NIL;
PropertyHint hint = PropertyHint::PROPERTY_HINT_NONE;
String hint_string;
// bool bound = false;
// uint64_t bound_object = 0;
// StringName bound_property;
};
Data *data = nullptr;
void unref();
// void init_ref();
public:
void set_value(const Variant &p_value);
Variant get_value() const;
void set_type(Variant::Type p_type);
Variant::Type get_type() const;
void set_hint(PropertyHint p_hint);
PropertyHint get_hint() const;
void set_hint_string(const String &p_hint_string);
String get_hint_string() const;
BBVariable duplicate() const;
bool is_same_prop_info(const BBVariable &p_other) const;
void copy_prop_info(const BBVariable &p_other);
// bool is_bound() { return bound; }
// void bind(Node *p_root, NodePath p_path);
// void unbind();
bool operator==(const BBVariable &p_var) const;
bool operator!=(const BBVariable &p_var) const;
void operator=(const BBVariable &p_var);
BBVariable(const BBVariable &p_var);
BBVariable(Variant::Type p_type = Variant::Type::NIL, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = "");
~BBVariable();
};
#endif // BB_VARIABLE_H

View File

@ -22,63 +22,71 @@
#include <godot_cpp/classes/ref.hpp>
#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/core/object.hpp>
#include <godot_cpp/variant/dictionary.hpp>
using namespace godot;
#endif
Ref<Blackboard> Blackboard::top() const {
Ref<Blackboard> bb(this);
while (bb->get_parent_scope().is_valid()) {
bb = bb->get_parent_scope();
while (bb->get_parent().is_valid()) {
bb = bb->get_parent();
}
return bb;
}
Variant Blackboard::get_var(const Variant &p_key, const Variant &p_default) const {
if (data.has(p_key)) {
return data.get(p_key, Variant());
Variant Blackboard::get_var(const String &p_name, const Variant &p_default) const {
if (data.has(p_name)) {
return data.get(p_name).get_value();
} else if (parent.is_valid()) {
return parent->get_var(p_key, p_default);
return parent->get_var(p_name, p_default);
} else {
return p_default;
}
}
void Blackboard::set_var(const Variant &p_key, const Variant &p_value) {
data[p_key] = p_value;
void Blackboard::set_var(const String &p_name, const Variant &p_value) {
if (data.has(p_name)) {
// Not checking type - allowing duck-typing.
data[p_name].set_value(p_value);
} else {
BBVariable var(p_value.get_type());
var.set_value(p_value);
data.insert(p_name, var);
}
}
bool Blackboard::has_var(const Variant &p_key) const {
return data.has(p_key) || (parent.is_valid() && parent->has_var(p_key));
bool Blackboard::has_var(const String &p_name) const {
return data.has(p_name) || (parent.is_valid() && parent->has_var(p_name));
}
void Blackboard::erase_var(const Variant &p_key) {
data.erase(p_key);
void Blackboard::erase_var(const String &p_name) {
data.erase(p_name);
}
void Blackboard::add_var(const String &p_name, const BBVariable &p_var) {
ERR_FAIL_COND(data.has(p_name));
data.insert(p_name, p_var);
}
void Blackboard::prefetch_nodepath_vars(Node *p_node) {
ERR_FAIL_COND(p_node == nullptr);
Array keys = data.keys();
Array values = data.values();
for (int i = 0; i < keys.size(); i++) {
if (values[i].get_type() == Variant::NODE_PATH) {
Node *fetched_node = p_node->get_node_or_null(values[i]);
for (const KeyValue<String, BBVariable> &kv : data) {
BBVariable var = kv.value;
if (var.get_value().get_type() == Variant::NODE_PATH) {
Node *fetched_node = p_node->get_node_or_null(var.get_value());
if (fetched_node != nullptr) {
data[keys[i]] = fetched_node;
var.set_value(fetched_node);
}
}
}
}
void Blackboard::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_data"), &Blackboard::get_data);
ClassDB::bind_method(D_METHOD("set_data", "p_data"), &Blackboard::set_data);
ClassDB::bind_method(D_METHOD("get_var", "p_key", "p_default"), &Blackboard::get_var, Variant());
ClassDB::bind_method(D_METHOD("set_var", "p_key", "p_value"), &Blackboard::set_var);
ClassDB::bind_method(D_METHOD("has_var", "p_key"), &Blackboard::has_var);
ClassDB::bind_method(D_METHOD("set_parent_scope", "p_blackboard"), &Blackboard::set_parent_scope);
ClassDB::bind_method(D_METHOD("get_parent_scope"), &Blackboard::get_parent_scope);
ClassDB::bind_method(D_METHOD("erase_var", "p_key"), &Blackboard::erase_var);
ClassDB::bind_method(D_METHOD("get_var", "p_name", "p_default"), &Blackboard::get_var, Variant());
ClassDB::bind_method(D_METHOD("set_var", "p_name", "p_value"), &Blackboard::set_var);
ClassDB::bind_method(D_METHOD("has_var", "p_name"), &Blackboard::has_var);
ClassDB::bind_method(D_METHOD("set_parent_scope", "p_blackboard"), &Blackboard::set_parent);
ClassDB::bind_method(D_METHOD("get_parent_scope"), &Blackboard::get_parent);
ClassDB::bind_method(D_METHOD("erase_var", "p_name"), &Blackboard::erase_var);
ClassDB::bind_method(D_METHOD("prefetch_nodepath_vars", "p_node"), &Blackboard::prefetch_nodepath_vars);
ClassDB::bind_method(D_METHOD("top"), &Blackboard::top);
}

View File

@ -12,10 +12,11 @@
#ifndef BLACKBOARD_H
#define BLACKBOARD_H
#include "bb_variable.h"
#ifdef LIMBOAI_MODULE
#include "core/object/object.h"
#include "core/object/ref_counted.h"
#include "core/variant/dictionary.h"
#include "core/variant/variant.h"
#include "scene/main/node.h"
#endif // LIMBOAI_MODULE
@ -25,7 +26,7 @@
#include <godot_cpp/classes/ref.hpp>
#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/core/object.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/templates/hash_map.hpp>
using namespace godot;
#endif // LIMBOAI_GDEXTENSION
@ -33,27 +34,28 @@ class Blackboard : public RefCounted {
GDCLASS(Blackboard, RefCounted);
private:
Dictionary data;
HashMap<String, BBVariable> data;
Ref<Blackboard> parent;
protected:
static void _bind_methods();
public:
void set_data(const Dictionary &p_value) { data = p_value; }
Dictionary get_data() const { return data; }
void set_parent_scope(const Ref<Blackboard> &p_blackboard) { parent = p_blackboard; }
Ref<Blackboard> get_parent_scope() const { return parent; }
void set_parent(const Ref<Blackboard> &p_blackboard) { parent = p_blackboard; }
Ref<Blackboard> get_parent() const { return parent; }
Ref<Blackboard> top() const;
Variant get_var(const Variant &p_key, const Variant &p_default) const;
void set_var(const Variant &p_key, const Variant &p_value);
bool has_var(const Variant &p_key) const;
void erase_var(const Variant &p_key);
Variant get_var(const String &p_name, const Variant &p_default) const;
void set_var(const String &p_name, const Variant &p_value);
bool has_var(const String &p_name) const;
void erase_var(const String &p_name);
void add_var(const String &p_name, const BBVariable &p_var);
void prefetch_nodepath_vars(Node *p_node);
// TODO: Add serialization API.
};
#endif // BLACKBOARD_H

View File

@ -0,0 +1,269 @@
/**
* blackboard_plan.cpp
* =============================================================================
* Copyright 2021-2024 Serhii Snitsaruk
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* =============================================================================
*/
#include "blackboard_plan.h"
bool BlackboardPlan::_set(const StringName &p_name, const Variant &p_value) {
String prop_name = p_name;
// * Editor
if (var_map.has(prop_name)) {
var_map[prop_name].set_value(p_value);
return true;
}
// * Storage
if (prop_name.begins_with("var/")) {
String var_name = prop_name.get_slicec('/', 1);
String what = prop_name.get_slicec('/', 2);
if (!var_map.has(var_name) && what == "name") {
add_var(var_name, BBVariable());
}
if (what == "name") {
// We don't store variable name with the variable.
} else if (what == "type") {
var_map[var_name].set_type((Variant::Type)(int)p_value);
} else if (what == "value") {
var_map[var_name].set_value(p_value);
} else if (what == "hint") {
var_map[var_name].set_hint((PropertyHint)(int)p_value);
} else if (what == "hint_string") {
var_map[var_name].set_hint_string(p_value);
} else {
return false;
}
return true;
}
return false;
}
bool BlackboardPlan::_get(const StringName &p_name, Variant &r_ret) const {
String prop_name = p_name;
// * Editor
if (var_map.has(prop_name)) {
r_ret = var_map[prop_name].get_value();
return true;
}
// * Storage
if (!prop_name.begins_with("var/")) {
return false;
}
String var_name = prop_name.get_slicec('/', 1);
String what = prop_name.get_slicec('/', 2);
ERR_FAIL_COND_V(!var_map.has(var_name), false);
if (what == "name") {
r_ret = var_name;
} else if (what == "type") {
r_ret = var_map[var_name].get_type();
} else if (what == "value") {
r_ret = var_map[var_name].get_value();
} else if (what == "hint") {
r_ret = var_map[var_name].get_hint();
} else if (what == "hint_string") {
r_ret = var_map[var_name].get_hint_string();
}
return true;
}
void BlackboardPlan::_get_property_list(List<PropertyInfo> *p_list) const {
for (const Pair<String, BBVariable> &p : var_list) {
String var_name = p.first;
BBVariable var = p.second;
// * Editor
if (!is_derived() || !var_name.begins_with("_")) {
p_list->push_back(PropertyInfo(var.get_type(), var_name, var.get_hint(), var.get_hint_string(), PROPERTY_USAGE_EDITOR));
}
// * Storage
p_list->push_back(PropertyInfo(Variant::STRING, "var/" + var_name + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::INT, "var/" + var_name + "/type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(var.get_type(), "var/" + var_name + "/value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::INT, "var/" + var_name + "/hint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::STRING, "var/" + var_name + "/hint_string", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
}
}
bool BlackboardPlan::_property_can_revert(const StringName &p_name) const {
return base.is_valid() && base->var_map.has(p_name);
}
bool BlackboardPlan::_property_get_revert(const StringName &p_name, Variant &r_property) const {
if (base->var_map.has(p_name)) {
r_property = base->var_map[p_name].get_value();
return true;
}
return false;
}
void BlackboardPlan::set_base_plan(const Ref<BlackboardPlan> &p_base) {
base = p_base;
sync_with_base_plan();
}
void BlackboardPlan::add_var(const String &p_name, const BBVariable &p_var) {
ERR_FAIL_COND(var_map.has(p_name));
var_map.insert(p_name, p_var);
var_list.push_back(Pair<String, BBVariable>(p_name, p_var));
notify_property_list_changed();
emit_changed();
}
void BlackboardPlan::remove_var(const String &p_name) {
ERR_FAIL_COND(!var_map.has(p_name));
var_list.erase(Pair<String, BBVariable>(p_name, var_map[p_name]));
var_map.erase(p_name);
notify_property_list_changed();
emit_changed();
}
BBVariable BlackboardPlan::get_var(const String &p_name) {
ERR_FAIL_COND_V(!var_map.has(p_name), BBVariable());
return var_map.get(p_name);
}
Pair<String, BBVariable> BlackboardPlan::get_var_by_index(int p_index) {
Pair<String, BBVariable> ret;
ERR_FAIL_INDEX_V(p_index, (int)var_map.size(), ret);
return var_list[p_index];
}
PackedStringArray BlackboardPlan::list_vars() const {
PackedStringArray ret;
for (const Pair<String, BBVariable> &p : var_list) {
ret.append(p.first);
}
return ret;
}
String BlackboardPlan::get_var_name(const BBVariable &p_var) const {
for (const Pair<String, BBVariable> &p : var_list) {
if (p.second == p_var) {
return p.first;
}
}
return String();
}
void BlackboardPlan::rename_var(const String &p_name, const String &p_new_name) {
ERR_FAIL_COND(p_new_name.is_empty());
ERR_FAIL_COND(var_map.has(p_new_name));
ERR_FAIL_COND(!var_map.has(p_name));
BBVariable var = var_map[p_name];
Pair<String, BBVariable> new_entry(p_new_name, var);
Pair<String, BBVariable> old_entry(p_name, var);
var_list.find(old_entry)->set(new_entry);
var_map.erase(p_name);
var_map.insert(p_new_name, var);
notify_property_list_changed();
emit_changed();
}
void BlackboardPlan::move_var(int p_index, int p_new_index) {
ERR_FAIL_INDEX(p_index, (int)var_map.size());
ERR_FAIL_INDEX(p_new_index, (int)var_map.size());
if (p_index == p_new_index) {
return;
}
List<Pair<String, BBVariable>>::Element *E = var_list.front();
for (int i = 0; i < p_index; i++) {
E = E->next();
}
List<Pair<String, BBVariable>>::Element *E2 = var_list.front();
for (int i = 0; i < p_new_index; i++) {
E2 = E2->next();
}
var_list.move_before(E, E2);
if (p_new_index > p_index) {
var_list.move_before(E2, E);
}
notify_property_list_changed();
emit_changed();
}
void BlackboardPlan::sync_with_base_plan() {
if (base.is_null()) {
return;
}
bool changed = false;
// Sync variables with the base plan.
for (const Pair<String, BBVariable> &p : base->var_list) {
const String &base_name = p.first;
const BBVariable &base_var = p.second;
if (!var_map.has(base_name)) {
add_var(base_name, base_var.duplicate());
changed = true;
continue;
}
BBVariable var = var_map[base_name];
if (!var.is_same_prop_info(base_var)) {
var.copy_prop_info(base_var);
changed = true;
}
if (var.get_value().get_type() != base_var.get_type()) {
var.set_value(base_var.get_value());
changed = true;
}
}
// Erase variables that do not exist in the base plan.
for (const Pair<String, BBVariable> &p : var_list) {
if (!base->has_var(p.first)) {
remove_var(p.first);
changed = true;
}
}
if (changed) {
notify_property_list_changed();
emit_changed();
}
}
Ref<Blackboard> BlackboardPlan::create_blackboard() {
Ref<Blackboard> bb = memnew(Blackboard);
for (const Pair<String, BBVariable> &p : var_list) {
bb->add_var(p.first, p.second.duplicate());
}
return bb;
}
void BlackboardPlan::populate_blackboard(const Ref<Blackboard> &p_blackboard, bool overwrite) {
for (const Pair<String, BBVariable> &p : var_list) {
if (p_blackboard->has_var(p.first)) {
if (overwrite) {
p_blackboard->erase_var(p.first);
} else {
continue;
}
}
p_blackboard->add_var(p.first, p.second.duplicate());
}
}
BlackboardPlan::BlackboardPlan() {
}

View File

@ -0,0 +1,74 @@
/**
* blackboard_plan.h
* =============================================================================
* Copyright 2021-2024 Serhii Snitsaruk
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* =============================================================================
*/
#ifndef BLACKBOARD_PLAN_H
#define BLACKBOARD_PLAN_H
#include "bb_variable.h"
#include "blackboard.h"
#ifdef LIMBOAI_MODULE
#include "core/io/resource.h"
#endif // LIMBOAI_MODULE
#ifdef LIMBOAI_GDEXTENSION
#include <godot_cpp/classes/resource.hpp>
using namespace godot;
#endif // LIMBOAI_GDEXTENSION
class BlackboardPlan : public Resource {
GDCLASS(BlackboardPlan, Resource);
private:
List<Pair<String, BBVariable>> var_list;
HashMap<String, BBVariable> var_map;
// When base is not null, the plan is considered to be derived from the base plan.
// A derived plan can only have variables that exist in the base plan,
// and only the values can be different in those variables.
Ref<BlackboardPlan> base;
protected:
static void _bind_methods() {}
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
bool _property_can_revert(const StringName &p_name) const;
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
public:
void set_base_plan(const Ref<BlackboardPlan> &p_base);
Ref<BlackboardPlan> get_base_plan() const { return base; }
void add_var(const String &p_name, const BBVariable &p_var);
void remove_var(const String &p_name);
BBVariable get_var(const String &p_name);
Pair<String, BBVariable> get_var_by_index(int p_index);
bool has_var(const String &p_name) { return var_map.has(p_name); }
bool is_empty() const { return var_map.is_empty(); }
int get_var_count() const { return var_map.size(); }
PackedStringArray list_vars() const;
String get_var_name(const BBVariable &p_var) const;
void rename_var(const String &p_name, const String &p_new_name);
void move_var(int p_index, int p_new_index);
void sync_with_base_plan();
bool is_derived() const { return base.is_valid(); }
Ref<Blackboard> create_blackboard();
void populate_blackboard(const Ref<Blackboard> &p_blackboard, bool overwrite);
BlackboardPlan();
};
#endif // BLACKBOARD_PLAN_H

View File

@ -22,6 +22,24 @@
#include "godot_cpp/core/error_macros.hpp"
#endif // ! LIMBOAI_GDEXTENSION
void BehaviorTree::set_description(const String &p_value) {
description = p_value;
emit_changed();
}
void BehaviorTree::set_blackboard_plan(const Ref<BlackboardPlan> &p_plan) {
blackboard_plan = p_plan;
if (blackboard_plan.is_null()) {
blackboard_plan = Ref<BlackboardPlan>(memnew(BlackboardPlan));
}
emit_changed();
}
void BehaviorTree::set_root_task(const Ref<BTTask> &p_value) {
root_task = p_value;
emit_changed();
}
Ref<BehaviorTree> BehaviorTree::clone() const {
Ref<BehaviorTree> copy = duplicate(false);
copy->set_path("");
@ -47,6 +65,8 @@ Ref<BTTask> BehaviorTree::instantiate(Node *p_agent, const Ref<Blackboard> &p_bl
void BehaviorTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_description", "p_value"), &BehaviorTree::set_description);
ClassDB::bind_method(D_METHOD("get_description"), &BehaviorTree::get_description);
ClassDB::bind_method(D_METHOD("set_blackboard_plan", "p_plan"), &BehaviorTree::set_blackboard_plan);
ClassDB::bind_method(D_METHOD("get_blackboard_plan"), &BehaviorTree::get_blackboard_plan);
ClassDB::bind_method(D_METHOD("set_root_task", "p_value"), &BehaviorTree::set_root_task);
ClassDB::bind_method(D_METHOD("get_root_task"), &BehaviorTree::get_root_task);
ClassDB::bind_method(D_METHOD("clone"), &BehaviorTree::clone);
@ -54,5 +74,9 @@ void BehaviorTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("instantiate", "p_agent", "p_blackboard"), &BehaviorTree::instantiate);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "description", PROPERTY_HINT_MULTILINE_TEXT), "set_description", "get_description");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard_plan", PROPERTY_HINT_RESOURCE_TYPE, "BlackboardPlan", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_blackboard_plan", "get_blackboard_plan");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root_task", PROPERTY_HINT_RESOURCE_TYPE, "BTTask", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_root_task", "get_root_task");
}
BehaviorTree::BehaviorTree() {
}

View File

@ -12,11 +12,11 @@
#ifndef BEHAVIOR_TREE_H
#define BEHAVIOR_TREE_H
#include "../blackboard/blackboard_plan.h"
#include "tasks/bt_task.h"
#ifdef LIMBOAI_MODULE
#include "core/io/resource.h"
#include "modules/limboai/blackboard/blackboard.h"
#endif // LIMBOAI_MODULE
#ifdef LIMBOAI_GDEXTENSION
@ -29,6 +29,7 @@ class BehaviorTree : public Resource {
private:
String description;
Ref<BlackboardPlan> blackboard_plan;
Ref<BTTask> root_task;
protected:
@ -39,21 +40,20 @@ public:
virtual bool editor_can_reload_from_file() override { return false; }
#endif
void set_description(String p_value) {
description = p_value;
emit_changed();
}
void set_description(const String &p_value);
String get_description() const { return description; }
void set_root_task(const Ref<BTTask> &p_value) {
root_task = p_value;
emit_changed();
}
void set_blackboard_plan(const Ref<BlackboardPlan> &p_plan);
Ref<BlackboardPlan> get_blackboard_plan() const { return blackboard_plan; }
void set_root_task(const Ref<BTTask> &p_value);
Ref<BTTask> get_root_task() const { return root_task; }
Ref<BehaviorTree> clone() const;
void copy_other(const Ref<BehaviorTree> &p_other);
Ref<BTTask> instantiate(Node *p_agent, const Ref<Blackboard> &p_blackboard) const;
BehaviorTree();
};
#endif // BEHAVIOR_TREE_H

View File

@ -63,11 +63,34 @@ void BTPlayer::_load_tree() {
#endif
}
void BTPlayer::_update_blackboard_plan() {
if (blackboard_plan.is_null()) {
blackboard_plan = Ref<BlackboardPlan>(memnew(BlackboardPlan));
}
blackboard_plan->set_base_plan(behavior_tree.is_valid() ? behavior_tree->get_blackboard_plan() : nullptr);
}
void BTPlayer::set_behavior_tree(const Ref<BehaviorTree> &p_tree) {
if (Engine::get_singleton()->is_editor_hint()) {
if (behavior_tree.is_valid() && behavior_tree->is_connected(LW_NAME(changed), callable_mp(this, &BTPlayer::_update_blackboard_plan))) {
behavior_tree->disconnect(LW_NAME(changed), callable_mp(this, &BTPlayer::_update_blackboard_plan));
}
if (p_tree.is_valid()) {
p_tree->connect(LW_NAME(changed), callable_mp(this, &BTPlayer::_update_blackboard_plan));
}
behavior_tree = p_tree;
if (Engine::get_singleton()->is_editor_hint() == false && get_owner()) {
_update_blackboard_plan();
} else {
behavior_tree = p_tree;
if (get_owner()) {
_load_tree();
}
}
}
void BTPlayer::set_blackboard_plan(const Ref<BlackboardPlan> &p_plan) {
blackboard_plan = p_plan;
_update_blackboard_plan();
}
void BTPlayer::set_update_mode(UpdateMode p_mode) {
@ -160,6 +183,12 @@ void BTPlayer::_notification(int p_notification) {
} break;
case NOTIFICATION_READY: {
if (!Engine::get_singleton()->is_editor_hint()) {
if (blackboard.is_null()) {
blackboard = Ref<Blackboard>(memnew(Blackboard));
}
if (blackboard_plan.is_valid()) {
blackboard_plan->populate_blackboard(blackboard, false);
}
if (behavior_tree.is_valid()) {
_load_tree();
}
@ -169,18 +198,27 @@ void BTPlayer::_notification(int p_notification) {
#endif
}
} break;
#ifdef DEBUG_ENABLED
case NOTIFICATION_ENTER_TREE: {
#ifdef DEBUG_ENABLED
if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) {
LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path());
}
#endif // DEBUG_ENABLED
} break;
case NOTIFICATION_EXIT_TREE: {
#ifdef DEBUG_ENABLED
if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) {
LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path());
}
} break;
#endif // DEBUG_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
if (behavior_tree.is_valid() && behavior_tree->is_connected(LW_NAME(changed), callable_mp(this, &BTPlayer::_update_blackboard_plan))) {
behavior_tree->disconnect(LW_NAME(changed), callable_mp(this, &BTPlayer::_update_blackboard_plan));
}
}
} break;
}
}
@ -196,8 +234,8 @@ void BTPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_prefetch_nodepath_vars", "p_value"), &BTPlayer::set_prefetch_nodepath_vars);
ClassDB::bind_method(D_METHOD("get_prefetch_nodepath_vars"), &BTPlayer::get_prefetch_nodepath_vars);
ClassDB::bind_method(D_METHOD("_set_blackboard_data", "p_blackboard"), &BTPlayer::_set_blackboard_data);
ClassDB::bind_method(D_METHOD("_get_blackboard_data"), &BTPlayer::_get_blackboard_data);
ClassDB::bind_method(D_METHOD("set_blackboard_plan", "p_plan"), &BTPlayer::set_blackboard_plan);
ClassDB::bind_method(D_METHOD("get_blackboard_plan"), &BTPlayer::get_blackboard_plan);
ClassDB::bind_method(D_METHOD("update", "p_delta"), &BTPlayer::update);
ClassDB::bind_method(D_METHOD("restart"), &BTPlayer::restart);
@ -207,7 +245,7 @@ void BTPlayer::_bind_methods() {
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");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard", PROPERTY_HINT_NONE, "Blackboard", 0), "set_blackboard", "get_blackboard");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_blackboard_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_blackboard_data", "_get_blackboard_data");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard_plan", PROPERTY_HINT_RESOURCE_TYPE, "BlackboardPlan", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_blackboard_plan", "get_blackboard_plan");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "prefetch_nodepath_vars"), "set_prefetch_nodepath_vars", "get_prefetch_nodepath_vars");
BIND_ENUM_CONSTANT(IDLE);

View File

@ -13,6 +13,7 @@
#define BT_PLAYER_H
#include "../blackboard/blackboard.h"
#include "../blackboard/blackboard_plan.h"
#include "behavior_tree.h"
#include "tasks/bt_task.h"
@ -36,6 +37,7 @@ public:
private:
Ref<BehaviorTree> behavior_tree;
Ref<BlackboardPlan> blackboard_plan;
UpdateMode update_mode = UpdateMode::PHYSICS;
bool active = true;
Ref<Blackboard> blackboard;
@ -45,19 +47,20 @@ private:
Ref<BTTask> tree_instance;
void _load_tree();
void _update_blackboard_plan();
protected:
static void _bind_methods();
void _set_blackboard_data(Dictionary p_value) { blackboard->set_data(p_value.duplicate()); }
Dictionary _get_blackboard_data() const { return blackboard->get_data(); }
void _notification(int p_notification);
public:
void set_behavior_tree(const Ref<BehaviorTree> &p_tree);
Ref<BehaviorTree> get_behavior_tree() const { return behavior_tree; };
void set_blackboard_plan(const Ref<BlackboardPlan> &p_plan);
Ref<BlackboardPlan> get_blackboard_plan() const { return blackboard_plan; }
void set_update_mode(UpdateMode p_mode);
UpdateMode get_update_mode() const { return update_mode; }

View File

@ -26,6 +26,25 @@
#include <godot_cpp/classes/engine_debugger.hpp>
#endif // LIMBOAI_GDEXTENSION
void BTState::set_behavior_tree(const Ref<BehaviorTree> &p_tree) {
if (Engine::get_singleton()->is_editor_hint()) {
if (behavior_tree.is_valid() && behavior_tree->is_connected(LW_NAME(changed), callable_mp(this, &BTState::_update_blackboard_plan))) {
behavior_tree->disconnect(LW_NAME(changed), callable_mp(this, &BTState::_update_blackboard_plan));
}
if (p_tree.is_valid()) {
p_tree->connect(LW_NAME(changed), callable_mp(this, &BTState::_update_blackboard_plan));
}
}
behavior_tree = p_tree;
}
void BTState::_update_blackboard_plan() {
if (get_blackboard_plan().is_null()) {
set_blackboard_plan(Ref<BlackboardPlan>(memnew(BlackboardPlan)));
}
get_blackboard_plan()->set_base_plan(behavior_tree.is_valid() ? behavior_tree->get_blackboard_plan() : nullptr);
}
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());
@ -53,22 +72,30 @@ void BTState::_update(double p_delta) {
}
}
#ifdef DEBUG_ENABLED
void BTState::_notification(int p_notification) {
switch (p_notification) {
#ifdef DEBUG_ENABLED
case NOTIFICATION_ENTER_TREE: {
if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) {
LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path());
}
} break;
#endif // DEBUG_ENABLED
case NOTIFICATION_EXIT_TREE: {
#ifdef DEBUG_ENABLED
if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) {
LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path());
}
#endif // DEBUG_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
if (behavior_tree.is_valid() && behavior_tree->is_connected(LW_NAME(changed), callable_mp(this, &BTState::_update_blackboard_plan))) {
behavior_tree->disconnect(LW_NAME(changed), callable_mp(this, &BTState::_update_blackboard_plan));
}
}
} break;
}
}
#endif // DEBUG_ENABLED
void BTState::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_behavior_tree", "p_value"), &BTState::set_behavior_tree);

View File

@ -26,6 +26,8 @@ private:
String success_event;
String failure_event;
void _update_blackboard_plan();
protected:
static void _bind_methods();
@ -34,7 +36,7 @@ protected:
virtual void _update(double p_delta) override;
public:
void set_behavior_tree(const Ref<BehaviorTree> &p_value) { behavior_tree = p_value; }
void set_behavior_tree(const Ref<BehaviorTree> &p_value);
Ref<BehaviorTree> get_behavior_tree() const { return behavior_tree; }
void set_success_event(String p_success_event) { success_event = p_success_event; }
@ -45,11 +47,8 @@ public:
BTState();
#ifdef DEBUG_ENABLED
protected:
void _notification(int p_notification);
#endif
};
#endif // BT_STATE_H

View File

@ -15,10 +15,14 @@ void BTNewScope::initialize(Node *p_agent, const Ref<Blackboard> &p_blackboard)
ERR_FAIL_COND(p_agent == nullptr);
ERR_FAIL_COND(p_blackboard == nullptr);
Ref<Blackboard> bb = memnew(Blackboard);
Ref<Blackboard> bb;
if (blackboard_plan.is_valid()) {
bb = blackboard_plan->create_blackboard();
} else {
bb = Ref<Blackboard>(memnew(Blackboard));
}
bb->set_data(blackboard_data.duplicate());
bb->set_parent_scope(p_blackboard);
bb->set_parent(p_blackboard);
BTDecorator::initialize(p_agent, bb);
}
@ -29,8 +33,8 @@ BT::Status BTNewScope::_tick(double p_delta) {
}
void BTNewScope::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_blackboard_data", "p_data"), &BTNewScope::_set_blackboard_data);
ClassDB::bind_method(D_METHOD("_get_blackboard_data"), &BTNewScope::_get_blackboard_data);
ClassDB::bind_method(D_METHOD("set_blackboard_plan", "p_plan"), &BTNewScope::set_blackboard_plan);
ClassDB::bind_method(D_METHOD("get_blackboard_plan"), &BTNewScope::get_blackboard_plan);
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_blackboard_data"), "_set_blackboard_data", "_get_blackboard_data");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard_plan", PROPERTY_HINT_RESOURCE_TYPE, "BlackboardPlan", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_blackboard_plan", "get_blackboard_plan");
}

View File

@ -14,18 +14,20 @@
#include "../bt_decorator.h"
#include "../../../blackboard/blackboard_plan.h"
class BTNewScope : public BTDecorator {
GDCLASS(BTNewScope, BTDecorator);
TASK_CATEGORY(Decorators);
private:
Dictionary blackboard_data;
Ref<BlackboardPlan> blackboard_plan;
protected:
static void _bind_methods();
void _set_blackboard_data(const Dictionary &p_value) { blackboard_data = p_value; }
Dictionary _get_blackboard_data() const { return blackboard_data; }
void set_blackboard_plan(const Ref<BlackboardPlan> &p_plan) { blackboard_plan = p_plan; }
Ref<BlackboardPlan> get_blackboard_plan() const { return blackboard_plan; }
virtual Status _tick(double p_delta) override;

View File

@ -60,6 +60,7 @@ def get_doc_classes():
"BehaviorTree",
"BehaviorTreeView",
"Blackboard",
"BlackboardPlan",
"BT",
"BTAction",
"BTAlwaysFail",

View File

@ -1,7 +1,14 @@
[gd_resource type="BehaviorTree" load_steps=10 format=3 uid="uid://cjkqi41oagagd"]
[gd_resource type="BehaviorTree" load_steps=11 format=3 uid="uid://cjkqi41oagagd"]
[ext_resource type="Script" path="res://demo/ai/tasks/arrive_pos.gd" id="1_rhs33"]
[sub_resource type="BlackboardPlan" id="BlackboardPlan_2hcqi"]
var/speed/name = "speed"
var/speed/type = 3
var/speed/value = 200.0
var/speed/hint = 1
var/speed/hint_string = "10,1000,10"
[sub_resource type="BTAction" id="BTAction_3xal7"]
script = ExtResource("1_rhs33")
target_position_var = "wp"
@ -36,4 +43,5 @@ duration = 3.0
children = [SubResource("BTCooldown_gen0l")]
[resource]
blackboard_plan = SubResource("BlackboardPlan_2hcqi")
root_task = SubResource("BTSelector_5dclr")

View File

@ -24,5 +24,3 @@ func _ready() -> void:
waypoints.reverse()
for wp in waypoints:
agent2.add_waypoint(wp.global_position)

View File

@ -1,9 +1,16 @@
[gd_scene load_steps=7 format=3 uid="uid://c26b8c8dndtop"]
[gd_scene load_steps=8 format=3 uid="uid://c26b8c8dndtop"]
[ext_resource type="Script" path="res://demo/examples/waypoints/patrolling_agent.gd" id="1_5wwhb"]
[ext_resource type="BehaviorTree" uid="uid://cjkqi41oagagd" path="res://demo/ai/trees/waypoints.tres" id="2_66y4v"]
[ext_resource type="Texture2D" uid="uid://d0mht3ntak7e5" path="res://demo/godot.png" id="3_64ge2"]
[sub_resource type="BlackboardPlan" id="BlackboardPlan_b86q8"]
var/speed/name = "speed"
var/speed/type = 3
var/speed/value = 300.0
var/speed/hint = 1
var/speed/hint_string = "10,1000,10"
[sub_resource type="Animation" id="Animation_5id00"]
length = 0.001
tracks/0/type = "value"
@ -46,9 +53,7 @@ script = ExtResource("1_5wwhb")
[node name="BTPlayer" type="BTPlayer" parent="."]
behavior_tree = ExtResource("2_66y4v")
_blackboard_data = {
"speed": 200.0
}
blackboard_plan = SubResource("BlackboardPlan_b86q8")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("3_64ge2")

View File

@ -0,0 +1,23 @@
:github_url: hide
.. DO NOT EDIT THIS FILE!!!
.. Generated automatically from Godot engine sources.
.. Generator: https://github.com/godotengine/godot/tree/4.2/doc/tools/make_rst.py.
.. XML source: https://github.com/godotengine/godot/tree/4.2/modules/limboai/doc_classes/BBFloat32Array.xml.
.. _class_BBFloat32Array:
BBFloat32Array
==============
**Inherits:** :ref:`BBParam<class_BBParam>`
PackedFloat32Array-type parameter for :ref:`BehaviorTree<class_BehaviorTree>` tasks. See :ref:`BBParam<class_BBParam>`.
.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)`
.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)`
.. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)`
.. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)`
.. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)`
.. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)`
.. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)`

View File

@ -0,0 +1,23 @@
:github_url: hide
.. DO NOT EDIT THIS FILE!!!
.. Generated automatically from Godot engine sources.
.. Generator: https://github.com/godotengine/godot/tree/4.2/doc/tools/make_rst.py.
.. XML source: https://github.com/godotengine/godot/tree/4.2/modules/limboai/doc_classes/BBFloat64Array.xml.
.. _class_BBFloat64Array:
BBFloat64Array
==============
**Inherits:** :ref:`BBParam<class_BBParam>`
PackedFloat64Array-type parameter for :ref:`BehaviorTree<class_BehaviorTree>` tasks. See :ref:`BBParam<class_BBParam>`.
.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)`
.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)`
.. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)`
.. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)`
.. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)`
.. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)`
.. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)`

View File

@ -0,0 +1,23 @@
:github_url: hide
.. DO NOT EDIT THIS FILE!!!
.. Generated automatically from Godot engine sources.
.. Generator: https://github.com/godotengine/godot/tree/4.2/doc/tools/make_rst.py.
.. XML source: https://github.com/godotengine/godot/tree/4.2/modules/limboai/doc_classes/BBInt32Array.xml.
.. _class_BBInt32Array:
BBInt32Array
============
**Inherits:** :ref:`BBParam<class_BBParam>`
PackedInt32Array-type parameter for :ref:`BehaviorTree<class_BehaviorTree>` tasks. See :ref:`BBParam<class_BBParam>`.
.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)`
.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)`
.. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)`
.. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)`
.. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)`
.. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)`
.. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)`

View File

@ -0,0 +1,23 @@
:github_url: hide
.. DO NOT EDIT THIS FILE!!!
.. Generated automatically from Godot engine sources.
.. Generator: https://github.com/godotengine/godot/tree/4.2/doc/tools/make_rst.py.
.. XML source: https://github.com/godotengine/godot/tree/4.2/modules/limboai/doc_classes/BBInt64Array.xml.
.. _class_BBInt64Array:
BBInt64Array
============
**Inherits:** :ref:`BBParam<class_BBParam>`
PackedInt64Array-type parameter for :ref:`BehaviorTree<class_BehaviorTree>` tasks. See :ref:`BBParam<class_BBParam>`.
.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)`
.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)`
.. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)`
.. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)`
.. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)`
.. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)`
.. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)`

View File

@ -12,7 +12,7 @@ BBParam
**Inherits:**
**Inherited By:** :ref:`BBAabb<class_BBAabb>`, :ref:`BBArray<class_BBArray>`, :ref:`BBBasis<class_BBBasis>`, :ref:`BBBool<class_BBBool>`, :ref:`BBByteArray<class_BBByteArray>`, :ref:`BBColor<class_BBColor>`, :ref:`BBColorArray<class_BBColorArray>`, :ref:`BBDictionary<class_BBDictionary>`, :ref:`BBFloat<class_BBFloat>`, :ref:`BBFloatArray<class_BBFloatArray>`, :ref:`BBInt<class_BBInt>`, :ref:`BBIntArray<class_BBIntArray>`, :ref:`BBNode<class_BBNode>`, :ref:`BBPlane<class_BBPlane>`, :ref:`BBQuaternion<class_BBQuaternion>`, :ref:`BBRect2<class_BBRect2>`, :ref:`BBRect2i<class_BBRect2i>`, :ref:`BBString<class_BBString>`, :ref:`BBStringArray<class_BBStringArray>`, :ref:`BBStringName<class_BBStringName>`, :ref:`BBTransform2D<class_BBTransform2D>`, :ref:`BBTransform3D<class_BBTransform3D>`, :ref:`BBVariant<class_BBVariant>`, :ref:`BBVector2<class_BBVector2>`, :ref:`BBVector2Array<class_BBVector2Array>`, :ref:`BBVector2i<class_BBVector2i>`, :ref:`BBVector3<class_BBVector3>`, :ref:`BBVector3Array<class_BBVector3Array>`, :ref:`BBVector3i<class_BBVector3i>`, :ref:`BBVector4<class_BBVector4>`, :ref:`BBVector4i<class_BBVector4i>`
**Inherited By:** :ref:`BBAabb<class_BBAabb>`, :ref:`BBArray<class_BBArray>`, :ref:`BBBasis<class_BBBasis>`, :ref:`BBBool<class_BBBool>`, :ref:`BBByteArray<class_BBByteArray>`, :ref:`BBColor<class_BBColor>`, :ref:`BBColorArray<class_BBColorArray>`, :ref:`BBDictionary<class_BBDictionary>`, :ref:`BBFloat<class_BBFloat>`, :ref:`BBFloat32Array<class_BBFloat32Array>`, :ref:`BBFloat64Array<class_BBFloat64Array>`, :ref:`BBInt<class_BBInt>`, :ref:`BBInt32Array<class_BBInt32Array>`, :ref:`BBInt64Array<class_BBInt64Array>`, :ref:`BBNode<class_BBNode>`, :ref:`BBPlane<class_BBPlane>`, :ref:`BBProjection<class_BBProjection>`, :ref:`BBQuaternion<class_BBQuaternion>`, :ref:`BBRect2<class_BBRect2>`, :ref:`BBRect2i<class_BBRect2i>`, :ref:`BBString<class_BBString>`, :ref:`BBStringArray<class_BBStringArray>`, :ref:`BBStringName<class_BBStringName>`, :ref:`BBTransform2D<class_BBTransform2D>`, :ref:`BBTransform3D<class_BBTransform3D>`, :ref:`BBVariant<class_BBVariant>`, :ref:`BBVector2<class_BBVector2>`, :ref:`BBVector2Array<class_BBVector2Array>`, :ref:`BBVector2i<class_BBVector2i>`, :ref:`BBVector3<class_BBVector3>`, :ref:`BBVector3Array<class_BBVector3Array>`, :ref:`BBVector3i<class_BBVector3i>`, :ref:`BBVector4<class_BBVector4>`, :ref:`BBVector4i<class_BBVector4i>`
A base class for LimboAI typed parameters.

View File

@ -0,0 +1,23 @@
:github_url: hide
.. DO NOT EDIT THIS FILE!!!
.. Generated automatically from Godot engine sources.
.. Generator: https://github.com/godotengine/godot/tree/4.2/doc/tools/make_rst.py.
.. XML source: https://github.com/godotengine/godot/tree/4.2/modules/limboai/doc_classes/BBProjection.xml.
.. _class_BBProjection:
BBProjection
============
**Inherits:** :ref:`BBParam<class_BBParam>`
Projection-type parameter for :ref:`BehaviorTree<class_BehaviorTree>` tasks. See :ref:`BBParam<class_BBParam>`.
.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)`
.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)`
.. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)`
.. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)`
.. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)`
.. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)`
.. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)`

View File

@ -41,9 +41,11 @@ Properties
.. table::
:widths: auto
+--------+-------------------------------------------------------------+--------+
+---------------------------------------------+---------------------------------------------------------------------+--------+
| :ref:`BlackboardPlan<class_BlackboardPlan>` | :ref:`blackboard_plan<class_BehaviorTree_property_blackboard_plan>` | |
+---------------------------------------------+---------------------------------------------------------------------+--------+
| String | :ref:`description<class_BehaviorTree_property_description>` | ``""`` |
+--------+-------------------------------------------------------------+--------+
+---------------------------------------------+---------------------------------------------------------------------+--------+
.. rst-class:: classref-reftable-group
@ -74,6 +76,23 @@ Methods
Property Descriptions
---------------------
.. _class_BehaviorTree_property_blackboard_plan:
.. rst-class:: classref-property
:ref:`BlackboardPlan<class_BlackboardPlan>` **blackboard_plan**
.. rst-class:: classref-property-setget
- void **set_blackboard_plan** **(** :ref:`BlackboardPlan<class_BlackboardPlan>` value **)**
- :ref:`BlackboardPlan<class_BlackboardPlan>` **get_blackboard_plan** **(** **)**
Stores and manages variables that will be used in constructing new :ref:`Blackboard<class_Blackboard>` instances.
.. rst-class:: classref-item-separator
----
.. _class_BehaviorTree_property_description:
.. rst-class:: classref-property

View File

@ -34,23 +34,19 @@ Methods
:widths: auto
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`erase_var<class_Blackboard_method_erase_var>` **(** Variant p_key **)** |
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+
| Dictionary | :ref:`get_data<class_Blackboard_method_get_data>` **(** **)** |const| |
| void | :ref:`erase_var<class_Blackboard_method_erase_var>` **(** String p_name **)** |
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+
| :ref:`Blackboard<class_Blackboard>` | :ref:`get_parent_scope<class_Blackboard_method_get_parent_scope>` **(** **)** |const| |
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+
| Variant | :ref:`get_var<class_Blackboard_method_get_var>` **(** Variant p_key, Variant p_default=null **)** |const| |
| Variant | :ref:`get_var<class_Blackboard_method_get_var>` **(** String p_name, Variant p_default=null **)** |const| |
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+
| bool | :ref:`has_var<class_Blackboard_method_has_var>` **(** Variant p_key **)** |const| |
| bool | :ref:`has_var<class_Blackboard_method_has_var>` **(** String p_name **)** |const| |
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`prefetch_nodepath_vars<class_Blackboard_method_prefetch_nodepath_vars>` **(** Node p_node **)** |
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`set_data<class_Blackboard_method_set_data>` **(** Dictionary p_data **)** |
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`set_parent_scope<class_Blackboard_method_set_parent_scope>` **(** :ref:`Blackboard<class_Blackboard>` p_blackboard **)** |
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+
| void | :ref:`set_var<class_Blackboard_method_set_var>` **(** Variant p_key, Variant p_value **)** |
| void | :ref:`set_var<class_Blackboard_method_set_var>` **(** String p_name, Variant p_value **)** |
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+
| :ref:`Blackboard<class_Blackboard>` | :ref:`top<class_Blackboard_method_top>` **(** **)** |const| |
+-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+
@ -68,7 +64,7 @@ Method Descriptions
.. rst-class:: classref-method
void **erase_var** **(** Variant p_key **)**
void **erase_var** **(** String p_name **)**
Removes a variable by its name.
@ -76,18 +72,6 @@ Removes a variable by its name.
----
.. _class_Blackboard_method_get_data:
.. rst-class:: classref-method
Dictionary **get_data** **(** **)** |const|
Returns Blackboard data as a ``Dictionary``.
.. rst-class:: classref-item-separator
----
.. _class_Blackboard_method_get_parent_scope:
.. rst-class:: classref-method
@ -104,7 +88,7 @@ Returns a Blackboard that serves as the parent scope for this instance.
.. rst-class:: classref-method
Variant **get_var** **(** Variant p_key, Variant p_default=null **)** |const|
Variant **get_var** **(** String p_name, Variant p_default=null **)** |const|
Returns variable value.
@ -116,7 +100,7 @@ Returns variable value.
.. rst-class:: classref-method
bool **has_var** **(** Variant p_key **)** |const|
bool **has_var** **(** String p_name **)** |const|
Returns ``true`` if the Blackboard contains the ``p_key`` variable, including the parent scopes.
@ -136,18 +120,6 @@ If ``true``, any ``NodePath`` variables in the **Blackboard** are replaced with
----
.. _class_Blackboard_method_set_data:
.. rst-class:: classref-method
void **set_data** **(** Dictionary p_data **)**
Overwrites Blackboard data, replacing any previously stored variables within current scope. Use with caution.
.. rst-class:: classref-item-separator
----
.. _class_Blackboard_method_set_parent_scope:
.. rst-class:: classref-method
@ -164,7 +136,7 @@ Assigns the parent scope. If a value isn't in the current Blackboard scope, it w
.. rst-class:: classref-method
void **set_var** **(** Variant p_key, Variant p_value **)**
void **set_var** **(** String p_name, Variant p_value **)**
Assigns a value to a Blackboard variable.

View File

@ -0,0 +1,23 @@
:github_url: hide
.. DO NOT EDIT THIS FILE!!!
.. Generated automatically from Godot engine sources.
.. Generator: https://github.com/godotengine/godot/tree/4.2/doc/tools/make_rst.py.
.. XML source: https://github.com/godotengine/godot/tree/4.2/modules/limboai/doc_classes/BlackboardPlan.xml.
.. _class_BlackboardPlan:
BlackboardPlan
==============
**Inherits:**
Stores and manages variables that will be used in constructing new :ref:`Blackboard<class_Blackboard>` instances.
.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)`
.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)`
.. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)`
.. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)`
.. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)`
.. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)`
.. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)`

View File

@ -33,9 +33,9 @@ Properties
.. table::
:widths: auto
+------------+---------------------------------------------------------------------+--------+
| Dictionary | :ref:`_blackboard_data<class_BTNewScope_property__blackboard_data>` | ``{}`` |
+------------+---------------------------------------------------------------------+--------+
+---------------------------------------------+-------------------------------------------------------------------+
| :ref:`BlackboardPlan<class_BlackboardPlan>` | :ref:`blackboard_plan<class_BTNewScope_property_blackboard_plan>` |
+---------------------------------------------+-------------------------------------------------------------------+
.. rst-class:: classref-section-separator
@ -46,13 +46,18 @@ Properties
Property Descriptions
---------------------
.. _class_BTNewScope_property__blackboard_data:
.. _class_BTNewScope_property_blackboard_plan:
.. rst-class:: classref-property
Dictionary **_blackboard_data** = ``{}``
:ref:`BlackboardPlan<class_BlackboardPlan>` **blackboard_plan**
Data that is used to populate a new scope of the :ref:`Blackboard<class_Blackboard>`.
.. rst-class:: classref-property-setget
- void **set_blackboard_plan** **(** :ref:`BlackboardPlan<class_BlackboardPlan>` value **)**
- :ref:`BlackboardPlan<class_BlackboardPlan>` **get_blackboard_plan** **(** **)**
Stores and manages variables that will be used in constructing new :ref:`Blackboard<class_Blackboard>` instances.
.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)`
.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)`

View File

@ -38,6 +38,8 @@ Properties
+---------------------------------------------+-------------------------------------------------------------------------------+-----------+
| :ref:`Blackboard<class_Blackboard>` | :ref:`blackboard<class_BTPlayer_property_blackboard>` | |
+---------------------------------------------+-------------------------------------------------------------------------------+-----------+
| :ref:`BlackboardPlan<class_BlackboardPlan>` | :ref:`blackboard_plan<class_BTPlayer_property_blackboard_plan>` | |
+---------------------------------------------+-------------------------------------------------------------------------------+-----------+
| bool | :ref:`monitor_performance<class_BTPlayer_property_monitor_performance>` | ``false`` |
+---------------------------------------------+-------------------------------------------------------------------------------+-----------+
| bool | :ref:`prefetch_nodepath_vars<class_BTPlayer_property_prefetch_nodepath_vars>` | ``true`` |
@ -193,6 +195,23 @@ Holds data shared by the behavior tree tasks. See :ref:`Blackboard<class_Blackbo
----
.. _class_BTPlayer_property_blackboard_plan:
.. rst-class:: classref-property
:ref:`BlackboardPlan<class_BlackboardPlan>` **blackboard_plan**
.. rst-class:: classref-property-setget
- void **set_blackboard_plan** **(** :ref:`BlackboardPlan<class_BlackboardPlan>` value **)**
- :ref:`BlackboardPlan<class_BlackboardPlan>` **get_blackboard_plan** **(** **)**
Stores and manages variables that will be used in constructing new :ref:`Blackboard<class_Blackboard>` instances.
.. rst-class:: classref-item-separator
----
.. _class_BTPlayer_property_monitor_performance:
.. rst-class:: classref-property

View File

@ -35,13 +35,15 @@ Properties
.. table::
:widths: auto
+-------------------------------------+-----------------------------------------------------------------+
+---------------------------------------------+-------------------------------------------------------------------+
| String | :ref:`EVENT_FINISHED<class_LimboState_property_EVENT_FINISHED>` |
+-------------------------------------+-----------------------------------------------------------------+
+---------------------------------------------+-------------------------------------------------------------------+
| Node | :ref:`agent<class_LimboState_property_agent>` |
+-------------------------------------+-----------------------------------------------------------------+
+---------------------------------------------+-------------------------------------------------------------------+
| :ref:`Blackboard<class_Blackboard>` | :ref:`blackboard<class_LimboState_property_blackboard>` |
+-------------------------------------+-----------------------------------------------------------------+
+---------------------------------------------+-------------------------------------------------------------------+
| :ref:`BlackboardPlan<class_BlackboardPlan>` | :ref:`blackboard_plan<class_LimboState_property_blackboard_plan>` |
+---------------------------------------------+-------------------------------------------------------------------+
.. rst-class:: classref-reftable-group
@ -188,6 +190,23 @@ An agent associated with the state, assigned during initialization.
A key/value data store shared by states within the state machine to which this state belongs.
.. rst-class:: classref-item-separator
----
.. _class_LimboState_property_blackboard_plan:
.. rst-class:: classref-property
:ref:`BlackboardPlan<class_BlackboardPlan>` **blackboard_plan**
.. rst-class:: classref-property-setget
- void **set_blackboard_plan** **(** :ref:`BlackboardPlan<class_BlackboardPlan>` value **)**
- :ref:`BlackboardPlan<class_BlackboardPlan>` **get_blackboard_plan** **(** **)**
Stores and manages variables that will be used in constructing new :ref:`Blackboard<class_Blackboard>` instances.
.. rst-class:: classref-section-separator
----

View File

@ -10,8 +10,8 @@
<tutorials>
</tutorials>
<members>
<member name="_blackboard_data" type="Dictionary" setter="_set_blackboard_data" getter="_get_blackboard_data" default="{}">
Data that is used to populate a new scope of the [Blackboard].
<member name="blackboard_plan" type="BlackboardPlan" setter="set_blackboard_plan" getter="get_blackboard_plan">
Stores and manages variables that will be used in constructing new [Blackboard] instances.
</member>
</members>
</class>

View File

@ -40,6 +40,9 @@
<member name="blackboard" type="Blackboard" setter="set_blackboard" getter="get_blackboard">
Holds data shared by the behavior tree tasks. See [Blackboard].
</member>
<member name="blackboard_plan" type="BlackboardPlan" setter="set_blackboard_plan" getter="get_blackboard_plan">
Stores and manages variables that will be used in constructing new [Blackboard] instances.
</member>
<member name="monitor_performance" type="bool" setter="_set_monitor_performance" getter="_get_monitor_performance" default="false">
If [code]true[/code], adds a performance monitor to "Debugger-&gt;Monitors" for each instance of this [BTPlayer] node.
</member>

View File

@ -51,6 +51,9 @@
</method>
</methods>
<members>
<member name="blackboard_plan" type="BlackboardPlan" setter="set_blackboard_plan" getter="get_blackboard_plan">
Stores and manages variables that will be used in constructing new [Blackboard] instances.
</member>
<member name="description" type="String" setter="set_description" getter="get_description" default="&quot;&quot;">
User-provided description of the BehaviorTree.
</member>

View File

@ -13,17 +13,11 @@
<methods>
<method name="erase_var">
<return type="void" />
<param index="0" name="p_key" type="Variant" />
<param index="0" name="p_name" type="String" />
<description>
Removes a variable by its name.
</description>
</method>
<method name="get_data" qualifiers="const">
<return type="Dictionary" />
<description>
Returns Blackboard data as a [Dictionary].
</description>
</method>
<method name="get_parent_scope" qualifiers="const">
<return type="Blackboard" />
<description>
@ -32,7 +26,7 @@
</method>
<method name="get_var" qualifiers="const">
<return type="Variant" />
<param index="0" name="p_key" type="Variant" />
<param index="0" name="p_name" type="String" />
<param index="1" name="p_default" type="Variant" default="null" />
<description>
Returns variable value.
@ -40,7 +34,7 @@
</method>
<method name="has_var" qualifiers="const">
<return type="bool" />
<param index="0" name="p_key" type="Variant" />
<param index="0" name="p_name" type="String" />
<description>
Returns [code]true[/code] if the Blackboard contains the [param p_key] variable, including the parent scopes.
</description>
@ -52,13 +46,6 @@
If [code]true[/code], any [NodePath] variables in the [Blackboard] are replaced with [Node] references when the tree is instantiated. References are retrieved by calling [method Node.get_node] on the agent instance.
</description>
</method>
<method name="set_data">
<return type="void" />
<param index="0" name="p_data" type="Dictionary" />
<description>
Overwrites Blackboard data, replacing any previously stored variables within current scope. Use with caution.
</description>
</method>
<method name="set_parent_scope">
<return type="void" />
<param index="0" name="p_blackboard" type="Blackboard" />
@ -68,7 +55,7 @@
</method>
<method name="set_var">
<return type="void" />
<param index="0" name="p_key" type="Variant" />
<param index="0" name="p_name" type="String" />
<param index="1" name="p_value" type="Variant" />
<description>
Assigns a value to a Blackboard variable.

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="BlackboardPlan" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Stores and manages variables that will be used in constructing new [Blackboard] instances.
</brief_description>
<description>
</description>
<tutorials>
</tutorials>
</class>

View File

@ -117,6 +117,9 @@
<member name="blackboard" type="Blackboard" setter="" getter="get_blackboard">
A key/value data store shared by states within the state machine to which this state belongs.
</member>
<member name="blackboard_plan" type="BlackboardPlan" setter="set_blackboard_plan" getter="get_blackboard_plan">
Stores and manages variables that will be used in constructing new [Blackboard] instances.
</member>
</members>
<signals>
<signal name="entered">

View File

@ -0,0 +1,449 @@
/**
* blackboard_plan_editor.cpp
* =============================================================================
* Copyright 2021-2024 Serhii Snitsaruk
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* =============================================================================
*/
#include "blackboard_plan_editor.h"
#include "../util/limbo_compat.h"
#include "../util/limbo_string_names.h"
#include "../util/limbo_utility.h"
#ifdef LIMBOAI_MODULE
#include "editor/editor_interface.h"
#include "editor/editor_scale.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/panel_container.h"
#include "scene/resources/style_box_flat.h"
#endif // LIMBOAI_MODULE
#ifdef LIMBOAI_GDEXTENSION
#include <godot_cpp/classes/button.hpp>
#include <godot_cpp/classes/editor_interface.hpp>
#include <godot_cpp/classes/h_box_container.hpp>
#include <godot_cpp/classes/input.hpp>
#include <godot_cpp/classes/input_event_mouse_motion.hpp>
#include <godot_cpp/classes/label.hpp>
#include <godot_cpp/classes/line_edit.hpp>
#include <godot_cpp/classes/margin_container.hpp>
#include <godot_cpp/classes/theme.hpp>
using namespace godot;
#endif // LIMBOAI_GDEXTENSION
void BlackboardPlanEditor::_add_var() {
ERR_FAIL_NULL(plan);
int suffix = 1;
String name = "var" + itos(suffix);
while (plan->has_var(name)) {
suffix += 1;
name = "var" + itos(suffix);
}
BBVariable var(Variant::Type::FLOAT);
plan->add_var(name, var);
_refresh();
}
void BlackboardPlanEditor::_trash_var(int p_index) {
ERR_FAIL_NULL(plan);
String var_name = plan->get_var_by_index(p_index).first;
plan->remove_var(var_name);
_refresh();
}
void BlackboardPlanEditor::_rename_var(const String &p_new_name, int p_index) {
ERR_FAIL_NULL(plan);
plan->rename_var(plan->get_var_by_index(p_index).first, p_new_name);
}
void BlackboardPlanEditor::_change_var_type(Variant::Type p_new_type, int p_index) {
ERR_FAIL_NULL(plan);
plan->get_var_by_index(p_index).second.set_type(p_new_type);
plan->notify_property_list_changed();
_refresh();
}
void BlackboardPlanEditor::_change_var_hint(PropertyHint p_new_hint, int p_index) {
ERR_FAIL_NULL(plan);
plan->get_var_by_index(p_index).second.set_hint(p_new_hint);
plan->notify_property_list_changed();
_refresh();
}
void BlackboardPlanEditor::_change_var_hint_string(const String &p_new_hint_string, int p_index) {
ERR_FAIL_NULL(plan);
plan->get_var_by_index(p_index).second.set_hint_string(p_new_hint_string);
plan->notify_property_list_changed();
}
void BlackboardPlanEditor::edit_plan(const Ref<BlackboardPlan> &p_plan) {
plan = p_plan;
_refresh();
}
void BlackboardPlanEditor::_show_button_popup(Button *p_button, PopupMenu *p_popup, int p_index) {
ERR_FAIL_NULL(p_button);
ERR_FAIL_NULL(p_popup);
Transform2D xform = p_button->get_screen_transform();
Rect2 rect(xform.get_origin(), xform.get_scale() * p_button->get_size());
rect.position.y += rect.size.height;
rect.size.height = 0;
p_popup->set_size(rect.size);
p_popup->set_position(rect.position);
last_index = p_index;
p_popup->popup();
}
void BlackboardPlanEditor::_type_chosen(int id) {
_change_var_type(Variant::Type(id), last_index);
}
void BlackboardPlanEditor::_hint_chosen(int id) {
_change_var_hint(PropertyHint(id), last_index);
}
void BlackboardPlanEditor::_drag_button_down(Control *p_row) {
drag_index = p_row->get_index();
drag_start = drag_index;
drag_mouse_y_delta = 0.0;
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
}
void BlackboardPlanEditor::_drag_button_up() {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
plan->move_var(drag_start, drag_index);
drag_index = -1;
drag_start = -1;
_refresh();
}
void BlackboardPlanEditor::_drag_button_gui_input(const Ref<InputEvent> &p_event) {
if (drag_index < 0) {
return;
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_null()) {
return;
}
drag_mouse_y_delta += mm->get_relative().y;
if ((drag_index == 0 && drag_mouse_y_delta < 0.0) || (drag_index == (plan->get_var_count() - 1) && drag_mouse_y_delta > 0.0)) {
drag_mouse_y_delta = 0.0;
return;
}
float required_distance = 30.0f * EDSCALE;
if (ABS(drag_mouse_y_delta) > required_distance) {
int drag_dir = drag_mouse_y_delta > 0.0f ? 1 : -1;
drag_mouse_y_delta -= required_distance * drag_dir;
Control *row = Object::cast_to<Control>(rows_vbox->get_child(drag_index));
Control *other_row = Object::cast_to<Control>(rows_vbox->get_child(drag_index + drag_dir));
ERR_FAIL_NULL(row);
ERR_FAIL_NULL(other_row);
rows_vbox->move_child(row, drag_index + drag_dir);
ADD_STYLEBOX_OVERRIDE(row, LW_NAME(panel), row->get_index() % 2 ? theme_cache.odd_style : theme_cache.even_style);
ADD_STYLEBOX_OVERRIDE(other_row, LW_NAME(panel), other_row->get_index() % 2 ? theme_cache.odd_style : theme_cache.even_style);
drag_index += drag_dir;
}
}
void BlackboardPlanEditor::_visibility_changed() {
if (!is_visible() && plan.is_valid()) {
plan->notify_property_list_changed();
}
}
void BlackboardPlanEditor::_refresh() {
for (int i = 0; i < rows_vbox->get_child_count(); i++) {
Control *child = Object::cast_to<Control>(rows_vbox->get_child(i));
ERR_FAIL_NULL(child);
child->hide();
child->queue_free();
}
// TODO: Name validation
PackedStringArray names = plan->list_vars();
int idx = 0;
for (const String &var_name : names) {
BBVariable var = plan->get_var(var_name);
PanelContainer *row_panel = memnew(PanelContainer);
rows_vbox->add_child(row_panel);
ADD_STYLEBOX_OVERRIDE(row_panel, LW_NAME(panel), idx % 2 ? theme_cache.odd_style : theme_cache.even_style);
row_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL);
HBoxContainer *props_hbox = memnew(HBoxContainer);
row_panel->add_child(props_hbox);
props_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);
Button *drag_button = memnew(Button);
props_hbox->add_child(drag_button);
drag_button->set_custom_minimum_size(Size2(28.0, 28.0) * EDSCALE);
BUTTON_SET_ICON(drag_button, theme_cache.grab_icon);
drag_button->connect(LW_NAME(gui_input), callable_mp(this, &BlackboardPlanEditor::_drag_button_gui_input));
drag_button->connect(LW_NAME(button_down), callable_mp(this, &BlackboardPlanEditor::_drag_button_down).bind(row_panel));
drag_button->connect(LW_NAME(button_up), callable_mp(this, &BlackboardPlanEditor::_drag_button_up));
LineEdit *name_edit = memnew(LineEdit);
props_hbox->add_child(name_edit);
name_edit->set_text(var_name);
name_edit->set_placeholder(TTR("Variable name"));
name_edit->set_flat(true);
name_edit->set_custom_minimum_size(Size2(300.0, 0.0) * EDSCALE);
name_edit->connect(LW_NAME(text_changed), callable_mp(this, &BlackboardPlanEditor::_rename_var).bind(idx));
name_edit->connect(LW_NAME(text_submitted), callable_mp(this, &BlackboardPlanEditor::_refresh).unbind(1));
Button *type_choice = memnew(Button);
props_hbox->add_child(type_choice);
type_choice->set_custom_minimum_size(Size2(170, 0.0) * EDSCALE);
type_choice->set_text(Variant::get_type_name(var.get_type()));
type_choice->set_tooltip_text(Variant::get_type_name(var.get_type()));
BUTTON_SET_ICON(type_choice, get_theme_icon(Variant::get_type_name(var.get_type()), LW_NAME(EditorIcons)));
type_choice->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
type_choice->set_flat(true);
type_choice->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
type_choice->connect(LW_NAME(pressed), callable_mp(this, &BlackboardPlanEditor::_show_button_popup).bind(type_choice, type_menu, idx));
Button *hint_choice = memnew(Button);
props_hbox->add_child(hint_choice);
hint_choice->set_custom_minimum_size(Size2(150.0, 0.0) * EDSCALE);
hint_choice->set_text(LimboUtility::get_singleton()->get_property_hint_text(var.get_hint()));
hint_choice->set_tooltip_text(LimboUtility::get_singleton()->get_property_hint_text(var.get_hint()));
hint_choice->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
hint_choice->set_flat(true);
hint_choice->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
hint_choice->connect(LW_NAME(pressed), callable_mp(this, &BlackboardPlanEditor::_show_button_popup).bind(hint_choice, hint_menu, idx));
LineEdit *hint_string_edit = memnew(LineEdit);
props_hbox->add_child(hint_string_edit);
hint_string_edit->set_custom_minimum_size(Size2(300.0, 0.0) * EDSCALE);
hint_string_edit->set_text(var.get_hint_string());
hint_string_edit->set_placeholder(TTR("Hint string"));
hint_string_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
hint_string_edit->set_flat(true);
hint_string_edit->connect(LW_NAME(text_changed), callable_mp(this, &BlackboardPlanEditor::_change_var_hint_string).bind(idx));
hint_string_edit->connect(LW_NAME(text_submitted), callable_mp(this, &BlackboardPlanEditor::_refresh).unbind(1));
Button *trash_button = memnew(Button);
props_hbox->add_child(trash_button);
trash_button->set_custom_minimum_size(Size2(24.0, 0.0) * EDSCALE);
BUTTON_SET_ICON(trash_button, theme_cache.trash_icon);
trash_button->connect(LW_NAME(pressed), callable_mp(this, &BlackboardPlanEditor::_trash_var).bind(idx));
idx += 1;
}
}
void BlackboardPlanEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
theme_cache.trash_icon = get_theme_icon(LW_NAME(Remove), LW_NAME(EditorIcons));
theme_cache.grab_icon = get_theme_icon(LW_NAME(TripleBar), LW_NAME(EditorIcons));
BUTTON_SET_ICON(add_var_tool, get_theme_icon(LW_NAME(Add), LW_NAME(EditorIcons)));
type_menu->clear();
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
if (i == Variant::RID || i == Variant::CALLABLE || i == Variant::SIGNAL) {
continue;
}
String type = Variant::get_type_name(Variant::Type(i));
type_menu->add_icon_item(get_theme_icon(type, LW_NAME(EditorIcons)), type, i);
}
ADD_STYLEBOX_OVERRIDE(scroll_container, LW_NAME(panel), get_theme_stylebox(LW_NAME(panel), LW_NAME(Tree)));
Color bg_color = get_theme_color(LW_NAME(dark_color_2), LW_NAME(Editor));
theme_cache.odd_style->set_bg_color(bg_color.darkened(-0.05));
theme_cache.even_style->set_bg_color(bg_color.darkened(0.05));
theme_cache.header_style->set_bg_color(bg_color.darkened(-0.2));
ADD_STYLEBOX_OVERRIDE(header_row, LW_NAME(panel), theme_cache.header_style);
} break;
case NOTIFICATION_READY: {
add_var_tool->connect(LW_NAME(pressed), callable_mp(this, &BlackboardPlanEditor::_add_var));
connect(LW_NAME(visibility_changed), callable_mp(this, &BlackboardPlanEditor::_visibility_changed));
type_menu->connect(LW_NAME(id_pressed), callable_mp(this, &BlackboardPlanEditor::_type_chosen));
hint_menu->connect(LW_NAME(id_pressed), callable_mp(this, &BlackboardPlanEditor::_hint_chosen));
} break;
}
}
BlackboardPlanEditor::BlackboardPlanEditor() {
set_title(TTR("Manage Blackboard Plan"));
VBoxContainer *vbox = memnew(VBoxContainer);
vbox->add_theme_constant_override(LW_NAME(separation), 8 * EDSCALE);
add_child(vbox);
HBoxContainer *toolbar = memnew(HBoxContainer);
vbox->add_child(toolbar);
add_var_tool = memnew(Button);
toolbar->add_child(add_var_tool);
add_var_tool->set_focus_mode(Control::FOCUS_NONE);
add_var_tool->set_text(TTR("Add variable"));
{
// * Header
header_row = memnew(PanelContainer);
vbox->add_child(header_row);
header_row->set_h_size_flags(Control::SIZE_EXPAND_FILL);
HBoxContainer *labels_hbox = memnew(HBoxContainer);
header_row->add_child(labels_hbox);
labels_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);
Control *offset = memnew(Control);
labels_hbox->add_child(offset);
offset->set_custom_minimum_size(Size2(2.0, 0.0) * EDSCALE);
Label *drag_header = memnew(Label);
labels_hbox->add_child(drag_header);
drag_header->set_custom_minimum_size(Size2(28.0, 28.0) * EDSCALE);
Label *name_header = memnew(Label);
labels_hbox->add_child(name_header);
name_header->set_text(TTR("Name"));
name_header->set_custom_minimum_size(Size2(300.0, 0.0) * EDSCALE);
name_header->set_theme_type_variation(LW_NAME(HeaderSmall));
Label *type_header = memnew(Label);
labels_hbox->add_child(type_header);
type_header->set_text(TTR("Type"));
type_header->set_custom_minimum_size(Size2(170, 0.0) * EDSCALE);
type_header->set_theme_type_variation(LW_NAME(HeaderSmall));
Label *hint_header = memnew(Label);
labels_hbox->add_child(hint_header);
hint_header->set_text(TTR("Hint"));
hint_header->set_custom_minimum_size(Size2(150.0, 0.0) * EDSCALE);
hint_header->set_theme_type_variation(LW_NAME(HeaderSmall));
Label *hint_string_header = memnew(Label);
labels_hbox->add_child(hint_string_header);
hint_string_header->set_text(TTR("Hint string"));
hint_string_header->set_custom_minimum_size(Size2(300.0, 0.0) * EDSCALE);
hint_string_header->set_h_size_flags(Control::SIZE_EXPAND_FILL);
hint_string_header->set_theme_type_variation(LW_NAME(HeaderSmall));
}
scroll_container = memnew(ScrollContainer);
vbox->add_child(scroll_container);
scroll_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
scroll_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
scroll_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
scroll_container->set_custom_minimum_size(Size2(0.0, 600.0) * EDSCALE);
rows_vbox = memnew(VBoxContainer);
scroll_container->add_child(rows_vbox);
rows_vbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);
rows_vbox->add_theme_constant_override(LW_NAME(separation), 0);
type_menu = memnew(PopupMenu);
add_child(type_menu);
hint_menu = memnew(PopupMenu);
add_child(hint_menu);
for (int i = 0; i < PropertyHint::PROPERTY_HINT_MAX; i++) {
hint_menu->add_item(LimboUtility::get_singleton()->get_property_hint_text(PropertyHint(i)), i);
}
theme_cache.odd_style.instantiate();
theme_cache.even_style.instantiate();
theme_cache.header_style.instantiate();
}
// ***** EditorInspectorPluginBBPlan *****
void EditorInspectorPluginBBPlan::_edit_plan(const Ref<BlackboardPlan> &p_plan) {
ERR_FAIL_NULL(p_plan);
plan_editor->edit_plan(p_plan);
plan_editor->popup_centered();
}
void EditorInspectorPluginBBPlan::_open_base_plan(const Ref<BlackboardPlan> &p_plan) {
ERR_FAIL_NULL(p_plan);
ERR_FAIL_NULL(p_plan->get_base_plan());
EditorInterface::get_singleton()->call_deferred("edit_resource", p_plan->get_base_plan());
}
#ifdef LIMBOAI_MODULE
bool EditorInspectorPluginBBPlan::can_handle(Object *p_object) {
#elif LIMBOAI_GDEXTENSION
bool EditorInspectorPluginBBPlan::_can_handle(Object *p_object) const {
#endif
Ref<BlackboardPlan> plan = Object::cast_to<BlackboardPlan>(p_object);
if (plan.is_valid()) {
plan->sync_with_base_plan();
}
return plan.is_valid();
}
#ifdef LIMBOAI_MODULE
void EditorInspectorPluginBBPlan::parse_begin(Object *p_object) {
#elif LIMBOAI_GDEXTENSION
void EditorInspectorPluginBBPlan::_parse_begin(Object *p_object) {
#endif
Ref<BlackboardPlan> plan = Object::cast_to<BlackboardPlan>(p_object);
ERR_FAIL_NULL(plan);
PanelContainer *panel = memnew(PanelContainer);
ADD_STYLEBOX_OVERRIDE(panel, LW_NAME(panel), toolbar_style);
MarginContainer *margin_container = memnew(MarginContainer);
panel->add_child(margin_container);
margin_container->set_theme_type_variation("MarginContainer4px");
margin_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
margin_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
VBoxContainer *toolbar = memnew(VBoxContainer);
margin_container->add_child(toolbar);
toolbar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
if (plan->is_derived()) {
Button *goto_btn = memnew(Button);
toolbar->add_child(goto_btn);
goto_btn->set_text(TTR("Edit Base"));
goto_btn->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
goto_btn->set_custom_minimum_size(Size2(150.0, 0.0) * EDSCALE);
BUTTON_SET_ICON(goto_btn, EditorInterface::get_singleton()->get_editor_theme()->get_icon(LW_NAME(Edit), LW_NAME(EditorIcons)));
goto_btn->connect(LW_NAME(pressed), callable_mp(this, &EditorInspectorPluginBBPlan::_open_base_plan).bind(plan));
} else {
Button *edit_btn = memnew(Button);
toolbar->add_child(edit_btn);
edit_btn->set_text(TTR("Manage..."));
edit_btn->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
edit_btn->set_custom_minimum_size(Size2(150.0, 0.0) * EDSCALE);
BUTTON_SET_ICON(edit_btn, EditorInterface::get_singleton()->get_editor_theme()->get_icon(LW_NAME(EditAddRemove), LW_NAME(EditorIcons)));
edit_btn->connect(LW_NAME(pressed), callable_mp(this, &EditorInspectorPluginBBPlan::_edit_plan).bind(plan));
}
add_custom_control(panel);
}
EditorInspectorPluginBBPlan::EditorInspectorPluginBBPlan() {
plan_editor = memnew(BlackboardPlanEditor);
EditorInterface::get_singleton()->get_base_control()->add_child(plan_editor);
plan_editor->hide();
toolbar_style = Ref<StyleBoxFlat>(memnew(StyleBoxFlat));
Color bg = EditorInterface::get_singleton()->get_editor_theme()->get_color(LW_NAME(accent_color), LW_NAME(Editor));
bg = bg.darkened(-0.1);
bg.a *= 0.2;
toolbar_style->set_bg_color(bg);
}

View File

@ -0,0 +1,117 @@
/**
* blackboard_plan_editor.h
* =============================================================================
* Copyright 2021-2024 Serhii Snitsaruk
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* =============================================================================
*/
#ifndef BLACKBOARD_PLAN_EDITOR_H
#define BLACKBOARD_PLAN_EDITOR_H
#include "../blackboard/blackboard_plan.h"
#ifdef LIMBOAI_MODULE
#include "editor/editor_inspector.h"
#include "scene/gui/dialogs.h"
#endif // LIMBOAI_MODULE
#ifdef LIMBOAI_GDEXTENSION
#include <godot_cpp/classes/accept_dialog.hpp>
#include <godot_cpp/classes/editor_inspector_plugin.hpp>
#include <godot_cpp/classes/panel_container.hpp>
#include <godot_cpp/classes/popup_menu.hpp>
#include <godot_cpp/classes/scroll_container.hpp>
#include <godot_cpp/classes/style_box_flat.hpp>
#include <godot_cpp/classes/v_box_container.hpp>
using namespace godot;
#endif // LIMBOAI_GDEXTENSION
// *****
class BlackboardPlanEditor : public AcceptDialog {
GDCLASS(BlackboardPlanEditor, AcceptDialog);
private:
struct ThemeCache {
Ref<Texture2D> trash_icon;
Ref<Texture2D> grab_icon;
Ref<StyleBoxFlat> odd_style;
Ref<StyleBoxFlat> even_style;
Ref<StyleBoxFlat> header_style;
} theme_cache;
int last_index = 0;
int drag_mouse_y_delta = 0;
int drag_start = -1;
int drag_index = -1;
Ref<BlackboardPlan> plan;
VBoxContainer *rows_vbox;
Button *add_var_tool;
PanelContainer *header_row;
ScrollContainer *scroll_container;
PopupMenu *type_menu;
PopupMenu *hint_menu;
void _add_var();
void _trash_var(int p_index);
void _rename_var(const String &p_new_name, int p_index);
void _change_var_type(Variant::Type p_new_type, int p_index);
void _change_var_hint(PropertyHint p_new_hint, int p_index);
void _change_var_hint_string(const String &p_new_hint_string, int p_index);
void _show_button_popup(Button *p_button, PopupMenu *p_popup, int p_index);
void _type_chosen(int id);
void _hint_chosen(int id);
void _drag_button_down(Control *p_row);
void _drag_button_up();
void _drag_button_gui_input(const Ref<InputEvent> &p_event);
void _refresh();
void _visibility_changed();
protected:
static void _bind_methods() {}
void _notification(int p_what);
public:
void edit_plan(const Ref<BlackboardPlan> &p_plan);
BlackboardPlanEditor();
};
// *****
class EditorInspectorPluginBBPlan : public EditorInspectorPlugin {
GDCLASS(EditorInspectorPluginBBPlan, EditorInspectorPlugin);
private:
BlackboardPlanEditor *plan_editor;
Ref<StyleBoxFlat> toolbar_style;
void _edit_plan(const Ref<BlackboardPlan> &p_plan);
void _open_base_plan(const Ref<BlackboardPlan> &p_plan);
protected:
static void _bind_methods() {}
public:
#ifdef LIMBOAI_MODULE
virtual bool can_handle(Object *p_object) override;
virtual void parse_begin(Object *p_object) override;
#elif LIMBOAI_GDEXTENSION
virtual bool _can_handle(Object *p_object) const override;
virtual void _parse_begin(Object *p_object) override;
#endif
EditorInspectorPluginBBPlan();
};
#endif // BLACKBOARD_PLAN_EDITOR_H

View File

@ -18,11 +18,12 @@
#include "../bt/tasks/composites/bt_probability_selector.h"
#include "../bt/tasks/composites/bt_selector.h"
#include "../bt/tasks/decorators/bt_subtree.h"
#include "../editor/debugger/limbo_debugger_plugin.h"
#include "../editor/editor_property_bb_param.h"
#include "../util/limbo_compat.h"
#include "../util/limbo_utility.h"
#include "action_banner.h"
#include "blackboard_plan_editor.h"
#include "debugger/limbo_debugger_plugin.h"
#include "editor_property_bb_param.h"
#ifdef LIMBOAI_MODULE
#include "core/config/project_settings.h"
@ -183,8 +184,9 @@ void LimboAIEditor::_update_history_buttons() {
}
void LimboAIEditor::_new_bt() {
BehaviorTree *bt = memnew(BehaviorTree);
Ref<BehaviorTree> bt = memnew(BehaviorTree);
bt->set_root_task(memnew(BTSelector));
bt->set_blackboard_plan(memnew(BlackboardPlan));
EDIT_RESOURCE(bt);
}
@ -221,6 +223,11 @@ void LimboAIEditor::edit_bt(Ref<BehaviorTree> p_behavior_tree, bool p_force_refr
return;
}
#ifdef LIMBOAI_MODULE
p_behavior_tree->editor_set_section_unfold("blackboard_plan", true);
p_behavior_tree->notify_property_list_changed();
#endif // LIMBOAI_MODULE
task_tree->load_bt(p_behavior_tree);
int idx = history.find(p_behavior_tree);
@ -697,6 +704,9 @@ void LimboAIEditor::_on_visibility_changed() {
void LimboAIEditor::_on_header_pressed() {
_update_header();
task_tree->deselect();
#ifdef LIMBOAI_MODULE
task_tree->get_bt()->editor_set_section_unfold("blackboard_plan", true);
#endif // LIMBOAI_MODULE
EDIT_RESOURCE(task_tree->get_bt());
}
@ -1393,6 +1403,11 @@ void LimboAIEditorPlugin::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_READY: {
add_debugger_plugin(memnew(LimboDebuggerPlugin));
add_inspector_plugin(memnew(EditorInspectorPluginBBPlan));
#ifdef LIMBOAI_MODULE
// ! Only used in the module version.
add_inspector_plugin(memnew(EditorInspectorPluginBBParam));
#endif // LIMBOAI_MODULE
} break;
case NOTIFICATION_ENTER_TREE: {
// Add BehaviorTree to the list of resources that should open in a new inspector.
@ -1446,11 +1461,6 @@ LimboAIEditorPlugin::LimboAIEditorPlugin() {
MAIN_SCREEN_CONTROL()->add_child(limbo_ai_editor);
limbo_ai_editor->hide();
limbo_ai_editor->set_plugin(this);
#ifdef LIMBOAI_MODULE
// ! Only used in the module version.
add_inspector_plugin(memnew(EditorInspectorPluginBBParam));
#endif // LIMBOAI_MODULE
}
LimboAIEditorPlugin::~LimboAIEditorPlugin() {

View File

@ -190,7 +190,7 @@ bool LimboHSM::dispatch(const String &p_event, const Variant &p_cargo) {
void LimboHSM::initialize(Node *p_agent, const Ref<Blackboard> &p_parent_scope) {
ERR_FAIL_COND(p_agent == nullptr);
if (!p_parent_scope.is_null()) {
blackboard->set_parent_scope(p_parent_scope);
blackboard->set_parent(p_parent_scope);
}
_initialize(p_agent, nullptr);

View File

@ -69,8 +69,9 @@ void LimboState::_initialize(Node *p_agent, const Ref<Blackboard> &p_blackboard)
agent = p_agent;
if (!p_blackboard.is_null()) {
if (!blackboard->get_data().is_empty()) {
blackboard->set_parent_scope(p_blackboard);
if (blackboard_plan.is_valid() && !blackboard_plan->is_empty()) {
blackboard = blackboard_plan->create_blackboard();
blackboard->set_parent(p_blackboard);
} else {
blackboard = p_blackboard;
}
@ -179,8 +180,8 @@ void LimboState::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear_guard"), &LimboState::clear_guard);
ClassDB::bind_method(D_METHOD("get_blackboard"), &LimboState::get_blackboard);
ClassDB::bind_method(D_METHOD("_set_blackboard_data", "p_blackboard"), &LimboState::_set_blackboard_data);
ClassDB::bind_method(D_METHOD("_get_blackboard_data"), &LimboState::_get_blackboard_data);
ClassDB::bind_method(D_METHOD("set_blackboard_plan", "p_plan"), &LimboState::set_blackboard_plan);
ClassDB::bind_method(D_METHOD("get_blackboard_plan"), &LimboState::get_blackboard_plan);
#ifdef LIMBOAI_MODULE
GDVIRTUAL_BIND(_setup);
@ -194,7 +195,7 @@ void LimboState::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "EVENT_FINISHED", PROPERTY_HINT_NONE, "", 0), "", "event_finished");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "agent", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_agent", "get_agent");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard", PROPERTY_HINT_RESOURCE_TYPE, "Blackboard", 0), "", "get_blackboard");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_blackboard_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_blackboard_data", "_get_blackboard_data");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard_plan", PROPERTY_HINT_RESOURCE_TYPE, "BlackboardPlan", PROPERTY_USAGE_DEFAULT), "set_blackboard_plan", "get_blackboard_plan");
ADD_SIGNAL(MethodInfo("setup"));
ADD_SIGNAL(MethodInfo("entered"));

View File

@ -13,6 +13,7 @@
#define LIMBO_STATE_H
#include "../blackboard/blackboard.h"
#include "../blackboard/blackboard_plan.h"
#include "../util/limbo_string_names.h"
@ -37,6 +38,7 @@ class LimboState : public Node {
GDCLASS(LimboState, Node);
private:
Ref<BlackboardPlan> blackboard_plan;
Node *agent;
Ref<Blackboard> blackboard;
HashMap<String, Callable> handlers;
@ -51,9 +53,6 @@ protected:
void _notification(int p_what);
void _set_blackboard_data(Dictionary p_value) { blackboard->set_data(p_value.duplicate()); }
Dictionary _get_blackboard_data() const { return blackboard->get_data(); }
virtual void _initialize(Node *p_agent, const Ref<Blackboard> &p_blackboard);
virtual void _setup();
@ -71,6 +70,9 @@ protected:
void add_event_handler(const String &p_event, const Callable &p_handler);
public:
void set_blackboard_plan(const Ref<BlackboardPlan> p_plan) { blackboard_plan = p_plan; }
Ref<BlackboardPlan> get_blackboard_plan() const { return blackboard_plan; }
Ref<Blackboard> get_blackboard() const { return blackboard; }
Node *get_agent() const { return agent; }

1
icons/BlackboardPlan.svg Normal file
View File

@ -0,0 +1 @@
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h16v16h-16z" fill="none"/><path d="m13 3h1.439c.861 0 1.56.699 1.56 1.56v8.88c0 .861-.699 1.56-1.56 1.56h-12.877c-.828 0-1.507-.647-1.558-1.463-.001.009-.001.017-.002.026-.001-.002 0-.049-.001-.119v-.004c0-.234.001-.591.001-.712v-8.168-1.468c0-1.295 2.999-1.483 2.999-.892v.8h3.999v1h-3.999v9.058c0-.362-1.126-.555-1.999-.35v.732c0 .309.251.56.56.56h12.877c.309 0 .56-.251.56-.56v-8.88c0-.309-.251-.56-.56-.56h-1.439zm-5 7v-6h4v6l-2 2zm2-5h-1v4h1zm-2-3.204c0-.439.896-.796 2-.796s2 .357 2 .796v1.204h-4z" fill="#c38ef1"/></svg>

After

Width:  |  Height:  |  Size: 686 B

View File

@ -47,6 +47,7 @@
#include "blackboard/bb_param/bb_vector4.h"
#include "blackboard/bb_param/bb_vector4i.h"
#include "blackboard/blackboard.h"
#include "blackboard/blackboard_plan.h"
#include "bt/behavior_tree.h"
#include "bt/bt_player.h"
#include "bt/bt_state.h"
@ -94,6 +95,7 @@
#include "bt/tasks/utility/bt_wait.h"
#include "bt/tasks/utility/bt_wait_ticks.h"
#include "editor/action_banner.h"
#include "editor/blackboard_plan_editor.h"
#include "editor/debugger/behavior_tree_data.h"
#include "editor/debugger/limbo_debugger.h"
#include "editor/debugger/limbo_debugger_plugin.h"
@ -133,6 +135,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(LimboUtility);
GDREGISTER_CLASS(Blackboard);
GDREGISTER_CLASS(BlackboardPlan);
GDREGISTER_CLASS(LimboState);
GDREGISTER_CLASS(LimboHSM);
@ -251,6 +254,8 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(BehaviorTreeView);
GDREGISTER_CLASS(LimboDebuggerTab);
GDREGISTER_CLASS(LimboDebuggerPlugin);
GDREGISTER_CLASS(BlackboardPlanEditor);
GDREGISTER_CLASS(EditorInspectorPluginBBPlan);
GDREGISTER_CLASS(LimboAIEditor);
GDREGISTER_CLASS(LimboAIEditorPlugin);
#endif // LIMBOAI_GDEXTENSION

View File

@ -50,7 +50,7 @@ TEST_CASE("[Modules][LimboAI] BTNewScope") {
CHECK(ns->get_blackboard() != parent->get_blackboard());
CHECK(ns->get_blackboard() == child->get_blackboard());
CHECK(parent->get_blackboard() == parent_bb);
CHECK(ns->get_blackboard()->get_parent_scope() == parent_bb);
CHECK(ns->get_blackboard()->get_parent() == parent_bb);
ns->get_blackboard()->set_var("fruit", "pear"); // * override "fruit"

View File

@ -15,6 +15,7 @@
#ifdef TOOLS_ENABLED
#include "core/io/resource.h"
#include "core/variant/variant.h"
#include "editor/editor_node.h"
#include "editor/plugins/script_editor_plugin.h"
#endif // TOOLS_ENABLED
@ -85,6 +86,128 @@ Variant _GLOBAL_DEF(const PropertyInfo &p_info, const Variant &p_default, bool p
// **** Shared
Variant VARIANT_DEFAULT(Variant::Type p_type) {
switch (p_type) {
case Variant::Type::NIL: {
return Variant();
} break;
case Variant::Type::BOOL: {
return Variant(false);
} break;
case Variant::Type::INT: {
return Variant(0);
} break;
case Variant::Type::FLOAT: {
return Variant(0.0);
} break;
case Variant::Type::STRING: {
return Variant("");
} break;
case Variant::Type::VECTOR2: {
return Variant(Vector2());
} break;
case Variant::Type::VECTOR2I: {
return Variant(Vector2i());
} break;
case Variant::Type::RECT2: {
return Variant(Rect2());
} break;
case Variant::Type::RECT2I: {
return Variant(Rect2i());
} break;
case Variant::Type::VECTOR3: {
return Variant(Vector3());
} break;
case Variant::Type::VECTOR3I: {
return Variant(Vector3i());
} break;
case Variant::Type::TRANSFORM2D: {
return Variant(Transform2D());
} break;
case Variant::Type::VECTOR4: {
return Variant(Vector4());
} break;
case Variant::Type::VECTOR4I: {
return Variant(Vector4i());
} break;
case Variant::Type::PLANE: {
return Variant(Plane());
} break;
case Variant::Type::QUATERNION: {
return Variant(Quaternion());
} break;
case Variant::Type::AABB: {
return Variant(AABB());
} break;
case Variant::Type::BASIS: {
return Variant(Basis());
} break;
case Variant::Type::TRANSFORM3D: {
return Variant(Transform3D());
} break;
case Variant::Type::PROJECTION: {
return Variant(Projection());
} break;
case Variant::Type::COLOR: {
return Variant(Color());
} break;
case Variant::Type::STRING_NAME: {
return Variant(StringName());
} break;
case Variant::Type::NODE_PATH: {
return Variant(NodePath());
} break;
case Variant::Type::RID: {
return Variant(RID());
} break;
case Variant::Type::OBJECT: {
return Variant();
} break;
case Variant::Type::CALLABLE: {
return Variant();
} break;
case Variant::Type::SIGNAL: {
return Variant();
} break;
case Variant::Type::DICTIONARY: {
return Variant(Dictionary());
} break;
case Variant::Type::ARRAY: {
return Variant(Array());
} break;
case Variant::Type::PACKED_BYTE_ARRAY: {
return Variant(PackedByteArray());
} break;
case Variant::Type::PACKED_INT32_ARRAY: {
return Variant(PackedInt32Array());
} break;
case Variant::Type::PACKED_INT64_ARRAY: {
return Variant(PackedInt64Array());
} break;
case Variant::Type::PACKED_FLOAT32_ARRAY: {
return Variant(PackedFloat32Array());
} break;
case Variant::Type::PACKED_FLOAT64_ARRAY: {
return Variant(PackedFloat64Array());
} break;
case Variant::Type::PACKED_STRING_ARRAY: {
return Variant(PackedStringArray());
} break;
case Variant::Type::PACKED_VECTOR2_ARRAY: {
return Variant(PackedVector2Array());
} break;
case Variant::Type::PACKED_VECTOR3_ARRAY: {
return Variant(PackedVector3Array());
} break;
case Variant::Type::PACKED_COLOR_ARRAY: {
return Variant(PackedColorArray());
} break;
default: {
return Variant();
}
}
}
#ifdef TOOLS_ENABLED
void SHOW_DOC(const String &p_topic) {

View File

@ -52,6 +52,7 @@
#define DIR_ACCESS_CREATE() DirAccess::create(DirAccess::ACCESS_RESOURCES)
#define PERFORMANCE_ADD_CUSTOM_MONITOR(m_id, m_callable) (Performance::get_singleton()->add_custom_monitor(m_id, m_callable, Variant()))
#define GET_SCRIPT(m_obj) (m_obj->get_script_instance() ? m_obj->get_script_instance()->get_script() : nullptr)
#define ADD_STYLEBOX_OVERRIDE(m_control, m_name, m_stylebox) (m_control->add_theme_style_override(m_name, m_stylebox))
#define VARIANT_EVALUATE(m_op, m_lvalue, m_rvalue, r_ret) r_ret = Variant::evaluate(m_op, m_lvalue, m_rvalue)
@ -130,6 +131,7 @@ using namespace godot;
#define DIR_ACCESS_CREATE() DirAccess::open("res://")
#define PERFORMANCE_ADD_CUSTOM_MONITOR(m_id, m_callable) (Performance::get_singleton()->add_custom_monitor(m_id, m_callable))
#define GET_SCRIPT(m_obj) (m_obj->get_script())
#define ADD_STYLEBOX_OVERRIDE(m_control, m_name, m_stylebox) (m_control->add_theme_stylebox_override(m_name, m_stylebox))
#define VARIANT_EVALUATE(m_op, m_lvalue, m_rvalue, r_ret) \
{ \
@ -218,6 +220,8 @@ inline void VARIANT_DELETE_IF_OBJECT(Variant m_variant) {
}
}
Variant VARIANT_DEFAULT(Variant::Type p_type);
#define PROJECT_CONFIG_FILE() GET_PROJECT_SETTINGS_DIR().path_join("limbo_ai.cfg")
#define IS_RESOURCE_FILE(m_path) (m_path.begins_with("res://") && m_path.find("::") == -1)
#define RESOURCE_TYPE_HINT(m_type) vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, m_type)

View File

@ -42,6 +42,7 @@ LimboStringNames::LimboStringNames() {
_update_banners = SN("_update_banners");
_weight_ = SN("_weight_");
accent_color = SN("accent_color");
Add = SN("Add");
add_child = SN("add_child");
add_child_at_index = SN("add_child_at_index");
AnimationFilter = SN("AnimationFilter");
@ -52,13 +53,19 @@ LimboStringNames::LimboStringNames() {
bold = SN("bold");
BTAlwaysFail = SN("BTAlwaysFail");
BTAlwaysSucceed = SN("BTAlwaysSucceed");
button_down = SN("button_down");
button_up = SN("button_up");
changed = SN("changed");
connect = SN("connect");
dark_color_1 = SN("dark_color_1");
dark_color_2 = SN("dark_color_2");
Debug = SN("Debug");
disabled_font_color = SN("disabled_font_color");
doc_italic = SN("doc_italic");
draw = SN("draw");
Duplicate = SN("Duplicate");
Edit = SN("Edit");
EditAddRemove = SN("EditAddRemove");
Editor = SN("Editor");
EditorFonts = SN("EditorFonts");
EditorIcons = SN("EditorIcons");
@ -76,6 +83,7 @@ LimboStringNames::LimboStringNames() {
gui_input = SN("gui_input");
GuiTreeArrowDown = SN("GuiTreeArrowDown");
GuiTreeArrowRight = SN("GuiTreeArrowRight");
HeaderSmall = SN("HeaderSmall");
Help = SN("Help");
icon_max_width = SN("icon_max_width");
id_pressed = SN("id_pressed");
@ -97,6 +105,7 @@ LimboStringNames::LimboStringNames() {
NodeWarning = SN("NodeWarning");
NonFavorite = SN("NonFavorite");
normal = SN("normal");
panel = SN("panel");
popup_hide = SN("popup_hide");
pressed = SN("pressed");
probability_clicked = SN("probability_clicked");
@ -111,6 +120,7 @@ LimboStringNames::LimboStringNames() {
Script = SN("Script");
ScriptCreate = SN("ScriptCreate");
Search = SN("Search");
separation = SN("separation");
set_custom_name = SN("set_custom_name");
set_root_task = SN("set_root_task");
setup = SN("setup");
@ -125,9 +135,12 @@ LimboStringNames::LimboStringNames() {
task_meta = SN("task_meta");
task_selected = SN("task_selected");
text_changed = SN("text_changed");
text_submitted = SN("text_submitted");
timeout = SN("timeout");
toggled = SN("toggled");
Tools = SN("Tools");
Tree = SN("Tree");
TripleBar = SN("TripleBar");
update_task = SN("update_task");
update_tree = SN("update_tree");
updated = SN("updated");

View File

@ -58,6 +58,7 @@ public:
StringName accent_color;
StringName add_child_at_index;
StringName add_child;
StringName Add;
StringName AnimationFilter;
StringName Back;
StringName behavior_tree_finished;
@ -66,13 +67,19 @@ public:
StringName bold;
StringName BTAlwaysFail;
StringName BTAlwaysSucceed;
StringName button_down;
StringName button_up;
StringName changed;
StringName connect;
StringName dark_color_1;
StringName dark_color_2;
StringName Debug;
StringName disabled_font_color;
StringName doc_italic;
StringName draw;
StringName Duplicate;
StringName Edit;
StringName EditAddRemove;
StringName Editor;
StringName EditorFonts;
StringName EditorIcons;
@ -90,6 +97,7 @@ public:
StringName gui_input;
StringName GuiTreeArrowDown;
StringName GuiTreeArrowRight;
StringName HeaderSmall;
StringName Help;
StringName icon_max_width;
StringName id_pressed;
@ -112,6 +120,7 @@ public:
StringName NodeWarning;
StringName NonFavorite;
StringName normal;
StringName panel;
StringName popup_hide;
StringName pressed;
StringName probability_clicked;
@ -126,6 +135,7 @@ public:
StringName Script;
StringName ScriptCreate;
StringName Search;
StringName separation;
StringName set_custom_name;
StringName set_root_task;
StringName setup;
@ -140,9 +150,12 @@ public:
StringName task_meta;
StringName task_selected;
StringName text_changed;
StringName text_submitted;
StringName timeout;
StringName toggled;
StringName Tools;
StringName Tree;
StringName TripleBar;
StringName update_task;
StringName update_tree;
StringName updated;

View File

@ -288,6 +288,129 @@ Variant LimboUtility::perform_operation(Operation p_operation, const Variant &le
return ret;
}
String LimboUtility::get_property_hint_text(PropertyHint p_hint) const {
switch (p_hint) {
case PROPERTY_HINT_NONE: {
return "NONE";
}
case PROPERTY_HINT_RANGE: {
return "RANGE";
}
case PROPERTY_HINT_ENUM: {
return "ENUM";
}
case PROPERTY_HINT_ENUM_SUGGESTION: {
return "SUGGESTION";
}
case PROPERTY_HINT_EXP_EASING: {
return "EXP_EASING";
}
case PROPERTY_HINT_LINK: {
return "LINK";
}
case PROPERTY_HINT_FLAGS: {
return "FLAGS";
}
case PROPERTY_HINT_LAYERS_2D_RENDER: {
return "LAYERS_2D_RENDER";
}
case PROPERTY_HINT_LAYERS_2D_PHYSICS: {
return "LAYERS_2D_PHYSICS";
}
case PROPERTY_HINT_LAYERS_2D_NAVIGATION: {
return "LAYERS_2D_NAVIGATION";
}
case PROPERTY_HINT_LAYERS_3D_RENDER: {
return "LAYERS_3D_RENDER";
}
case PROPERTY_HINT_LAYERS_3D_PHYSICS: {
return "LAYERS_3D_PHYSICS";
}
case PROPERTY_HINT_LAYERS_3D_NAVIGATION: {
return "LAYERS_3D_NAVIGATION";
}
case PROPERTY_HINT_FILE: {
return "FILE";
}
case PROPERTY_HINT_DIR: {
return "DIR";
}
case PROPERTY_HINT_GLOBAL_FILE: {
return "GLOBAL_FILE";
}
case PROPERTY_HINT_GLOBAL_DIR: {
return "GLOBAL_DIR";
}
case PROPERTY_HINT_RESOURCE_TYPE: {
return "RESOURCE_TYPE";
}
case PROPERTY_HINT_MULTILINE_TEXT: {
return "MULTILINE_TEXT";
}
case PROPERTY_HINT_EXPRESSION: {
return "EXPRESSION";
}
case PROPERTY_HINT_PLACEHOLDER_TEXT: {
return "PLACEHOLDER_TEXT";
}
case PROPERTY_HINT_COLOR_NO_ALPHA: {
return "COLOR_NO_ALPHA";
}
case PROPERTY_HINT_OBJECT_ID: {
return "OBJECT_ID";
}
case PROPERTY_HINT_TYPE_STRING: {
return "TYPE_STRING";
}
case PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE: {
return "NODE_PATH_TO_EDITED_NODE";
}
case PROPERTY_HINT_OBJECT_TOO_BIG: {
return "OBJECT_TOO_BIG";
}
case PROPERTY_HINT_NODE_PATH_VALID_TYPES: {
return "NODE_PATH_VALID_TYPES";
}
case PROPERTY_HINT_SAVE_FILE: {
return "SAVE_FILE";
}
case PROPERTY_HINT_GLOBAL_SAVE_FILE: {
return "GLOBAL_SAVE_FILE";
}
case PROPERTY_HINT_INT_IS_OBJECTID: {
return "INT_IS_OBJECTID";
}
case PROPERTY_HINT_INT_IS_POINTER: {
return "INT_IS_POINTER";
}
case PROPERTY_HINT_ARRAY_TYPE: {
return "ARRAY_TYPE";
}
case PROPERTY_HINT_LOCALE_ID: {
return "LOCALE_ID";
}
case PROPERTY_HINT_LOCALIZABLE_STRING: {
return "LOCALIZABLE_STRING";
}
case PROPERTY_HINT_NODE_TYPE: {
return "NODE_TYPE";
}
case PROPERTY_HINT_HIDE_QUATERNION_EDIT: {
return "HIDE_QUATERNION_EDIT";
}
case PROPERTY_HINT_PASSWORD: {
return "PASSWORD";
}
case PROPERTY_HINT_LAYERS_AVOIDANCE: {
return "LAYERS_AVOIDANCE";
}
case PROPERTY_HINT_MAX: {
return "MAX";
}
}
return "";
}
#ifdef TOOLS_ENABLED
Ref<Shortcut> LimboUtility::add_shortcut(const String &p_path, const String &p_name, Key p_keycode) {

View File

@ -88,6 +88,8 @@ public:
String get_operation_string(Operation p_operation) const;
Variant perform_operation(Operation p_operation, const Variant &left_value, const Variant &right_value);
String get_property_hint_text(PropertyHint p_hint) const;
#ifdef TOOLS_ENABLED
Ref<Shortcut> add_shortcut(const String &p_path, const String &p_name, Key p_keycode = LW_KEY(NONE));
bool is_shortcut(const String &p_path, const Ref<InputEvent> &p_event) const;