limbonaut / limboai

LimboAI - Behavior Trees and State Machines for Godot 4
https://limboai.readthedocs.io/
MIT License
1.24k stars 48 forks source link

Updating `BTSubTree.subtree` at runtime #94

Open onze opened 7 months ago

onze commented 7 months ago

My tree contains a BTSubTree that I want to load dynamically. In order to do this, I leave the subtree field unset, and a sibling custom node does this:

var target_subtree: BTSubtree = find_btask(
    bt_player.get_tree_instance(), 
    my_subtree_task_name
) as BTSubtree
# assert target_subtree != null
target_subtree.subtree = null
var new_btree := ... # roughly load('res://...') as BehaviorTree
target_subtree.subtree = new_btree

In the editor, before this runs, I get the following error:

my_file.gd:95 @ <anonymous lambda>(): Subtree root task is not valid.
  <C++ Error>    Condition "!subtree->get_root_task().is_valid()" is true.
  <C++ Source>   limboai/bt/tasks/decorators/bt_subtree.cpp:33 @ initialize()

And when my code above runs, the task doesn't update properly.

As a workaround, I tried creating a dummy btree and assign it to the BTSubTree, so that the error isn't raised and the subtree can be replaced. It works until I try to set subtree to the new BehaviorTree, which doesn't raise an issue and doesn't update it.

Is this supported?

limbonaut commented 7 months ago

There was a discussion some time ago: #58 It is currently not supported, as BTSubtree is designed to be a simple loader. However, you can do it dynamically, you just need to do it without BTSubtree, either by using a custom decorator, or by walking the tree and inserting your branch where you need it to be. Here's a simple example decorator (written from memory):

extends BTDecorator
## MyCustomBranch

func _setup() -> void:
    var bt: BehaviorTree = load("res://...")
    var my_branch: BTTask = bt.instantiate(agent, blackboard)
    add_child(my_branch)

func _tick(delta) -> Status:
    var child: BTTask = get_child(0)
    return child.execute(delta)

You may or may not want to isolate the blackboard scope for your subtree like this:

var new_scope := Blackboard.new()
new_scope.set_parent(blackboard)
var my_branch: BTTask = bt.instantiate(agent, new_scope)

If your branch utilizes blackboard plan system:

# Using new scope:
var new_scope: Blackboard = bt.blackboard_plan.create_blackboard(agent)
var my_branch: BTTask = bt.instantiate(agent, new_scope)

# Or not using new scope:
bt.blackboard_plan.populate_blackboard(blackboard, false, agent)
var my_branch: BTTask = bt.instantiate(agent, blackboard)
onze commented 7 months ago

Makes sense, thanks for the quick answer. I opened a PR to add a quick mention to this in BTSubtree's doc itself. Feel free to dismiss it if you feel like it's too much detail.

speakk commented 2 months ago

The example provided here seems to be quite outdated is bt.instantiate has a different signature now, and also gives an error such as: Cannot assign a value of type BTInstance to variable "my_branch" with specified type BTTask.

I'll see if I can get this working myself (very new with limboAI but need this feature), but otherwise any insight from the creator would be appreciated.

limbonaut commented 2 months ago

@speakk Check out the following link. It's an up-to-date example of how to create and manage a BT instance. https://github.com/limbonaut/limboai-extra/blob/main/examples/custom_bt_player/custom_bt_player.gd

You can also find information about methods in the documentation.

limbonaut commented 2 months ago

@speakk You probably don't need to create BTInstance for this use-case. Try bt.get_root_task().clone() instead. That should fix that example, I think.

Something like this:

extends BTDecorator
## MyCustomBranch

func _setup() -> void:
    var bt: BehaviorTree = load("res://...")
    var my_branch: BTTask = bt.get_root_task().clone()
    my_branch.initialize(agent, blackboard)
    add_child(my_branch)

func _tick(delta) -> Status:
    var child: BTTask = get_child(0)
    return child.execute(delta)
speakk commented 2 months ago

Thank you I will try this out!

On Wed, 4 Sept 2024, 15.16 Serhii Snitsaruk, @.***> wrote:

@speakk https://github.com/speakk You probably don't need to create BTInstance for this use-case. Try bt.get_root_task().clone() instead. That should fix that example, I think.

— Reply to this email directly, view it on GitHub https://github.com/limbonaut/limboai/issues/94#issuecomment-2328821276, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAKEEGC5BSKWIXIPLATP2YTZU323BAVCNFSM6AAAAABGR3RLBSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGMRYHAZDCMRXGY . You are receiving this because you were mentioned.Message ID: @.***>