-
-
Notifications
You must be signed in to change notification settings - Fork 123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ParallelNode composite #58
Comments
@monkeez thank you for your suggestion. Could you perhaps update the description with more details on:
|
@bitbrain I stumbled upon a situation that I think a parallel node would solve my problem. |
@Ohan17 I didn't put a lot of thought in it, but on first sight it seems that since Roam and Discharge are independent actions maybe they could be different behavior trees all together. And if they need to share data you can assign a common blackboard to both of them. The only issue with that is that it kind of works if your independent nodes are children of the first node I guess. If they are further down the tree, maybe you can have conditions that check the status and running action of the other tree maybe, without having to replicate the whole tree up to that point. |
Any updates on this? Very interested. |
My understanding after reading this is that there cannot be a "parallel" version of sequence and selector, but it is rather its own type of composite node with a policy. We can therefore introduce some sort of enum and call it 'ExecutionPolicy' that can either be SEQUENCE or SELECTOR. In case you want to introduce two nodes, feel free to do so. It is up to the implementer to decide. In addition, the implementer needs to consider the table on that page and replicate its behavior by using Godot threads. Also, we require extensive unit tests (it is documented here how to do that). Also do not forget to write documentation! |
|
I would just like to add my two cents to this discussion... How is using two AI trees for two different behaviors less flexible than creating a whole new node for just this case? If the two behaviors need to share data you can use the same blackboard, as it has already been said. By breaking the single responsibility principle, we actually make it less flexible. I will give you an example that is an extreme edge case but is just to prove my point: imagine you want to run 2 random actions in parallel from a collection of 3 possible actions. This is possible to do it with 3 trees for each behavior, sharing the same blackboard. Or we would need to make an entirely new Sure, maybe creating a new ParallelComposite feels easier than creating a whole new tree but I think that in the provided case is just a matter of choosing the right tool for the job. The discharge part is so simple it doesnt even need a beehave tree. I could be just a node with a script and a child Timer. (From what I could gather from the comments, maybe theres more to it) Sorry for the wall of text 😅 |
Here's a workaround I've managed for now, inspired by https://gdscript.com/solutions/godot-behaviour-tree/ class_name Parallel
extends Composite
enum Policy { SEQUENCE, SELECTOR }
@export var policy: Policy = Policy.SEQUENCE
var finish_count := 0
func tick(actor: Node, blackboard: Blackboard) -> int:
for c in get_children():
if c != running_child:
c.before_run(actor, blackboard)
var response = c.tick(actor, blackboard)
if can_send_message(blackboard):
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
match response:
SUCCESS:
_cleanup_running_task(c, actor, blackboard)
c.after_run(actor, blackboard)
finish_count += 1
if policy == Policy.SEQUENCE:
if finish_count >= get_child_count():
finish_count = 0
return SUCCESS
else:
return SUCCESS
FAILURE:
_cleanup_running_task(c, actor, blackboard)
c.after_run(actor, blackboard)
finish_count += 1
if policy == Policy.SELECTOR:
if finish_count >= get_child_count():
finish_count = 0
return FAILURE
else:
return FAILURE
RUNNING:
running_child = c
if c is ActionLeaf:
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
return RUNNING
func after_run(actor: Node, blackboard: Blackboard) -> void:
finish_count = 0
super(actor, blackboard)
func interrupt(actor: Node, blackboard: Blackboard) -> void:
finish_count = 0
super(actor, blackboard)
## Changes `running_action` and `running_child` after the node finishes executing.
func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard):
var blackboard_name = str(actor.get_instance_id())
if finished_action == running_child:
running_child = null
if finished_action == blackboard.get_value("running_action", null, blackboard_name):
blackboard.set_value("running_action", null, blackboard_name)
func get_class_name() -> Array[StringName]:
var classes := super()
classes.push_back(&"ParallelComposite")
return classes Doesn't support multithreading obviously but I just need basic parallelism here. If I have time later I will try to make a PR. Sequence policy fails when any child fails, and succeeds only when all succeed. |
May I ask how do you do two running task at the same time without parallel node? Just think about this a few days and cannot figure it out.. |
@eterlan the idea is that they are not truly running in parallel (multi-threaded) but effectively every frame multiple taks in a |
May I ask what state this feature is currently in? |
@bozoVuksan architecturally, beehave 2.x has been designed to only ever have one running node at any time. This feature would require the need to support multiple running nodes. Our current assumption is that this breaks compatibility (beehave node methods need changing) so currently it is aimed for beehave 3.x In case anyone finds a way to make this work for 2.x without breaking the compatibility, let us know. |
Done thanks to #332 |
I think it's still valuable to keep this issue open since Additionally, nesting |
I was trying out beehave and I've been enjoying it for creating AI. But it got me thinking about if it'd be possible to reuse AI actions and conditions for a player character controller too. This is where I ran into issues as dealing with player inputs in a behaviour tree gets messy pretty fast (very deep nested node trees).
While looking around I noticed a description here of a parallel composite node (which can run in selector or sequence mode). This would make it possible to make a behviour tree more flat which would work better when you're dealing with lots of different player inputs.
I think I know how to implement this in a simple way, but it wouldn't be truly parallel. It would run each child tree collecting their statuses and after all children have been processed it the Parallel node could return success or failure based on the collected results.
Todo
SequenceParallelComposite
SelectorParallelComposite
SequenceParallelComposite
SelectorParallelComposite
get_running_action()
withget_running_actions()
array, as with parallel nodes multiple nodes can be running at the same time.The text was updated successfully, but these errors were encountered: