Compare commits
19 Commits
a67f38b862
...
60f92a6432
Author | SHA1 | Date |
---|---|---|
|
60f92a6432 | |
|
590af23665 | |
|
d091461cac | |
|
127955b802 | |
|
42d54db286 | |
|
9482a45b76 | |
|
4d49a02881 | |
|
273af02cd8 | |
|
dd8b72850e | |
|
dddc713a3a | |
|
6fecc1e4b5 | |
|
fe87a87759 | |
|
0e0a4685f9 | |
|
955da07652 | |
|
90c0a9a4f6 | |
|
1359f1e606 | |
|
0d65c1117a | |
|
a5118ebc1b | |
|
35a2cbac56 |
36
README.md
36
README.md
|
@ -16,7 +16,7 @@
|
|||
**LimboAI** is an open-source C++ plugin for **Godot Engine 4** providing a combination of
|
||||
**Behavior Trees** and **State Machines**, which can be used together to create complex AI behaviors.
|
||||
It comes with a behavior tree editor, built-in documentation, visual debugger, extensive demo project with a tutorial, and more!
|
||||
While it is implemented in C++, it fully supports GDScript for [creating your own tasks](https://limboai.readthedocs.io/en/stable/getting-started/custom-tasks.html) and [states](https://limboai.readthedocs.io/en/stable/getting-started/hsm.html).
|
||||
While it is implemented in C++, it fully supports GDScript for [creating your own tasks](https://limboai.readthedocs.io/en/stable/behavior-trees/custom-tasks.html) and [states](https://limboai.readthedocs.io/en/stable/hierarchical-state-machines/create-hsm.html).
|
||||
|
||||
If you enjoy using LimboAI, please **consider supporting** my efforts with a donation on Ko-fi 😊 Your contribution will help me continue developing and improving it.
|
||||
|
||||
|
@ -24,7 +24,7 @@ If you enjoy using LimboAI, please **consider supporting** my efforts with a don
|
|||
|
||||

|
||||
|
||||
Behavior Trees are powerful hierarchical structures used to model and control the behavior of agents in a game (e.g., characters, enemies). They are designed to make it easier to create rich and highly modular behaviors for your games. To learn more about behavior trees, check out [Introduction to Behavior Trees](https://limboai.readthedocs.io/en/stable/getting-started/introduction.html) and our demo project, which includes a tutorial.
|
||||
Behavior Trees are powerful hierarchical structures used to model and control the behavior of agents in a game (e.g., characters, enemies). They are designed to make it easier to create rich and highly modular behaviors for your games. To learn more about behavior trees, check out [Introduction to Behavior Trees](https://limboai.readthedocs.io/en/stable/behavior-trees/introduction.html) and our demo project, which includes a tutorial.
|
||||
|
||||
## Demonstration
|
||||
|
||||
|
@ -50,13 +50,13 @@ Behavior Trees are powerful hierarchical structures used to model and control th
|
|||
- Execute `BehaviorTree` resources using the `BTPlayer` node.
|
||||
- Create complex behaviors by combining and nesting tasks in a hierarchy.
|
||||
- Control execution flow using composite, decorator, and condition tasks.
|
||||
- [Create custom tasks](https://limboai.readthedocs.io/en/stable/getting-started/custom-tasks.html) by extending core classes: `BTAction`, `BTCondition`, `BTDecorator`, and `BTComposite`.
|
||||
- [Create custom tasks](https://limboai.readthedocs.io/en/stable/behavior-trees/custom-tasks.html) by extending core classes: `BTAction`, `BTCondition`, `BTDecorator`, and `BTComposite`.
|
||||
- Built-in class documentation.
|
||||
- Blackboard system: Share data seamlessly between tasks using the `Blackboard`.
|
||||
- Blackboard plans: Define variables in the BehaviorTree resource and override their values in the BTPlayer node.
|
||||
- Plan editor: Manage variables, their data types and property hints.
|
||||
- Blackboard scopes: Prevent name conflicts and enable advanced techniques like [sharing data between several agents](https://limboai.readthedocs.io/en/stable/getting-started/using-blackboard.html#sharing-data-between-several-agents).
|
||||
- Blackboard parameters: [Export a BB parameter](https://limboai.readthedocs.io/en/stable/getting-started/using-blackboard.html#task-parameters), for which user can provide a value or bind it to a blackboard variable (can be used in custom tasks).
|
||||
- Blackboard scopes: Prevent name conflicts and enable advanced techniques like [sharing data between several agents](https://limboai.readthedocs.io/en/stable/behavior-trees/using-blackboard.html#sharing-data-between-several-agents).
|
||||
- Blackboard parameters: [Export a BB parameter](https://limboai.readthedocs.io/en/stable/behavior-trees/using-blackboard.html#task-parameters), for which user can provide a value or bind it to a blackboard variable (can be used in custom tasks).
|
||||
- Inspector support for specifying blackboard variables (custom editor for exported `StringName` properties ending with "_var").
|
||||
- Use the `BTSubtree` task to execute a tree from a different resource file, promoting organization and reusability.
|
||||
- Visual Debugger: Inspect the execution of any BT in a running scene to identify and troubleshoot issues.
|
||||
|
@ -67,24 +67,24 @@ Behavior Trees are powerful hierarchical structures used to model and control th
|
|||
- Extend the `LimboState` class to implement state logic.
|
||||
- `LimboHSM` node serves as a state machine that manages `LimboState` instances and transitions.
|
||||
- `LimboHSM` is a state itself and can be nested within other `LimboHSM` instances.
|
||||
- [Event-based](https://limboai.readthedocs.io/en/stable/getting-started/hsm.html#events-and-transitions): Transitions are associated with events and are triggered by the state machine when the relevant event is dispatched, allowing for better decoupling of transitions from state logic.
|
||||
- [Event-based](https://limboai.readthedocs.io/en/stable/hierarchical-state-machines/create-hsm.html#events-and-transitions): Transitions are associated with events and are triggered by the state machine when the relevant event is dispatched, allowing for better decoupling of transitions from state logic.
|
||||
- Combine state machines with behavior trees using `BTState` for advanced reactive AI.
|
||||
- Delegation Option: Using the vanilla `LimboState`, [delegate the implementation](https://limboai.readthedocs.io/en/stable/getting-started/hsm.html#single-file-state-machine-setup) to your callback functions, making it perfect for rapid prototyping and game jams.
|
||||
- Delegation Option: Using the vanilla `LimboState`, [delegate the implementation](https://limboai.readthedocs.io/en/stable/hierarchical-state-machines/create-hsm.html#single-file-state-machine-setup) to your callback functions, making it perfect for rapid prototyping and game jams.
|
||||
- 🛈 Note: State machine setup and initialization require code; there is no GUI editor.
|
||||
|
||||
- **Tested:** Behavior tree tasks and HSM are covered by unit tests.
|
||||
|
||||
- **GDExtension:** LimboAI can be [used as extension](https://limboai.readthedocs.io/en/stable/getting-started/gdextension.html). Custom engine builds are not necessary.
|
||||
- **GDExtension:** LimboAI can be [used as extension](https://limboai.readthedocs.io/en/stable/getting-started/getting-limboai.html#get-gdextension-version). Custom engine builds are not necessary.
|
||||
|
||||
- **Demo + Tutorial:** Check out our extensive demo project, which includes an introduction to behavior trees using examples.
|
||||
|
||||
## First steps
|
||||
|
||||
Follow the [First steps](https://limboai.readthedocs.io/en/stable/index.html#first-steps) guide to learn how to get started with LimboAI and the demo project.
|
||||
Follow the [Getting started](https://limboai.readthedocs.io/en/stable/getting-started/getting-limboai.html) guide to learn how to get started with LimboAI and the demo project.
|
||||
|
||||
## Getting LimboAI
|
||||
|
||||
LimboAI can be used as either a C++ module or as a GDExtension shared library. GDExtension version is more convenient to use but somewhat limited in features. Whichever you choose to use, your project will stay compatible with both and you can switch from one to the other any time. See [Using GDExtension](https://limboai.readthedocs.io/en/stable/getting-started/gdextension.html).
|
||||
LimboAI can be used as either a C++ module or as a GDExtension shared library. GDExtension version is more convenient to use but somewhat limited in features. Whichever you choose to use, your project will stay compatible with both and you can switch from one to the other any time. See [Using GDExtension](https://limboai.readthedocs.io/en/stable/getting-started/getting-limboai.html#get-gdextension-version).
|
||||
|
||||
### Precompiled builds
|
||||
|
||||
|
@ -109,15 +109,15 @@ LimboAI can be used as either a C++ module or as a GDExtension shared library. G
|
|||
## Using the plugin
|
||||
|
||||
- Online Documentation: [stable](https://limboai.readthedocs.io/en/stable/index.html), [latest](https://limboai.readthedocs.io/en/latest/index.html)
|
||||
- [First steps](https://limboai.readthedocs.io/en/stable/index.html#first-steps)
|
||||
- [Introduction to Behavior Trees](https://limboai.readthedocs.io/en/stable/getting-started/introduction.html)
|
||||
- [Creating custom tasks in GDScript](https://limboai.readthedocs.io/en/stable/getting-started/custom-tasks.html)
|
||||
- [Sharing data using Blackboard](https://limboai.readthedocs.io/en/stable/getting-started/using-blackboard.html)
|
||||
- [Accessing nodes in the scene tree](https://limboai.readthedocs.io/en/stable/getting-started/accessing-nodes.html)
|
||||
- [State machines](https://limboai.readthedocs.io/en/stable/getting-started/hsm.html)
|
||||
- [Using GDExtension](https://limboai.readthedocs.io/en/stable/getting-started/gdextension.html)
|
||||
- [Getting started](https://limboai.readthedocs.io/en/stable/getting-started/getting-limboai.html)
|
||||
- [Introduction to Behavior Trees](https://limboai.readthedocs.io/en/stable/behavior-trees/introduction.html)
|
||||
- [Creating custom tasks in GDScript](https://limboai.readthedocs.io/en/stable/behavior-trees/custom-tasks.html)
|
||||
- [Sharing data using Blackboard](https://limboai.readthedocs.io/en/stable/behavior-trees/using-blackboard.html)
|
||||
- [Accessing nodes in the scene tree](https://limboai.readthedocs.io/en/stable/behavior-trees/accessing-nodes.html)
|
||||
- [State machines](https://limboai.readthedocs.io/en/stable/hierarchical-state-machines/create-hsm.html)
|
||||
- [Using GDExtension](https://limboai.readthedocs.io/en/stable/getting-started/getting-limboai.html#get-gdextension-version)
|
||||
- [Using LimboAI with C#](https://limboai.readthedocs.io/en/stable/getting-started/c-sharp.html)
|
||||
- [Class reference](https://limboai.readthedocs.io/en/stable/getting-started/featured-classes.html)
|
||||
- [Class reference](https://limboai.readthedocs.io/en/stable/classes/featured-classes.html)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
Variant BBNode::get_value(Node *p_scene_root, const Ref<Blackboard> &p_blackboard, const Variant &p_default) {
|
||||
ERR_FAIL_NULL_V_MSG(p_scene_root, Variant(), "BBNode: get_value() failed - scene_root is null.");
|
||||
ERR_FAIL_NULL_V_MSG(p_blackboard, Variant(), "BBNode: get_value() failed - blackboard is null.");
|
||||
ERR_FAIL_COND_V_MSG(p_blackboard.is_null(), Variant(), "BBNode: get_value() failed - blackboard is null.");
|
||||
|
||||
Variant val;
|
||||
if (get_value_source() == SAVED_VALUE) {
|
||||
|
|
|
@ -532,7 +532,7 @@ void BlackboardPlan::populate_blackboard(const Ref<Blackboard> &p_blackboard, bo
|
|||
if (has_mapping) {
|
||||
StringName target_var = parent_scope_mapping[p.first];
|
||||
if (target_var != StringName()) {
|
||||
ERR_CONTINUE_MSG(p_blackboard->get_parent() == nullptr, vformat("BlackboardPlan: Cannot link variable %s to parent scope because the parent scope is not set.", LimboUtility::get_singleton()->decorate_var(p.first)));
|
||||
ERR_CONTINUE_MSG(p_blackboard->get_parent().is_null(), vformat("BlackboardPlan: Cannot link variable %s to parent scope because the parent scope is not set.", LimboUtility::get_singleton()->decorate_var(p.first)));
|
||||
p_blackboard->link_var(p.first, p_blackboard->get_parent(), target_var);
|
||||
}
|
||||
} else if (is_bound) {
|
||||
|
|
|
@ -78,10 +78,10 @@ void BehaviorTree::copy_other(const Ref<BehaviorTree> &p_other) {
|
|||
}
|
||||
|
||||
Ref<BTInstance> BehaviorTree::instantiate(Node *p_agent, const Ref<Blackboard> &p_blackboard, Node *p_instance_owner, Node *p_custom_scene_root) const {
|
||||
ERR_FAIL_COND_V_MSG(root_task == nullptr, nullptr, "BehaviorTree: Instantiation failed - BT has no valid root task.");
|
||||
ERR_FAIL_COND_V_MSG(root_task.is_null(), nullptr, "BehaviorTree: Instantiation failed - BT has no valid root task.");
|
||||
ERR_FAIL_NULL_V_MSG(p_agent, nullptr, "BehaviorTree: Instantiation failed - agent can't be null.");
|
||||
ERR_FAIL_NULL_V_MSG(p_instance_owner, nullptr, "BehaviorTree: Instantiation failed -- instance owner can't be null.");
|
||||
ERR_FAIL_NULL_V_MSG(p_blackboard, nullptr, "BehaviorTree: Instantiation failed - blackboard can't be null.");
|
||||
ERR_FAIL_COND_V_MSG(p_blackboard.is_null(), nullptr, "BehaviorTree: Instantiation failed - blackboard can't be null.");
|
||||
Node *scene_root = p_custom_scene_root ? p_custom_scene_root : p_instance_owner->get_owner();
|
||||
ERR_FAIL_NULL_V_MSG(scene_root, nullptr, "BehaviorTree: Instantiation failed - unable to establish scene root. This is likely due to the instance owner not being owned by a scene node and custom_scene_root being null.");
|
||||
Ref<BTTask> root_copy = root_task->clone();
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
#endif
|
||||
|
||||
Ref<BTInstance> BTInstance::create(Ref<BTTask> p_root_task, String p_source_bt_path, Node *p_owner_node) {
|
||||
ERR_FAIL_NULL_V(p_root_task, nullptr);
|
||||
ERR_FAIL_COND_V(p_root_task.is_null(), nullptr);
|
||||
ERR_FAIL_NULL_V(p_owner_node, nullptr);
|
||||
Ref<BTInstance> inst;
|
||||
inst.instantiate();
|
||||
|
@ -103,7 +103,7 @@ double BTInstance::_get_mean_update_time_msec_and_reset() {
|
|||
|
||||
void BTInstance::_add_custom_monitor() {
|
||||
ERR_FAIL_NULL(get_owner_node());
|
||||
ERR_FAIL_NULL(root_task);
|
||||
ERR_FAIL_COND(root_task.is_null());
|
||||
ERR_FAIL_NULL(root_task->get_agent());
|
||||
|
||||
if (monitor_id == StringName()) {
|
||||
|
|
|
@ -98,7 +98,7 @@ void BTState::_update(double p_delta) {
|
|||
// Bail out if a transition happened in the meantime.
|
||||
return;
|
||||
}
|
||||
ERR_FAIL_NULL(bt_instance);
|
||||
ERR_FAIL_COND(bt_instance.is_null());
|
||||
BT::Status status = bt_instance->update(p_delta);
|
||||
if (status == BTTask::SUCCESS) {
|
||||
get_root()->dispatch(success_event, Variant());
|
||||
|
|
|
@ -30,7 +30,7 @@ PackedStringArray BTComment::get_configuration_warnings() {
|
|||
if (get_child_count_excluding_comments() > 0) {
|
||||
warnings.append("Can only have other comment tasks as children.");
|
||||
}
|
||||
if (get_parent() == nullptr) {
|
||||
if (get_parent().is_null()) {
|
||||
warnings.append("Can't be the root task.");
|
||||
}
|
||||
return warnings;
|
||||
|
|
|
@ -163,7 +163,7 @@ void BTTask::set_custom_name(const String &p_name) {
|
|||
|
||||
void BTTask::initialize(Node *p_agent, const Ref<Blackboard> &p_blackboard, Node *p_scene_root) {
|
||||
ERR_FAIL_NULL(p_agent);
|
||||
ERR_FAIL_NULL(p_blackboard);
|
||||
ERR_FAIL_COND(p_blackboard.is_null());
|
||||
ERR_FAIL_NULL(p_scene_root);
|
||||
data.agent = p_agent;
|
||||
data.blackboard = p_blackboard;
|
||||
|
|
|
@ -80,7 +80,7 @@ void BTCooldown::_chill() {
|
|||
timer->set_time_left(duration);
|
||||
} else {
|
||||
timer = SCENE_TREE()->create_timer(duration, process_pause);
|
||||
ERR_FAIL_NULL(timer);
|
||||
ERR_FAIL_COND(timer.is_null());
|
||||
timer->connect(LW_NAME(timeout), callable_mp(this, &BTCooldown::_on_timeout), CONNECT_ONE_SHOT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,16 +32,16 @@ void BTNewScope::set_blackboard_plan(const Ref<BlackboardPlan> &p_plan) {
|
|||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void BTNewScope::_set_parent_scope_plan_from_bt() {
|
||||
ERR_FAIL_NULL(get_blackboard_plan());
|
||||
ERR_FAIL_COND(get_blackboard_plan().is_null());
|
||||
Ref<BehaviorTree> bt = get_root()->editor_get_behavior_tree();
|
||||
ERR_FAIL_NULL(bt);
|
||||
get_blackboard_plan()->set_parent_scope_plan_provider(callable_mp(bt.ptr(), &BehaviorTree::get_blackboard_plan));
|
||||
ERR_FAIL_COND(bt.is_null());
|
||||
get_blackboard_plan()->set_parent_scope_plan_provider(Callable(bt.ptr(), "get_blackboard_plan"));
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
void BTNewScope::initialize(Node *p_agent, const Ref<Blackboard> &p_blackboard, Node *p_scene_root) {
|
||||
ERR_FAIL_COND(p_agent == nullptr);
|
||||
ERR_FAIL_COND(p_blackboard == nullptr);
|
||||
ERR_FAIL_COND(p_blackboard.is_null());
|
||||
|
||||
Ref<Blackboard> bb;
|
||||
if (blackboard_plan.is_valid()) {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
.. _create_tree::
|
||||
|
||||
Creating Behavior Trees
|
||||
=======================
|
||||
|
||||
This chapter describes how to create and debug behavior trees.
|
||||
|
||||
Add a Behavior Tree to an agent
|
||||
-------------------------------
|
||||
|
||||
Follow these steps to add a behavior tree to a new or existing agent:
|
||||
|
||||
1. Make a scene file for your agent, or open an existing scene.
|
||||
2. Add a :ref:`BTPlayer<class_BTPlayer>` node to your scene.
|
||||
3. Select :ref:`BTPlayer<class_BTPlayer>`, and create a new behavior tree in the inspector.
|
||||
4. Optionally, you can save the behavior tree to a file using the property's context menu.
|
||||
5. Click the behavior tree property to open it in the LimboAI editor.
|
||||
|
||||
Debugging Behavior Trees
|
||||
------------------------
|
||||
|
||||
In Godot Engine, follow to "Bottom Panel > Debugger > LimboAI" tab. With the LimboAI debugger,
|
||||
you can inspect any currently active behavior tree within the running project. The debugger can be detached
|
||||
from the main editor window, which can be particularly useful if you have a HiDPI or a secondary display.
|
|
@ -1,7 +1,7 @@
|
|||
.. _custom_tasks:
|
||||
|
||||
Creating custom tasks in GDScript
|
||||
=================================
|
||||
Creating custom tasks
|
||||
=====================
|
||||
|
||||
By default, user tasks should be placed in the ``res://ai/tasks``
|
||||
directory. You can set an alternative location for user tasks in the
|
||||
|
@ -145,3 +145,47 @@ Example 2: InRange condition
|
|||
return SUCCESS
|
||||
else:
|
||||
return FAILURE
|
||||
|
||||
.. _creating_tasks_in_c
|
||||
|
||||
Creating tasks in C#
|
||||
--------------------
|
||||
|
||||
You can use the following script template for custom tasks:
|
||||
|
||||
.. code:: csharp
|
||||
|
||||
using Godot;
|
||||
using System;
|
||||
|
||||
[Tool]
|
||||
public partial class _CLASS_ : _BASE_
|
||||
{
|
||||
public override string _GenerateName()
|
||||
{
|
||||
return "_CLASS_";
|
||||
}
|
||||
|
||||
public override void _Setup()
|
||||
{
|
||||
}
|
||||
|
||||
public override void _Enter()
|
||||
{
|
||||
}
|
||||
|
||||
public override void _Exit()
|
||||
{
|
||||
}
|
||||
|
||||
public override Status _Tick(double delta)
|
||||
{
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public override string[] _GetConfigurationWarnings()
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
|
@ -21,47 +21,3 @@ Each provided build comes with a GodotSharp folder, which has packages under
|
|||
Regarding GDExtension, I can only confirm success with the module version and C#.
|
||||
Unfortunately, I haven't had any luck with the GDExtension version yet.
|
||||
If you've had success with GDExtension, please let me know via Discord or email.
|
||||
|
||||
Creating custom tasks in C#
|
||||
---------------------------
|
||||
|
||||
**🛈 Note:** For more information, check out :ref:`creating custom tasks in GDScript <custom_tasks>`.
|
||||
|
||||
You can use the following script template for custom tasks:
|
||||
|
||||
.. code:: csharp
|
||||
|
||||
using Godot;
|
||||
using System;
|
||||
|
||||
[Tool]
|
||||
public partial class _CLASS_ : _BASE_
|
||||
{
|
||||
public override string _GenerateName()
|
||||
{
|
||||
return "_CLASS_";
|
||||
}
|
||||
|
||||
public override void _Setup()
|
||||
{
|
||||
}
|
||||
|
||||
public override void _Enter()
|
||||
{
|
||||
}
|
||||
|
||||
public override void _Exit()
|
||||
{
|
||||
}
|
||||
|
||||
public override Status _Tick(double delta)
|
||||
{
|
||||
return Status.Success;
|
||||
}
|
||||
|
||||
public override string[] _GetConfigurationWarnings()
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
.. _gdextension:
|
||||
|
||||
Using GDExtension
|
||||
=================
|
||||
|
||||
**🛈 See also:** `What is GDExtension? <https://docs.godotengine.org/en/stable/tutorials/scripting/gdextension/what_is_gdextension.html#what-is-gdextension>`_
|
||||
|
||||
LimboAI can be used as either a C++ module or as a GDExtension shared library.
|
||||
The module version is the most feature-full and slightly more performant, but
|
||||
it requires using custom engine builds including the export templates.
|
||||
|
||||
**🛈 Note:** Precompiled builds are available on the official
|
||||
`LimboAI GitHub <https://github.com/limbonaut/limboai#getting-limboai>`_ page.
|
||||
|
||||
GDExtension version is more convenient to use, as you don't need a custom engine
|
||||
build. You can simply download the extension and put it inside your project.
|
||||
However, it has certain limitations, described in detail in the next section.
|
||||
|
||||
Whichever you choose to use, remember, your project will stay compatible with
|
||||
both and you can transition from one to the other any time.
|
||||
|
||||
|
||||
Limitations of the GDExtension version
|
||||
--------------------------------------
|
||||
|
||||
GDExtension is the most convenient way of using the LimboAI plugin, but it comes
|
||||
with certain limitations.
|
||||
|
||||
* Built-in documentation is not available. The plugin will open online documentation instead when requested.
|
||||
* Documentation tooltips are not available.
|
||||
* Handy :ref:`class_BBParam` property editor is not available in the extension due to dependencies on the engine classes that are not available in the Godot API.
|
|
@ -0,0 +1,46 @@
|
|||
Getting LimboAI
|
||||
===============
|
||||
|
||||
LimboAI can be used as either a C++ module or as a GDExtension shared library.
|
||||
There are some differences between the two. In short, GDExtension version is more
|
||||
convenient to use but somewhat limited in features. The module version provides better editor
|
||||
experience and is slightly more performant, but it requires using custom engine builds including the export templates.
|
||||
Whichever you choose to use, your project will stay compatible with both and you can switch from one to
|
||||
the other any time.
|
||||
|
||||
Choose the version you'd like to use. If you're unsure, start with the GDExtension version.
|
||||
You can change your decision at any time - both versions are fully compatible.
|
||||
|
||||
Get GDExtension version
|
||||
------------------------
|
||||
|
||||
GDExtension is the most convenient way of using the LimboAI plugin, but it comes
|
||||
with certain limitations:
|
||||
|
||||
* Documentation tooltips are not available.
|
||||
* Handy :ref:`class_BBParam` property editor is not available in the extension due to dependencies on the engine classes that are not available in the Godot API.
|
||||
|
||||
**🛈 See also:** `What is GDExtension? <https://docs.godotengine.org/en/stable/tutorials/scripting/gdextension/what_is_gdextension.html#what-is-gdextension>`_
|
||||
|
||||
Installation instructions:
|
||||
|
||||
1. Make sure you're using the latest stable version of the Godot editor.
|
||||
2. Create a new project for your experiments with LimboAI.
|
||||
3. In Godot, click AssetLib tab at the top of the screen and search for LimboAI. Download it. LimboAI plugin will be downloaded with the demo project files. Don't mind the errors printed at this point, this is due to the extension library not being loaded just yet.
|
||||
4. Reload your project with `Project -> Reload project`. There shouldn't be any errors printed now.
|
||||
5. In the project files, locate a scene file called `showcase.tscn` and run it. It's the demo's entry point.
|
||||
|
||||
Get module version
|
||||
-------------------
|
||||
|
||||
Precompiled builds are available on the official
|
||||
`LimboAI GitHub <https://github.com/limbonaut/limboai#getting-limboai>`_ page,
|
||||
and in the Asset Library (coming soon!).
|
||||
|
||||
Installation instructions:
|
||||
|
||||
1. In `GitHub releases <https://github.com/limbonaut/limboai/releases/>`_, download the latest pre-compiled release build for your platform.
|
||||
2. Download the demo project archive from the same release.
|
||||
3. Extract the pre-compiled editor and the demo project files.
|
||||
4. Launch the pre-compiled editor binary, import and open the demo project.
|
||||
5. Run the project.
|
|
@ -1,8 +1,8 @@
|
|||
.. _hsm:
|
||||
|
||||
|
||||
State Machines
|
||||
==============
|
||||
Create State Machines
|
||||
=====================
|
||||
|
||||
This guide will show how to set up and use a state machine using :ref:`LimboHSM<class_LimboHSM>`.
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
LimboAI Documentation
|
||||
=====================
|
||||
|
||||
About
|
||||
-----
|
||||
|
||||
**LimboAI** is an open-source C++ module for **Godot Engine 4** providing a combination of
|
||||
**Behavior Trees** and **State Machines** for crafting your game’s AI. It comes with a
|
||||
behavior tree editor, built-in documentation, visual debugger, and more! While
|
||||
|
@ -18,86 +15,42 @@ of agents in a game (e.g., characters, enemies, entities). They are designed to
|
|||
make it easier to create complex and highly modular behaviors for your games.
|
||||
To learn more about behavior trees, check out :ref:`introduction`.
|
||||
|
||||
|
||||
Getting LimboAI
|
||||
---------------
|
||||
|
||||
Precompiled builds are available on the official
|
||||
`LimboAI GitHub <https://github.com/limbonaut/limboai#getting-limboai>`_ page,
|
||||
and in the Asset Library (coming soon!).
|
||||
|
||||
LimboAI can be used as either a C++ module or as a GDExtension shared library.
|
||||
There are some differences between the two. In short, GDExtension version is more
|
||||
convenient to use but somewhat limited in features. Whichever you choose to use,
|
||||
your project will stay compatible with both and you can switch from one to
|
||||
the other any time. For more information on this topic, see :ref:`gdextension`.
|
||||
|
||||
**🛈 Note:** Class reference is available in the side bar.
|
||||
|
||||
|
||||
First steps
|
||||
-----------
|
||||
|
||||
Choose the version you'd like to use. The module version provides better editor
|
||||
experience and performance, while the GDExtension version is more convenient to use.
|
||||
If you're unsure, start with the GDExtension version.
|
||||
You can change your decision at any time - both versions are fully compatible.
|
||||
For more information, see :ref:`gdextension`.
|
||||
|
||||
With GDExtension version
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
1. Make sure you're using the latest stable version of the Godot editor.
|
||||
2. Create a new project for your experiments with LimboAI.
|
||||
3. In Godot, click AssetLib tab at the top of the screen and search for LimboAI. Download it. LimboAI plugin will be downloaded with the demo project files. Don't mind the errors printed at this point, this is due to the extension library not being loaded just yet.
|
||||
4. Reload your project with `Project -> Reload project`. There shouldn't be any errors printed now.
|
||||
5. In the project files, locate a scene file called `showcase.tscn` and run it. It's the demo's entry point.
|
||||
|
||||
With module version
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
1. In `GitHub releases <https://github.com/limbonaut/limboai/releases/>`_, download the latest pre-compiled release build for your platform.
|
||||
2. Download the demo project archive from the same release.
|
||||
3. Extract the pre-compiled editor and the demo project files.
|
||||
4. Launch the pre-compiled editor binary, import and open the demo project.
|
||||
5. Run the project.
|
||||
|
||||
Creating your own behavior trees
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
1. Make a scene file for your agent, or open an existing scene.
|
||||
2. Add a :ref:`BTPlayer<class_BTPlayer>` node to your scene.
|
||||
3. Select :ref:`BTPlayer<class_BTPlayer>`, and create a new behavior tree in the inspector.
|
||||
4. Optionally, you can save the behavior tree to a file using the property's context menu.
|
||||
5. Click the behavior tree property to open it in the LimboAI editor.
|
||||
|
||||
**Hierarchical State Machines** are finite state machines that allow any state to host their own
|
||||
sub-state machine. This allows you to tackle your AI's state and transition complexity by breaking down
|
||||
one big state machine into multiple smaller ones.
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: Getting started
|
||||
|
||||
getting-started/introduction
|
||||
getting-started/custom-tasks
|
||||
getting-started/using-blackboard
|
||||
getting-started/accessing-nodes
|
||||
getting-started/hsm
|
||||
getting-started/gdextension
|
||||
getting-started/getting-limboai
|
||||
getting-started/c-sharp
|
||||
getting-started/featured-classes
|
||||
getting-started/contributing
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: Behavior Trees
|
||||
|
||||
behavior-trees/introduction
|
||||
behavior-trees/create-tree
|
||||
behavior-trees/custom-tasks
|
||||
behavior-trees/using-blackboard
|
||||
behavior-trees/accessing-nodes
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: Hierarchical State MachineS
|
||||
|
||||
hierarchical-state-machines/create-hsm
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: Class reference
|
||||
:glob:
|
||||
|
||||
classes/featured-classes
|
||||
classes/class_*
|
||||
|
||||
Debugging behavior trees
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In Godot Engine, follow to "Bottom Panel > Debugger > LimboAI" tab. With the LimboAI debugger,
|
||||
you can inspect any currently active behavior tree within the running project. The debugger can be detached
|
||||
from the main editor window, which can be particularly useful if you have a HiDPI or a secondary display.
|
||||
|
|
|
@ -46,7 +46,7 @@ LineEdit *BlackboardPlanEditor::_get_name_edit(int p_row_index) const {
|
|||
}
|
||||
|
||||
void BlackboardPlanEditor::_add_var() {
|
||||
ERR_FAIL_NULL(plan);
|
||||
ERR_FAIL_COND(plan.is_null());
|
||||
|
||||
int suffix = 1;
|
||||
StringName var_name = default_var_name == StringName() ? "var" : default_var_name;
|
||||
|
@ -65,14 +65,14 @@ void BlackboardPlanEditor::_add_var() {
|
|||
}
|
||||
|
||||
void BlackboardPlanEditor::_trash_var(int p_index) {
|
||||
ERR_FAIL_NULL(plan);
|
||||
ERR_FAIL_COND(plan.is_null());
|
||||
StringName var_name = plan->get_var_by_index(p_index).first;
|
||||
plan->remove_var(var_name);
|
||||
_refresh();
|
||||
}
|
||||
|
||||
void BlackboardPlanEditor::_rename_var(const StringName &p_new_name, int p_index) {
|
||||
ERR_FAIL_NULL(plan);
|
||||
ERR_FAIL_COND(plan.is_null());
|
||||
|
||||
LineEdit *name_edit = _get_name_edit(p_index);
|
||||
ERR_FAIL_NULL(name_edit);
|
||||
|
@ -96,7 +96,7 @@ void BlackboardPlanEditor::_rename_var(const StringName &p_new_name, int p_index
|
|||
}
|
||||
|
||||
void BlackboardPlanEditor::_change_var_type(Variant::Type p_new_type, int p_index) {
|
||||
ERR_FAIL_NULL(plan);
|
||||
ERR_FAIL_COND(plan.is_null());
|
||||
|
||||
BBVariable var = plan->get_var_by_index(p_index).second;
|
||||
if (var.get_type() == p_new_type) {
|
||||
|
@ -115,14 +115,14 @@ void BlackboardPlanEditor::_change_var_type(Variant::Type p_new_type, int p_inde
|
|||
}
|
||||
|
||||
void BlackboardPlanEditor::_change_var_hint(PropertyHint p_new_hint, int p_index) {
|
||||
ERR_FAIL_NULL(plan);
|
||||
ERR_FAIL_COND(plan.is_null());
|
||||
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);
|
||||
ERR_FAIL_COND(plan.is_null());
|
||||
plan->get_var_by_index(p_index).second.set_hint_string(p_new_hint_string);
|
||||
plan->notify_property_list_changed();
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ void BlackboardPlanEditor::_refresh() {
|
|||
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->set_button_icon(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));
|
||||
|
@ -297,7 +297,7 @@ void BlackboardPlanEditor::_refresh() {
|
|||
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_button_icon(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);
|
||||
|
@ -326,7 +326,7 @@ void BlackboardPlanEditor::_refresh() {
|
|||
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->set_button_icon(theme_cache.trash_icon);
|
||||
trash_button->connect(LW_NAME(pressed), callable_mp(this, &BlackboardPlanEditor::_trash_var).bind(i));
|
||||
}
|
||||
}
|
||||
|
@ -337,7 +337,7 @@ void BlackboardPlanEditor::_notification(int p_what) {
|
|||
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)));
|
||||
add_var_tool->set_button_icon(get_theme_icon(LW_NAME(Add), LW_NAME(EditorIcons)));
|
||||
|
||||
type_menu->clear();
|
||||
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
|
||||
|
@ -472,14 +472,14 @@ BlackboardPlanEditor::BlackboardPlanEditor() {
|
|||
// ***** EditorInspectorPluginBBPlan *****
|
||||
|
||||
void EditorInspectorPluginBBPlan::_edit_plan(const Ref<BlackboardPlan> &p_plan) {
|
||||
ERR_FAIL_NULL(p_plan);
|
||||
ERR_FAIL_COND(p_plan.is_null());
|
||||
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());
|
||||
ERR_FAIL_COND(p_plan.is_null());
|
||||
ERR_FAIL_COND(p_plan->get_base_plan().is_null());
|
||||
EditorInterface::get_singleton()->call_deferred("edit_resource", p_plan->get_base_plan());
|
||||
}
|
||||
|
||||
|
@ -501,7 +501,7 @@ void EditorInspectorPluginBBPlan::parse_begin(Object *p_object) {
|
|||
void EditorInspectorPluginBBPlan::_parse_begin(Object *p_object) {
|
||||
#endif
|
||||
Ref<BlackboardPlan> plan = Object::cast_to<BlackboardPlan>(p_object);
|
||||
ERR_FAIL_NULL(plan);
|
||||
ERR_FAIL_COND(plan.is_null());
|
||||
|
||||
PanelContainer *panel = memnew(PanelContainer);
|
||||
ADD_STYLEBOX_OVERRIDE(panel, LW_NAME(panel), toolbar_style);
|
||||
|
@ -522,7 +522,7 @@ void EditorInspectorPluginBBPlan::_parse_begin(Object *p_object) {
|
|||
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->set_button_icon(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);
|
||||
|
@ -530,7 +530,7 @@ void EditorInspectorPluginBBPlan::_parse_begin(Object *p_object) {
|
|||
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->set_button_icon(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));
|
||||
}
|
||||
|
||||
|
|
|
@ -209,7 +209,7 @@ void LimboDebuggerTab::_notification(int p_what) {
|
|||
} break;
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
alert_icon->set_texture(get_theme_icon(LW_NAME(StatusWarning), LW_NAME(EditorIcons)));
|
||||
BUTTON_SET_ICON(resource_header, LimboUtility::get_singleton()->get_task_icon("BehaviorTree"));
|
||||
resource_header->set_button_icon(LimboUtility::get_singleton()->get_task_icon("BehaviorTree"));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -287,14 +287,14 @@ void EditorPropertyBBParam::update_property() {
|
|||
variable_editor->update_property();
|
||||
variable_editor->show();
|
||||
bottom_container->hide();
|
||||
type_choice->set_icon(get_editor_theme_icon(SNAME("LimboExtraVariable")));
|
||||
type_choice->set_button_icon(get_editor_theme_icon(SNAME("LimboExtraVariable")));
|
||||
} else {
|
||||
_create_value_editor(param->get_type());
|
||||
variable_editor->hide();
|
||||
value_editor->show();
|
||||
value_editor->set_object_and_property(param.ptr(), SNAME("saved_value"));
|
||||
value_editor->update_property();
|
||||
type_choice->set_icon(get_editor_theme_icon(Variant::get_type_name(param->get_type())));
|
||||
type_choice->set_button_icon(get_editor_theme_icon(Variant::get_type_name(param->get_type())));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,7 +316,7 @@ void EditorPropertyBBParam::_notification(int p_what) {
|
|||
|
||||
{
|
||||
String type = Variant::get_type_name(_get_edited_param()->get_type());
|
||||
type_choice->set_icon(get_editor_theme_icon(type));
|
||||
type_choice->set_button_icon(get_editor_theme_icon(type));
|
||||
}
|
||||
|
||||
// Initialize type choice.
|
||||
|
|
|
@ -156,7 +156,7 @@ void EditorPropertyPropertyPath::_notification(int p_what) {
|
|||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
BUTTON_SET_ICON(action_menu, get_theme_icon(LW_NAME(GuiTabMenuHl), LW_NAME(EditorIcons)));
|
||||
action_menu->set_button_icon(get_theme_icon(LW_NAME(GuiTabMenuHl), LW_NAME(EditorIcons)));
|
||||
action_menu->get_popup()->set_item_icon(ACTION_CLEAR, get_theme_icon(LW_NAME(Clear), LW_NAME(EditorIcons)));
|
||||
action_menu->get_popup()->set_item_icon(ACTION_COPY, get_theme_icon(LW_NAME(ActionCopy), LW_NAME(EditorIcons)));
|
||||
action_menu->get_popup()->set_item_icon(ACTION_EDIT, get_theme_icon(LW_NAME(Edit), LW_NAME(EditorIcons)));
|
||||
|
|
|
@ -36,7 +36,7 @@ int EditorPropertyVariableName::last_caret_column = 0;
|
|||
//***** EditorPropertyVariableName
|
||||
|
||||
void EditorPropertyVariableName::_show_variables_popup() {
|
||||
ERR_FAIL_NULL(plan);
|
||||
ERR_FAIL_COND(plan.is_null());
|
||||
|
||||
variables_popup->clear();
|
||||
variables_popup->reset_size();
|
||||
|
@ -86,30 +86,30 @@ void EditorPropertyVariableName::_update_status() {
|
|||
}
|
||||
String var_name = name_edit->get_text();
|
||||
if (var_name.is_empty() && allow_empty) {
|
||||
BUTTON_SET_ICON(status_btn, theme_cache.var_empty_icon);
|
||||
status_btn->set_button_icon(theme_cache.var_empty_icon);
|
||||
status_btn->set_tooltip_text(TTR("Variable name not specified.\nClick to open the blackboard plan."));
|
||||
} else if (plan->has_var(var_name)) {
|
||||
if (expected_type == Variant::NIL || plan->get_var(var_name).get_type() == expected_type) {
|
||||
BUTTON_SET_ICON(status_btn, theme_cache.var_exists_icon);
|
||||
status_btn->set_button_icon(theme_cache.var_exists_icon);
|
||||
status_btn->set_tooltip_text(TTR("This variable is present in the blackboard plan.\nClick to open the blackboard plan."));
|
||||
} else {
|
||||
BUTTON_SET_ICON(status_btn, theme_cache.var_error_icon);
|
||||
status_btn->set_button_icon(theme_cache.var_error_icon);
|
||||
status_btn->set_tooltip_text(TTR(vformat(
|
||||
"The %s variable in the blackboard plan should be of type %s.\nClick to open the blackboard plan.",
|
||||
LimboUtility::get_singleton()->decorate_var(var_name),
|
||||
Variant::get_type_name(expected_type))));
|
||||
}
|
||||
} else if (name_edit->get_text().begins_with("_")) {
|
||||
BUTTON_SET_ICON(status_btn, theme_cache.var_private_icon);
|
||||
status_btn->set_button_icon(theme_cache.var_private_icon);
|
||||
status_btn->set_tooltip_text(TTR("This variable is private and is not included in the blackboard plan.\nClick to open the blackboard plan."));
|
||||
} else {
|
||||
BUTTON_SET_ICON(status_btn, theme_cache.var_not_found_icon);
|
||||
status_btn->set_button_icon(theme_cache.var_not_found_icon);
|
||||
status_btn->set_tooltip_text(TTR("No matching variable found in the blackboard plan!\nClick to open the blackboard plan."));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPropertyVariableName::_status_pressed() {
|
||||
ERR_FAIL_NULL(plan);
|
||||
ERR_FAIL_COND(plan.is_null());
|
||||
if (!plan->has_var(name_edit->get_text())) {
|
||||
BlackboardPlanEditor::get_singleton()->set_defaults(name_edit->get_text(),
|
||||
expected_type == Variant::NIL ? Variant::FLOAT : expected_type,
|
||||
|
@ -120,14 +120,14 @@ void EditorPropertyVariableName::_status_pressed() {
|
|||
}
|
||||
|
||||
void EditorPropertyVariableName::_status_mouse_entered() {
|
||||
ERR_FAIL_NULL(plan);
|
||||
ERR_FAIL_COND(plan.is_null());
|
||||
if (!plan->has_var(name_edit->get_text())) {
|
||||
BUTTON_SET_ICON(status_btn, theme_cache.var_add_icon);
|
||||
status_btn->set_button_icon(theme_cache.var_add_icon);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPropertyVariableName::_status_mouse_exited() {
|
||||
ERR_FAIL_NULL(plan);
|
||||
ERR_FAIL_COND(plan.is_null());
|
||||
_update_status();
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ void EditorPropertyVariableName::_notification(int p_what) {
|
|||
}
|
||||
} break;
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
BUTTON_SET_ICON(drop_btn, get_theme_icon(LW_NAME(GuiOptionArrow), LW_NAME(EditorIcons)));
|
||||
drop_btn->set_button_icon(get_theme_icon(LW_NAME(GuiOptionArrow), LW_NAME(EditorIcons)));
|
||||
theme_cache.var_add_icon = LimboUtility::get_singleton()->get_task_icon(LW_NAME(LimboVarAdd));
|
||||
theme_cache.var_exists_icon = LimboUtility::get_singleton()->get_task_icon(LW_NAME(LimboVarExists));
|
||||
theme_cache.var_not_found_icon = LimboUtility::get_singleton()->get_task_icon(LW_NAME(LimboVarNotFound));
|
||||
|
@ -262,7 +262,7 @@ bool EditorInspectorPluginVariableName::_parse_property(Object *p_object, const
|
|||
Variant default_value;
|
||||
if (is_mapping) {
|
||||
plan.reference_ptr(Object::cast_to<BlackboardPlan>(p_object));
|
||||
ERR_FAIL_NULL_V(plan, false);
|
||||
ERR_FAIL_COND_V(plan.is_null(), false);
|
||||
String var_name = p_path.trim_prefix("mapping/");
|
||||
if (plan->has_var(var_name)) {
|
||||
BBVariable variable = plan->get_var(var_name);
|
||||
|
@ -277,7 +277,7 @@ bool EditorInspectorPluginVariableName::_parse_property(Object *p_object, const
|
|||
plan = parent_plan;
|
||||
}
|
||||
}
|
||||
ERR_FAIL_NULL_V(plan, false);
|
||||
ERR_FAIL_COND_V(plan.is_null(), false);
|
||||
} else {
|
||||
plan = editor_plan_provider.call();
|
||||
}
|
||||
|
|
|
@ -200,7 +200,7 @@ void LimboAIEditor::_remove_task(const Ref<BTTask> &p_task) {
|
|||
ERR_FAIL_COND(p_task.is_null());
|
||||
ERR_FAIL_COND(task_tree->get_bt().is_null());
|
||||
EditorUndoRedoManager *undo_redo = _new_undo_redo_action(TTR("Remove BT Task"));
|
||||
if (p_task->get_parent() == nullptr) {
|
||||
if (p_task->get_parent().is_null()) {
|
||||
ERR_FAIL_COND(task_tree->get_bt()->get_root_task() != p_task);
|
||||
undo_redo->add_do_method(task_tree->get_bt().ptr(), LW_NAME(set_root_task), Variant());
|
||||
undo_redo->add_undo_method(task_tree->get_bt().ptr(), LW_NAME(set_root_task), task_tree->get_bt()->get_root_task());
|
||||
|
@ -746,7 +746,7 @@ void LimboAIEditor::_action_selected(int p_id) {
|
|||
|
||||
void LimboAIEditor::_on_probability_edited(double p_value) {
|
||||
Ref<BTTask> selected = task_tree->get_selected();
|
||||
ERR_FAIL_COND(selected == nullptr);
|
||||
ERR_FAIL_COND(selected.is_null());
|
||||
Ref<BTProbabilitySelector> probability_selector = selected->get_parent();
|
||||
ERR_FAIL_COND(probability_selector.is_null());
|
||||
if (percent_mode->is_pressed()) {
|
||||
|
@ -1241,7 +1241,7 @@ void LimboAIEditor::_tab_menu_option_selected(int p_id) {
|
|||
} break;
|
||||
case TAB_JUMP_TO_OWNER: {
|
||||
Ref<BehaviorTree> bt = history[idx_history];
|
||||
ERR_FAIL_NULL(bt);
|
||||
ERR_FAIL_COND(bt.is_null());
|
||||
String bt_path = bt->get_path();
|
||||
if (!bt_path.is_empty()) {
|
||||
owner_picker->pick_and_open_owner_of_resource(bt_path);
|
||||
|
@ -1359,7 +1359,7 @@ void LimboAIEditor::_update_favorite_tasks() {
|
|||
}
|
||||
btn->set_text(task_name);
|
||||
btn->set_meta(LW_NAME(task_meta), task_meta);
|
||||
BUTTON_SET_ICON(btn, LimboUtility::get_singleton()->get_task_icon(task_meta));
|
||||
btn->set_button_icon(LimboUtility::get_singleton()->get_task_icon(task_meta));
|
||||
btn->set_tooltip_text(vformat(TTR("Add %s task."), task_name));
|
||||
btn->set_flat(true);
|
||||
btn->add_theme_constant_override(LW_NAME(icon_max_width), 16 * EDSCALE); // Force user icons to be of the proper size.
|
||||
|
@ -1530,11 +1530,11 @@ void LimboAIEditor::_notification(int p_what) {
|
|||
|
||||
ADD_STYLEBOX_OVERRIDE(tab_bar_panel, "panel", get_theme_stylebox("tabbar_background", "TabContainer"));
|
||||
|
||||
BUTTON_SET_ICON(new_btn, get_theme_icon(LW_NAME(New), LW_NAME(EditorIcons)));
|
||||
BUTTON_SET_ICON(load_btn, get_theme_icon(LW_NAME(Load), LW_NAME(EditorIcons)));
|
||||
BUTTON_SET_ICON(save_btn, get_theme_icon(LW_NAME(Save), LW_NAME(EditorIcons)));
|
||||
BUTTON_SET_ICON(new_script_btn, get_theme_icon(LW_NAME(ScriptCreate), LW_NAME(EditorIcons)));
|
||||
BUTTON_SET_ICON(misc_btn, get_theme_icon(LW_NAME(Tools), LW_NAME(EditorIcons)));
|
||||
new_btn->set_button_icon(get_theme_icon(LW_NAME(New), LW_NAME(EditorIcons)));
|
||||
load_btn->set_button_icon(get_theme_icon(LW_NAME(Load), LW_NAME(EditorIcons)));
|
||||
save_btn->set_button_icon(get_theme_icon(LW_NAME(Save), LW_NAME(EditorIcons)));
|
||||
new_script_btn->set_button_icon(get_theme_icon(LW_NAME(ScriptCreate), LW_NAME(EditorIcons)));
|
||||
misc_btn->set_button_icon(get_theme_icon(LW_NAME(Tools), LW_NAME(EditorIcons)));
|
||||
|
||||
_update_favorite_tasks();
|
||||
} break;
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "core/object/class_db.h"
|
||||
#include "core/object/object.h"
|
||||
#include "core/templates/hash_set.h"
|
||||
#include "editor/editor_main_screen.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_undo_redo_manager.h"
|
||||
#include "editor/gui/editor_spin_slider.h"
|
||||
|
@ -42,6 +43,7 @@
|
|||
#include "scene/gui/popup.h"
|
||||
#include "scene/gui/popup_menu.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/tab_bar.h"
|
||||
#include "scene/gui/tree.h"
|
||||
#include "scene/resources/texture.h"
|
||||
#endif // LIMBOAI_MODULE
|
||||
|
@ -295,7 +297,7 @@ public:
|
|||
#ifdef LIMBOAI_MODULE
|
||||
bool has_main_screen() const override { return true; }
|
||||
|
||||
virtual String get_name() const override { return "LimboAI"; }
|
||||
virtual String get_plugin_name() const override { return "LimboAI"; }
|
||||
virtual void make_visible(bool p_visible) override;
|
||||
virtual void edit(Object *p_object) override;
|
||||
virtual bool handles(Object *p_object) const override;
|
||||
|
|
|
@ -44,7 +44,7 @@ private:
|
|||
|
||||
_FORCE_INLINE_ void _set_mode_by_index(int p_index) {
|
||||
current_mode_index = p_index;
|
||||
BUTTON_SET_ICON(this, modes[current_mode_index].icon);
|
||||
this->set_button_icon(modes[current_mode_index].icon);
|
||||
if (!modes[current_mode_index].tooltip.is_empty()) {
|
||||
set_tooltip_text(modes[current_mode_index].tooltip);
|
||||
}
|
||||
|
|
|
@ -68,9 +68,6 @@ Control *TaskButton::_do_make_tooltip() const {
|
|||
help_symbol = "class|" + task_meta + "|";
|
||||
}
|
||||
|
||||
EditorHelpBit *help_bit = memnew(EditorHelpBit(help_symbol));
|
||||
help_bit->set_content_height_limits(1, 360 * EDSCALE);
|
||||
|
||||
String desc = _module_get_help_description(task_meta);
|
||||
if (desc.is_empty() && is_resource) {
|
||||
// ! HACK: Force documentation parsing.
|
||||
|
@ -84,14 +81,10 @@ Control *TaskButton::_do_make_tooltip() const {
|
|||
desc = _module_get_help_description(task_meta);
|
||||
}
|
||||
}
|
||||
if (desc.is_empty() && help_bit->get_description().is_empty()) {
|
||||
if (desc.is_empty()) {
|
||||
desc = "[i]" + TTR("No description.") + "[/i]";
|
||||
}
|
||||
if (!desc.is_empty()) {
|
||||
help_bit->set_description(desc);
|
||||
}
|
||||
|
||||
EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<TaskButton *>(this));
|
||||
EditorHelpBitTooltip::show_tooltip(const_cast<TaskButton *>(this), help_symbol, desc);
|
||||
#endif // LIMBOAI_MODULE
|
||||
|
||||
#ifdef LIMBOAI_GDEXTENSION
|
||||
|
@ -184,7 +177,7 @@ void TaskPaletteSection::set_filter(String p_filter_text) {
|
|||
void TaskPaletteSection::add_task_button(const String &p_name, const Ref<Texture> &icon, const String &p_meta) {
|
||||
TaskButton *btn = memnew(TaskButton);
|
||||
btn->set_text(p_name);
|
||||
BUTTON_SET_ICON(btn, icon);
|
||||
btn->set_button_icon(icon);
|
||||
btn->set_tooltip_text("dummy_text"); // Force tooltip to be shown.
|
||||
btn->set_task_meta(p_meta);
|
||||
btn->add_theme_constant_override(LW_NAME(icon_max_width), 16 * EDSCALE); // Force user icons to be of the proper size.
|
||||
|
@ -195,7 +188,7 @@ void TaskPaletteSection::add_task_button(const String &p_name, const Ref<Texture
|
|||
|
||||
void TaskPaletteSection::set_collapsed(bool p_collapsed) {
|
||||
tasks_container->set_visible(!p_collapsed);
|
||||
BUTTON_SET_ICON(section_header, (p_collapsed ? theme_cache.arrow_right_icon : theme_cache.arrow_down_icon));
|
||||
section_header->set_button_icon((p_collapsed ? theme_cache.arrow_right_icon : theme_cache.arrow_down_icon));
|
||||
}
|
||||
|
||||
bool TaskPaletteSection::is_collapsed() const {
|
||||
|
@ -214,7 +207,7 @@ void TaskPaletteSection::_notification(int p_what) {
|
|||
} break;
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
_do_update_theme_item_cache();
|
||||
BUTTON_SET_ICON(section_header, (is_collapsed() ? theme_cache.arrow_right_icon : theme_cache.arrow_down_icon));
|
||||
section_header->set_button_icon((is_collapsed() ? theme_cache.arrow_right_icon : theme_cache.arrow_down_icon));
|
||||
section_header->add_theme_font_override(LW_NAME(font), get_theme_font(LW_NAME(bold), LW_NAME(EditorFonts)));
|
||||
} break;
|
||||
}
|
||||
|
@ -600,13 +593,13 @@ void TaskPalette::_notification(int p_what) {
|
|||
case NOTIFICATION_THEME_CHANGED: {
|
||||
_do_update_theme_item_cache();
|
||||
|
||||
BUTTON_SET_ICON(tool_filters, get_theme_icon(LW_NAME(AnimationFilter), LW_NAME(EditorIcons)));
|
||||
BUTTON_SET_ICON(tool_refresh, get_theme_icon(LW_NAME(Reload), LW_NAME(EditorIcons)));
|
||||
tool_filters->set_button_icon(get_theme_icon(LW_NAME(AnimationFilter), LW_NAME(EditorIcons)));
|
||||
tool_refresh->set_button_icon(get_theme_icon(LW_NAME(Reload), LW_NAME(EditorIcons)));
|
||||
|
||||
filter_edit->set_right_icon(get_theme_icon(LW_NAME(Search), LW_NAME(EditorIcons)));
|
||||
|
||||
BUTTON_SET_ICON(select_all, LimboUtility::get_singleton()->get_task_icon("LimboSelectAll"));
|
||||
BUTTON_SET_ICON(deselect_all, LimboUtility::get_singleton()->get_task_icon("LimboDeselectAll"));
|
||||
select_all->set_button_icon(LimboUtility::get_singleton()->get_task_icon("LimboSelectAll"));
|
||||
deselect_all->set_button_icon(LimboUtility::get_singleton()->get_task_icon("LimboDeselectAll"));
|
||||
|
||||
category_choice->queue_redraw();
|
||||
|
||||
|
|
|
@ -192,7 +192,7 @@ void TaskTree::_on_item_collapsed(Object *p_obj) {
|
|||
}
|
||||
|
||||
Ref<BTTask> task = item->get_metadata(0);
|
||||
ERR_FAIL_NULL(task);
|
||||
ERR_FAIL_COND(task.is_null());
|
||||
task->set_display_collapsed(item->is_collapsed());
|
||||
}
|
||||
|
||||
|
@ -574,7 +574,7 @@ void TaskTree::_bind_methods() {
|
|||
|
||||
// TreeSearch API
|
||||
void TaskTree::tree_search_show_and_focus() {
|
||||
ERR_FAIL_NULL(tree_search);
|
||||
ERR_FAIL_COND(tree_search.is_null());
|
||||
tree_search_panel->set_visible(true);
|
||||
tree_search_panel->focus_editor();
|
||||
}
|
||||
|
@ -587,7 +587,7 @@ TreeSearch::SearchInfo TaskTree::tree_search_get_search_info() const {
|
|||
}
|
||||
|
||||
void TaskTree::tree_search_set_search_info(const TreeSearch::SearchInfo &p_search_info) {
|
||||
ERR_FAIL_NULL(tree_search);
|
||||
ERR_FAIL_COND(tree_search.is_null());
|
||||
tree_search_panel->set_search_info(p_search_info);
|
||||
}
|
||||
|
||||
|
|
|
@ -158,7 +158,7 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, const Rect2 p_rect,
|
|||
if (font.is_null()) {
|
||||
font = p_tree_item->get_tree()->get_theme_font(LW_NAME(font));
|
||||
}
|
||||
ERR_FAIL_NULL(font);
|
||||
ERR_FAIL_COND(font.is_null());
|
||||
float font_size = p_tree_item->get_custom_font_size(0);
|
||||
if (font_size == -1) {
|
||||
font_size = p_tree_item->get_tree()->get_theme_font_size(LW_NAME(font));
|
||||
|
@ -176,7 +176,7 @@ void TreeSearch::_draw_highlight_item(TreeItem *p_tree_item, const Rect2 p_rect,
|
|||
|
||||
// Stylebox
|
||||
Ref<StyleBox> stylebox = p_tree_item->get_tree()->get_theme_stylebox(LW_NAME(Focus));
|
||||
ERR_FAIL_NULL(stylebox);
|
||||
ERR_FAIL_COND(stylebox.is_null());
|
||||
|
||||
// Extract separation
|
||||
float h_sep = p_tree_item->get_tree()->get_theme_constant(LW_NAME(h_separation));
|
||||
|
@ -559,9 +559,9 @@ void TreeSearchPanel::_notification(int p_what) {
|
|||
break;
|
||||
}
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
BUTTON_SET_ICON(close_button, get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons)));
|
||||
BUTTON_SET_ICON(find_prev_button, get_theme_icon("MoveUp", LW_NAME(EditorIcons)));
|
||||
BUTTON_SET_ICON(find_next_button, get_theme_icon("MoveDown", LW_NAME(EditorIcons)));
|
||||
close_button->set_button_icon(get_theme_icon(LW_NAME(Close), LW_NAME(EditorIcons)));
|
||||
find_prev_button->set_button_icon(get_theme_icon("MoveUp", LW_NAME(EditorIcons)));
|
||||
find_next_button->set_button_icon(get_theme_icon("MoveDown", LW_NAME(EditorIcons)));
|
||||
label_filter->set_text(TTR("Filter"));
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ void LimboState::set_blackboard_plan(const Ref<BlackboardPlan> &p_plan) {
|
|||
blackboard_plan = p_plan;
|
||||
|
||||
if (Engine::get_singleton()->is_editor_hint() && blackboard_plan.is_valid()) {
|
||||
blackboard_plan->set_parent_scope_plan_provider(callable_mp(this, &LimboState::_get_parent_scope_plan));
|
||||
blackboard_plan->set_parent_scope_plan_provider(Callable(this, "_get_parent_scope_plan"));
|
||||
}
|
||||
|
||||
_update_blackboard_plan();
|
||||
|
@ -213,6 +213,8 @@ void LimboState::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_blackboard_plan", "plan"), &LimboState::set_blackboard_plan);
|
||||
ClassDB::bind_method(D_METHOD("get_blackboard_plan"), &LimboState::get_blackboard_plan);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_get_parent_scope_plan"), &LimboState::_get_parent_scope_plan);
|
||||
|
||||
GDVIRTUAL_BIND(_setup);
|
||||
GDVIRTUAL_BIND(_enter);
|
||||
GDVIRTUAL_BIND(_exit);
|
||||
|
|
|
@ -78,7 +78,7 @@ TEST_CASE("[Modules][LimboAI] BTTask") {
|
|||
SUBCASE("Test next_sibling()") {
|
||||
CHECK(child1->next_sibling() == child2);
|
||||
CHECK(child2->next_sibling() == child3);
|
||||
CHECK(child3->next_sibling() == nullptr);
|
||||
CHECK(child3->next_sibling().is_null());
|
||||
}
|
||||
SUBCASE("Test remove_child()") {
|
||||
task->remove_child(child2);
|
||||
|
@ -153,7 +153,7 @@ TEST_CASE("[Modules][LimboAI] BTTask") {
|
|||
CHECK(child3->get_root() == task);
|
||||
}
|
||||
SUBCASE("Test get_parent()") {
|
||||
CHECK(task->get_parent() == nullptr);
|
||||
CHECK(task->get_parent().is_null());
|
||||
CHECK(child1->get_parent() == task);
|
||||
CHECK(child2->get_parent() == task);
|
||||
CHECK(child2->get_parent() == task);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#ifdef TOOLS_ENABLED
|
||||
#include "core/io/resource.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "editor/editor_main_screen.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/plugins/script_editor_plugin.h"
|
||||
#endif // TOOLS_ENABLED
|
||||
|
@ -213,7 +214,7 @@ Variant VARIANT_DEFAULT(Variant::Type p_type) {
|
|||
void SHOW_BUILTIN_DOC(const String &p_topic) {
|
||||
#ifdef LIMBOAI_MODULE
|
||||
ScriptEditor::get_singleton()->goto_help(p_topic);
|
||||
EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
|
||||
EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
|
||||
#elif LIMBOAI_GDEXTENSION
|
||||
TypedArray<ScriptEditorBase> open_editors = EditorInterface::get_singleton()->get_script_editor()->get_open_script_editors();
|
||||
ERR_FAIL_COND_MSG(open_editors.size() == 0, "Can't open help page. Need at least one script open in the script editor.");
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
#define EDITOR_FILE_SYSTEM() (EditorFileSystem::get_singleton())
|
||||
#define EDITOR_SETTINGS() (EditorSettings::get_singleton())
|
||||
#define BASE_CONTROL() (EditorNode::get_singleton()->get_gui_base())
|
||||
#define MAIN_SCREEN_CONTROL() (EditorNode::get_singleton()->get_main_screen_control())
|
||||
#define MAIN_SCREEN_CONTROL() (EditorNode::get_singleton()->get_editor_main_screen())
|
||||
#define SCENE_TREE() (SceneTree::get_singleton())
|
||||
#define IS_DEBUGGER_ACTIVE() (EngineDebugger::is_active())
|
||||
#define FS_DOCK_SELECT_FILE(m_path) FileSystemDock::get_singleton()->select_file(m_path)
|
||||
|
@ -37,7 +37,6 @@
|
|||
#define IS_CLASS(m_obj, m_class) (m_obj->is_class_ptr(m_class::get_class_ptr_static()))
|
||||
#define RAND_RANGE(m_from, m_to) (Math::random(m_from, m_to))
|
||||
#define RANDF() (Math::randf())
|
||||
#define BUTTON_SET_ICON(m_btn, m_icon) m_btn->set_icon(m_icon)
|
||||
#define RESOURCE_LOAD(m_path, m_hint) ResourceLoader::load(m_path, m_hint)
|
||||
#define RESOURCE_LOAD_NO_CACHE(m_path, m_hint) ResourceLoader::load(m_path, m_hint, ResourceFormatLoader::CACHE_MODE_IGNORE)
|
||||
#define RESOURCE_SAVE(m_res, m_path, m_flags) ResourceSaver::save(m_res, m_path, m_flags)
|
||||
|
@ -47,7 +46,7 @@
|
|||
#define GET_PROJECT_SETTINGS_DIR() EditorPaths::get_singleton()->get_project_settings_dir()
|
||||
#define EDIT_RESOURCE(m_res) EditorNode::get_singleton()->edit_resource(m_res)
|
||||
#define INSPECTOR_GET_EDITED_OBJECT() (InspectorDock::get_inspector_singleton()->get_edited_object())
|
||||
#define SET_MAIN_SCREEN_EDITOR(m_name) (EditorNode::get_singleton()->select_editor_by_name(m_name))
|
||||
#define SET_MAIN_SCREEN_EDITOR(m_name) (EditorNode::get_singleton()->get_editor_main_screen()->select_by_name(m_name))
|
||||
#define FILE_EXISTS(m_path) FileAccess::exists(m_path)
|
||||
#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()))
|
||||
|
@ -96,7 +95,6 @@ using namespace godot;
|
|||
#define IS_CLASS(m_obj, m_class) (m_obj->is_class(#m_class))
|
||||
#define RAND_RANGE(m_from, m_to) (UtilityFunctions::randf_range(m_from, m_to))
|
||||
#define RANDF() (UtilityFunctions::randf())
|
||||
#define BUTTON_SET_ICON(m_btn, m_icon) m_btn->set_button_icon(m_icon)
|
||||
#define RESOURCE_LOAD(m_path, m_hint) ResourceLoader::get_singleton()->load(m_path, m_hint)
|
||||
#define RESOURCE_LOAD_NO_CACHE(m_path, m_hint) ResourceLoader::get_singleton()->load(m_path, m_hint, ResourceLoader::CACHE_MODE_IGNORE)
|
||||
#define RESOURCE_SAVE(m_res, m_path, m_flags) ResourceSaver::get_singleton()->save(m_res, m_path, m_flags)
|
||||
|
|
|
@ -395,6 +395,18 @@ String LimboUtility::get_property_hint_text(PropertyHint p_hint) const {
|
|||
case PROPERTY_HINT_ARRAY_TYPE: {
|
||||
return "ARRAY_TYPE";
|
||||
}
|
||||
case PROPERTY_HINT_DICTIONARY_TYPE: {
|
||||
return "DICTIONARY_TYPE";
|
||||
}
|
||||
case PROPERTY_HINT_TOOL_BUTTON: {
|
||||
return "TOOL_BUTTON";
|
||||
}
|
||||
case PROPERTY_HINT_ONESHOT: {
|
||||
return "ONESHOT";
|
||||
}
|
||||
case PROPERTY_HINT_NO_NODEPATH: {
|
||||
return "NO_NODEPATH";
|
||||
}
|
||||
case PROPERTY_HINT_LOCALE_ID: {
|
||||
return "LOCALE_ID";
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue