From b4bef31df57cd2ed829a76bc4c598527863836d9 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 8 Dec 2023 21:56:14 +0100 Subject: [PATCH] Add property editor for blackboard parameters --- blackboard/bb_param/bb_param.h | 4 +- demo/ai/tasks/arrive_pos.gd | 1 - demo/project.godot | 1 + .../blackboard_parameters/test_bb_params.gd | 37 +++ editor/editor_property_bb_param.cpp | 301 ++++++++++++++++++ editor/editor_property_bb_param.h | 60 ++++ editor/limbo_ai_editor_plugin.cpp | 2 + icons/LimboSpecifyValue.svg | 1 + 8 files changed, 404 insertions(+), 3 deletions(-) create mode 100644 demo/tests/blackboard_parameters/test_bb_params.gd create mode 100644 editor/editor_property_bb_param.cpp create mode 100644 editor/editor_property_bb_param.h create mode 100644 icons/LimboSpecifyValue.svg diff --git a/blackboard/bb_param/bb_param.h b/blackboard/bb_param/bb_param.h index 4a74e2d..65e32cf 100644 --- a/blackboard/bb_param/bb_param.h +++ b/blackboard/bb_param/bb_param.h @@ -41,8 +41,6 @@ private: protected: static void _bind_methods(); - virtual Variant::Type get_type() const { return Variant::NIL; } - _FORCE_INLINE_ void _assign_default_value() { Callable::CallError err; Variant::construct(get_type(), saved_value, nullptr, 0, err); @@ -51,6 +49,8 @@ protected: void _get_property_list(List *p_list) const; public: + virtual Variant::Type get_type() const { return Variant::NIL; } + void set_value_source(ValueSource p_value); ValueSource get_value_source() const { return value_source; } diff --git a/demo/ai/tasks/arrive_pos.gd b/demo/ai/tasks/arrive_pos.gd index 3c04885..e0e361d 100644 --- a/demo/ai/tasks/arrive_pos.gd +++ b/demo/ai/tasks/arrive_pos.gd @@ -11,7 +11,6 @@ @tool @icon("res://icon.png") -class_name ArrivePos extends BTAction @export var target_position_var := "target_position" diff --git a/demo/project.godot b/demo/project.godot index f100ae9..d2acb33 100644 --- a/demo/project.godot +++ b/demo/project.godot @@ -11,6 +11,7 @@ config_version=5 [application] config/name="LimboAI Test" +config/tags=PackedStringArray("demo") run/main_scene="res://examples/waypoints/example_waypoints.tscn" config/features=PackedStringArray("4.2") config/icon="res://icon.png" diff --git a/demo/tests/blackboard_parameters/test_bb_params.gd b/demo/tests/blackboard_parameters/test_bb_params.gd new file mode 100644 index 0000000..459fe5b --- /dev/null +++ b/demo/tests/blackboard_parameters/test_bb_params.gd @@ -0,0 +1,37 @@ +@tool +class_name TestBBParams +extends Resource + +@export var bool_param: BBBool +@export var int_param: BBInt +@export var float_param: BBFloat +@export var string_param: BBString +@export var vec2_param: BBVector2 +@export var vec2i_param: BBVector2i +@export var rect2_param: BBRect2 +@export var rect2i_param: BBRect2i +@export var vector3_param: BBVector3 +@export var vector3i_param: BBVector3i +@export var vector4_param: BBVector4 +@export var vector4i_param: BBVector4i +@export var transform2d_param: BBTransform2D +@export var plane_param: BBPlane +@export var quaternion_param: BBQuaternion +@export var aabb_param: BBAabb +@export var basis_param: BBBasis +@export var transform3d_param: BBTransform3D +#@export var projection_param: BBProjection +@export var color_param: BBColor +@export var stringname_param: BBStringName +@export var node_param: BBNode +@export var dictionary_param: BBDictionary +@export var array_param: BBArray +@export var byte_array_param: BBByteArray +@export var int_array_param: BBIntArray +@export var float_array_param: BBFloatArray +@export var string_array_param: BBStringArray +@export var vector2_array_param: BBVector2Array +@export var vector3_array_param: BBVector2Array +@export var color_array_param: BBColorArray +@export var variant_param: BBVariant + diff --git a/editor/editor_property_bb_param.cpp b/editor/editor_property_bb_param.cpp new file mode 100644 index 0000000..cd36f63 --- /dev/null +++ b/editor/editor_property_bb_param.cpp @@ -0,0 +1,301 @@ +/** + * editor_property_bb_param.cpp + * ============================================================================= + * Copyright 2021-2023 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 "editor_property_bb_param.h" + +#include "modules/limboai/blackboard/bb_param/bb_param.h" + +#include "core/error/error_macros.h" +#include "core/object/class_db.h" +#include "core/os/memory.h" +#include "core/string/print_string.h" +#include "editor/editor_inspector.h" +#include "editor/editor_properties.h" +#include "editor/editor_properties_array_dict.h" +#include "editor/editor_properties_vector.h" +#include "editor/editor_settings.h" +#include "scene/gui/base_button.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/line_edit.h" + +Ref EditorPropertyBBParam::_get_edited_param() { + Ref param = get_edited_property_value(); + if (param.is_null()) { + // Create parameter resource if null. + param = ClassDB::instantiate(param_type); + get_edited_object()->set(get_edited_property(), param); + } + return param; +} + +void EditorPropertyBBParam::_create_value_editor(Variant::Type p_type) { + if (value_editor) { + if (value_editor->get_meta(SNAME("_param_type")) == Variant(p_type)) { + return; + } + _remove_value_editor(); + } + + switch (p_type) { + case Variant::BOOL: { + value_editor = memnew(EditorPropertyCheck); + } break; + case Variant::INT: { + EditorPropertyInteger *editor = memnew(EditorPropertyInteger); + editor->setup(-100000, 100000, 1, false, true, true); + value_editor = editor; + } break; + case Variant::FLOAT: { + EditorPropertyFloat *editor = memnew(EditorPropertyFloat); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true, false, true, true); + value_editor = editor; + } break; + case Variant::STRING: { + if (property_hint == PROPERTY_HINT_MULTILINE_TEXT) { + value_editor = memnew(EditorPropertyMultilineText); + } else { + value_editor = memnew(EditorPropertyText); + } + } break; + case Variant::VECTOR2: { + EditorPropertyVector2 *editor = memnew(EditorPropertyVector2); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::VECTOR2I: { + EditorPropertyVector2i *editor = memnew(EditorPropertyVector2i); + editor->setup(-100000, 100000); + value_editor = editor; + } break; + case Variant::RECT2: { + EditorPropertyRect2 *editor = memnew(EditorPropertyRect2); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::RECT2I: { + EditorPropertyRect2i *editor = memnew(EditorPropertyRect2i); + editor->setup(-100000, 100000); + value_editor = editor; + } break; + case Variant::VECTOR3: { + EditorPropertyVector3 *editor = memnew(EditorPropertyVector3); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::VECTOR3I: { + EditorPropertyVector3i *editor = memnew(EditorPropertyVector3i); + editor->setup(-100000, 100000); + value_editor = editor; + } break; + case Variant::VECTOR4: { + EditorPropertyVector4 *editor = memnew(EditorPropertyVector4); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::VECTOR4I: { + EditorPropertyVector4i *editor = memnew(EditorPropertyVector4i); + editor->setup(-100000, 100000); + value_editor = editor; + } break; + case Variant::TRANSFORM2D: { + EditorPropertyTransform2D *editor = memnew(EditorPropertyTransform2D); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::PLANE: { + EditorPropertyPlane *editor = memnew(EditorPropertyPlane); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::QUATERNION: { + EditorPropertyQuaternion *editor = memnew(EditorPropertyQuaternion); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::AABB: { + EditorPropertyAABB *editor = memnew(EditorPropertyAABB); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::BASIS: { + EditorPropertyBasis *editor = memnew(EditorPropertyBasis); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::TRANSFORM3D: { + EditorPropertyTransform3D *editor = memnew(EditorPropertyTransform3D); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::PROJECTION: { + EditorPropertyProjection *editor = memnew(EditorPropertyProjection); + editor->setup(-100000, 100000, EDITOR_GET("interface/inspector/default_float_step"), true); + value_editor = editor; + } break; + case Variant::COLOR: { + value_editor = memnew(EditorPropertyColor); + } break; + case Variant::STRING_NAME: { + EditorPropertyText *editor = memnew(EditorPropertyText); + editor->set_string_name(true); + value_editor = editor; + } break; + case Variant::NODE_PATH: { + value_editor = memnew(EditorPropertyNodePath); + } break; + // case Variant::RID: { + // } break; + // case Variant::SIGNAL: { + // } break; + // case Variant::CALLABLE: { + // } break; + // case Variant::OBJECT: { + // } break; + case Variant::DICTIONARY: { + value_editor = memnew(EditorPropertyDictionary); + } break; + + case Variant::ARRAY: + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + case Variant::PACKED_STRING_ARRAY: + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::PACKED_COLOR_ARRAY: { + EditorPropertyArray *editor = memnew(EditorPropertyArray); + editor->setup(p_type); + value_editor = editor; + } break; + + default: { + ERR_PRINT("Unexpected variant type!"); + value_editor = memnew(EditorPropertyNil); + } + } + value_editor->set_name_split_ratio(0.0); + hbox->add_child(value_editor); + value_editor->set_h_size_flags(SIZE_EXPAND_FILL); + value_editor->set_meta(SNAME("_param_type"), p_type); + value_editor->connect(SNAME("property_changed"), callable_mp(this, &EditorPropertyBBParam::_value_edited)); +} + +void EditorPropertyBBParam::_remove_value_editor() { + if (value_editor) { + hbox->remove_child(value_editor); + value_editor->queue_free(); + value_editor = nullptr; + } +} + +void EditorPropertyBBParam::_value_edited(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { + _get_edited_param()->set_saved_value(p_value); +} + +void EditorPropertyBBParam::_mode_changed() { + _get_edited_param()->set_value_source(value_mode->is_pressed() ? BBParam::SAVED_VALUE : BBParam::BLACKBOARD_VAR); + update_property(); +} + +void EditorPropertyBBParam::_variable_edited(const String &p_text) { + _get_edited_param()->set_variable(p_text); +} + +void EditorPropertyBBParam::update_property() { + Ref param = _get_edited_param(); + if (param->get_value_source() == BBParam::BLACKBOARD_VAR) { + _remove_value_editor(); + variable_edit->set_text(param->get_variable()); + variable_edit->set_editable(true); + variable_edit->show(); + variable_mode->set_pressed_no_signal(true); + value_mode->set_pressed_no_signal(false); + } else { + variable_edit->hide(); + _create_value_editor(param->get_type()); + value_editor->show(); + value_editor->set_object_and_property(param.ptr(), SNAME("saved_value")); + value_mode->set_pressed_no_signal(true); + variable_mode->set_pressed_no_signal(false); + value_editor->update_property(); + } +} + +void EditorPropertyBBParam::setup(PropertyHint p_hint, const String &p_hint_text) { + param_type = p_hint_text; + property_hint = p_hint; +} + +void EditorPropertyBBParam::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + value_mode->set_icon(get_editor_theme_icon(SNAME("LimboSpecifyValue"))); + variable_mode->set_icon(get_editor_theme_icon(SNAME("BTSetVar"))); + } break; + } +} + +EditorPropertyBBParam::EditorPropertyBBParam() { + hbox = memnew(HBoxContainer); + add_child(hbox); + + HBoxContainer *modes = memnew(HBoxContainer); + hbox->add_child(modes); + modes->add_theme_constant_override("separation", 0); + + Ref modes_group; + modes_group.instantiate(); + + value_mode = memnew(Button); + modes->add_child(value_mode); + value_mode->set_focus_mode(FOCUS_NONE); + value_mode->set_button_group(modes_group); + value_mode->set_toggle_mode(true); + value_mode->set_tooltip_text(TTR("Specify value")); + value_mode->connect(SNAME("pressed"), callable_mp(this, &EditorPropertyBBParam::_mode_changed)); + + variable_mode = memnew(Button); + modes->add_child(variable_mode); + variable_mode->set_focus_mode(FOCUS_NONE); + variable_mode->set_button_group(modes_group); + variable_mode->set_toggle_mode(true); + variable_mode->set_tooltip_text(TTR("Bind to a blackboard variable")); + variable_mode->connect(SNAME("pressed"), callable_mp(this, &EditorPropertyBBParam::_mode_changed)); + + variable_edit = memnew(LineEdit); + hbox->add_child(variable_edit); + variable_edit->set_placeholder(TTR("Variable")); + variable_edit->set_h_size_flags(SIZE_EXPAND_FILL); + variable_edit->connect(SNAME("text_changed"), callable_mp(this, &EditorPropertyBBParam::_variable_edited)); + + param_type = SNAME("BBString"); +} + +//***** EditorInspectorPluginBBParam + +bool EditorInspectorPluginBBParam::can_handle(Object *p_object) { + return true; // Handles everything. +} + +bool EditorInspectorPluginBBParam::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField p_usage, const bool p_wide) { + if (p_hint == PROPERTY_HINT_RESOURCE_TYPE && p_hint_text.begins_with("BB")) { + // TODO: Add more rigid hint check. + EditorPropertyBBParam *editor = memnew(EditorPropertyBBParam()); + editor->setup(p_hint, p_hint_text); + add_property_editor(p_path, editor); + return true; + } + return false; +} diff --git a/editor/editor_property_bb_param.h b/editor/editor_property_bb_param.h new file mode 100644 index 0000000..63b1130 --- /dev/null +++ b/editor/editor_property_bb_param.h @@ -0,0 +1,60 @@ +/** + * editor_property_bb_param.h + * ============================================================================= + * Copyright 2021-2023 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 EDITOR_PROPERTY_BB_PARAM_H +#define EDITOR_PROPERTY_BB_PARAM_H + +#include "core/object/object.h" +#include "editor/editor_inspector.h" + +#include "modules/limboai/blackboard/bb_param/bb_param.h" + +class EditorPropertyBBParam : public EditorProperty { + GDCLASS(EditorPropertyBBParam, EditorProperty); + +private: + StringName param_type; + PropertyHint property_hint = PROPERTY_HINT_NONE; + + HBoxContainer *hbox = nullptr; + Button *value_mode = nullptr; + Button *variable_mode = nullptr; + EditorProperty *value_editor = nullptr; + LineEdit *variable_edit = nullptr; + + Ref _get_edited_param(); + + void _create_value_editor(Variant::Type p_type); + void _remove_value_editor(); + + void _value_edited(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false); + void _variable_edited(const String &p_text); + void _mode_changed(); + +protected: + void _notification(int p_what); + +public: + virtual void update_property() override; + void setup(PropertyHint p_hint, const String &p_hint_text); + + EditorPropertyBBParam(); +}; + +class EditorInspectorPluginBBParam : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginBBParam, EditorInspectorPlugin); + +public: + virtual bool can_handle(Object *p_object) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField p_usage, const bool p_wide = false) override; +}; + +#endif // EDITOR_PROPERTY_BB_PARAM_H diff --git a/editor/limbo_ai_editor_plugin.cpp b/editor/limbo_ai_editor_plugin.cpp index 846fcd8..54516b4 100644 --- a/editor/limbo_ai_editor_plugin.cpp +++ b/editor/limbo_ai_editor_plugin.cpp @@ -18,6 +18,7 @@ #include "modules/limboai/bt/tasks/composites/bt_probability_selector.h" #include "modules/limboai/bt/tasks/composites/bt_selector.h" #include "modules/limboai/editor/debugger/limbo_debugger_plugin.h" +#include "modules/limboai/editor/editor_property_bb_param.h" #include "modules/limboai/util/limbo_utility.h" #include "core/config/project_settings.h" @@ -1157,6 +1158,7 @@ LimboAIEditorPlugin::LimboAIEditorPlugin() { EditorNode::get_singleton()->get_main_screen_control()->add_child(limbo_ai_editor); limbo_ai_editor->hide(); add_debugger_plugin(memnew(LimboDebuggerPlugin)); + add_inspector_plugin(memnew(EditorInspectorPluginBBParam)); } LimboAIEditorPlugin::~LimboAIEditorPlugin() { diff --git a/icons/LimboSpecifyValue.svg b/icons/LimboSpecifyValue.svg new file mode 100644 index 0000000..008bee7 --- /dev/null +++ b/icons/LimboSpecifyValue.svg @@ -0,0 +1 @@ + \ No newline at end of file