-
-
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
Added a new composite node: simple_parallel #332
Merged
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
@tool | ||
@icon("../../icons/simple_parallel.svg") | ||
class_name SimpleParallelComposite extends Composite | ||
|
||
## Simple Parallel nodes will attampt to execute all chidren at same time and | ||
## can only have exactly two children. First child as primary node, second | ||
## child as secondary node. | ||
## This node will always report primary node's state, and continue tick while | ||
## primary node return 'RUNNING'. The state of secondary node will be ignored | ||
## and executed like a subtree. | ||
## If primary node return 'SUCCESS' or 'FAILURE', this node will interrupt | ||
## secondary node and return primary node's result. | ||
## If this node is running under delay mode, it will wait seconday node | ||
## finish its action after primary node terminates. | ||
|
||
#how many times should secondary node repeat, zero means loop forever | ||
@export var secondary_node_repeat_count:int = 0 | ||
|
||
#wether to wait secondary node finish its current action after primary node finished | ||
@export var delay_mode:bool = false | ||
|
||
var delayed_result := SUCCESS | ||
var main_task_finished:bool = false | ||
var secondary_node_running:bool = false | ||
var secondary_node_repeat_left:int = 0 | ||
|
||
func _get_configuration_warnings() -> PackedStringArray: | ||
var warnings: PackedStringArray = super._get_configuration_warnings() | ||
|
||
if get_child_count() != 2: | ||
warnings.append("SimpleParallel should have exactly two child nodes.") | ||
|
||
if not get_child(0) is ActionLeaf: | ||
warnings.append("SimpleParallel should have an action leaf node as first child node.") | ||
|
||
return warnings | ||
|
||
func tick(actor, blackboard: Blackboard): | ||
for c in get_children(): | ||
var node_index = c.get_index() | ||
if node_index == 0 and not main_task_finished: | ||
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) | ||
|
||
delayed_result = response | ||
match response: | ||
SUCCESS,FAILURE: | ||
_cleanup_running_task(c, actor, blackboard) | ||
c.after_run(actor, blackboard) | ||
main_task_finished = true | ||
if not delay_mode: | ||
if secondary_node_running: | ||
get_child(1).interrupt(actor, blackboard) | ||
_reset() | ||
return delayed_result | ||
RUNNING: | ||
running_child = c | ||
if c is ActionLeaf: | ||
blackboard.set_value("running_action", c, str(actor.get_instance_id())) | ||
|
||
elif node_index == 1: | ||
if secondary_node_repeat_count == 0 or secondary_node_repeat_left > 0: | ||
if not secondary_node_running: | ||
c.before_run(actor, blackboard) | ||
var subtree_response = c.tick(actor, blackboard) | ||
if subtree_response != RUNNING: | ||
secondary_node_running = false | ||
c.after_run(actor, blackboard) | ||
if delay_mode and main_task_finished: | ||
_reset() | ||
return delayed_result | ||
elif secondary_node_repeat_left > 0: | ||
secondary_node_repeat_left -= 1 | ||
else: | ||
secondary_node_running = true | ||
|
||
return RUNNING | ||
|
||
func before_run(actor: Node, blackboard:Blackboard) -> void: | ||
secondary_node_repeat_left = secondary_node_repeat_count | ||
super(actor, blackboard) | ||
|
||
func interrupt(actor: Node, blackboard: Blackboard) -> void: | ||
if not main_task_finished: | ||
get_child(0).interrupt(actor, blackboard) | ||
if secondary_node_running: | ||
get_child(1).interrupt(actor, blackboard) | ||
_reset() | ||
super(actor, blackboard) | ||
|
||
func after_run(actor: Node, blackboard: Blackboard) -> void: | ||
_reset() | ||
super(actor, blackboard) | ||
|
||
func _reset() -> void: | ||
main_task_finished = false | ||
secondary_node_running = false | ||
|
||
## 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(&"SimpleParallelComposite") | ||
return classes |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Simple Parallel Node | ||
The Simple Parallel node is a fundamental building block in Behavior Trees, used to execute two children at same time. It helps you run multiple actions simultaneously.Think of the Simple Parallel node as "While doing A, do B as well." | ||
|
||
## How does it work? | ||
Simple Parallel nodes will attampt to execute all chidren at same time and can only have exactly two children. First child as primary node, second child as secondary node. | ||
This node will always report primary node's state, and continue tick while primary node return `RUNNING`. The state of secondary node will be ignored and executed like a subtree. | ||
If primary node return `SUCCESS` or `FAILURE`, this node will interrupt secondary node and return primary node's result. | ||
If this node is running under delay mode, it will wait seconday node finish its action after primary node terminates. | ||
|
||
|
||
## Example Scenarios | ||
Here are some example scenarios to help you understand the Sequence node better: | ||
|
||
### Example: While attacking the enemy, move toward the enemy | ||
Imagine you want a ranged enemy character trying to shoot you whenever he can while to move towards you. You can use a Simple Parallel node with the following child nodes architecture: | ||
|
||
1. Move to point A near player | ||
2. Sequence Node | ||
1. Check if enemy can shoot | ||
2. Shoot | ||
|
||
The enemy will move to a location near player and try to shoot at same time, and if move action is successful or failure, the Simple Parallel node will termitate the child sequence node for shooting attempt, then return `SUCCESS` or `FAILURE` according to move action result. | ||
|
||
Simple Parallel can be nested to create complex behaviors while it's not suggested, because too much nesting would make it hard to maintain your behavior tree. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
# GdUnit generated TestSuite | ||
class_name SimpleParallelTest | ||
extends GdUnitTestSuite | ||
@warning_ignore("unused_parameter") | ||
@warning_ignore("return_value_discarded") | ||
|
||
# TestSuite generated from | ||
const __source = "res://addons/beehave/nodes/composites/simple_parallel.gd" | ||
const __count_up_action = "res://test/actions/count_up_action.gd" | ||
const __blackboard = "res://addons/beehave/blackboard.gd" | ||
const __tree = "res://addons/beehave/nodes/beehave_tree.gd" | ||
|
||
var tree: BeehaveTree | ||
var simple_parallel: SimpleParallelComposite | ||
var action1: ActionLeaf | ||
var action2: ActionLeaf | ||
var actor: Node | ||
var blackboard: Blackboard | ||
|
||
|
||
func before_test() -> void: | ||
tree = auto_free(load(__tree).new()) | ||
simple_parallel = auto_free(load(__source).new()) | ||
action1 = auto_free(load(__count_up_action).new()) | ||
action2 = auto_free(load(__count_up_action).new()) | ||
actor = auto_free(Node2D.new()) | ||
blackboard = auto_free(load(__blackboard).new()) | ||
|
||
tree.add_child(simple_parallel) | ||
simple_parallel.add_child(action1) | ||
simple_parallel.add_child(action2) | ||
|
||
tree.actor = actor | ||
tree.blackboard = blackboard | ||
|
||
|
||
func test_always_return_first_node_result() -> void: | ||
action2.status = BeehaveNode.FAILURE | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) | ||
assert_that(action1.count).is_equal(1) | ||
assert_that(action2.count).is_equal(0) | ||
|
||
action1.status = BeehaveNode.FAILURE | ||
action2.status = BeehaveNode.SUCCESS | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) | ||
assert_that(action1.count).is_equal(2) | ||
assert_that(action2.count).is_equal(0) | ||
|
||
func test_interrupt_second_when_first_is_succeeding() -> void: | ||
action1.status = BeehaveNode.RUNNING | ||
action2.status = BeehaveNode.RUNNING | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) | ||
assert_that(action1.count).is_equal(1) | ||
assert_that(action2.count).is_equal(1) | ||
|
||
action1.status = BeehaveNode.SUCCESS | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) | ||
assert_that(action1.count).is_equal(2) | ||
assert_that(action2.count).is_equal(0) | ||
|
||
|
||
func test_interrupt_second_when_first_is_failing() -> void: | ||
action1.status = BeehaveNode.RUNNING | ||
action2.status = BeehaveNode.RUNNING | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) | ||
assert_that(action1.count).is_equal(1) | ||
assert_that(action2.count).is_equal(1) | ||
|
||
action1.status = BeehaveNode.FAILURE | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.FAILURE) | ||
assert_that(action1.count).is_equal(2) | ||
assert_that(action2.count).is_equal(0) | ||
|
||
|
||
func test_continue_tick_when_child_returns_failing() -> void: | ||
action1.status = BeehaveNode.RUNNING | ||
action2.status = BeehaveNode.FAILURE | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) | ||
assert_that(action1.count).is_equal(1) | ||
assert_that(action2.count).is_equal(1) | ||
|
||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) | ||
assert_that(action1.count).is_equal(2) | ||
assert_that(action2.count).is_equal(2) | ||
|
||
|
||
func test_child_continue_tick_in_delay_mode() -> void: | ||
simple_parallel.delay_mode = true | ||
action1.status = BeehaveNode.RUNNING | ||
action2.status = BeehaveNode.RUNNING | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) | ||
assert_that(action1.count).is_equal(1) | ||
assert_that(action2.count).is_equal(1) | ||
|
||
action1.status = BeehaveNode.SUCCESS | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) | ||
assert_that(action1.count).is_equal(2) | ||
assert_that(action2.count).is_equal(2) | ||
|
||
action2.status = BeehaveNode.FAILURE | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) | ||
assert_that(action1.count).is_equal(2) | ||
assert_that(action2.count).is_equal(3) | ||
|
||
func test_child_tick_count() -> void: | ||
simple_parallel.secondary_node_repeat_count = 2 | ||
action1.status = BeehaveNode.RUNNING | ||
action2.status = BeehaveNode.FAILURE | ||
assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) | ||
assert_that(action1.count).is_equal(1) | ||
assert_that(action2.count).is_equal(1) | ||
assert_that(simple_parallel.secondary_node_repeat_left).is_equal(1) | ||
|
||
action2.status = BeehaveNode.RUNNING | ||
assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) | ||
assert_that(action1.count).is_equal(2) | ||
assert_that(action2.count).is_equal(2) | ||
assert_that(simple_parallel.secondary_node_repeat_left).is_equal(1) | ||
|
||
action2.status = BeehaveNode.SUCCESS | ||
assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) | ||
assert_that(action1.count).is_equal(3) | ||
assert_that(action2.count).is_equal(3) | ||
assert_that(simple_parallel.secondary_node_repeat_left).is_equal(0) | ||
|
||
assert_that(tree.tick()).is_equal(BeehaveNode.RUNNING) | ||
assert_that(action1.count).is_equal(4) | ||
assert_that(action2.count).is_equal(3) | ||
|
||
func test_nested_simple_parallel() -> void: | ||
var simple_parallel2 = auto_free(load(__source).new()) | ||
var action3 = auto_free(load(__count_up_action).new()) | ||
simple_parallel.remove_child(action2) | ||
simple_parallel.add_child(simple_parallel2) | ||
simple_parallel2.add_child(action2) | ||
simple_parallel2.add_child(action3) | ||
|
||
action1.status = BeehaveNode.RUNNING | ||
action2.status = BeehaveNode.RUNNING | ||
action3.status = BeehaveNode.RUNNING | ||
|
||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) | ||
|
||
assert_that(action1.count).is_equal(1) | ||
assert_that(action2.count).is_equal(1) | ||
assert_that(action3.count).is_equal(1) | ||
|
||
action2.status = BeehaveNode.SUCCESS | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) | ||
assert_that(action1.count).is_equal(2) | ||
assert_that(action2.count).is_equal(2) | ||
assert_that(action3.count).is_equal(0) | ||
|
||
action3.status = BeehaveNode.RUNNING | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) | ||
assert_that(action1.count).is_equal(3) | ||
assert_that(action2.count).is_equal(3) | ||
assert_that(action3.count).is_equal(0) | ||
|
||
action2.status = BeehaveNode.RUNNING | ||
action3.status = BeehaveNode.RUNNING | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.RUNNING) | ||
assert_that(action1.count).is_equal(4) | ||
assert_that(action2.count).is_equal(4) | ||
assert_that(action3.count).is_equal(1) | ||
|
||
action1.status = BeehaveNode.SUCCESS | ||
assert_that(simple_parallel.tick(actor, blackboard)).is_equal(BeehaveNode.SUCCESS) | ||
assert_that(action1.count).is_equal(5) | ||
assert_that(action2.count).is_equal(0) | ||
assert_that(action3.count).is_equal(0) | ||
|
||
simple_parallel2.remove_child(action2) | ||
simple_parallel2.remove_child(action3) | ||
simple_parallel.remove_child(simple_parallel2) | ||
simple_parallel.add_child(action2) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you remove this?