From 1acf5499a659faaee65320c10691a9ab8444ff84 Mon Sep 17 00:00:00 2001 From: miguel Date: Fri, 17 Nov 2023 19:12:24 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix=20interrupt=20of=20sequence?= =?UTF-8?q?=20star/random=20+=20improve=20docs=20(#249)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :bug: fix sequence star interrupt * improve docs * improve docs further --- .../nodes/composites/sequence_random.gd | 3 ++ .../beehave/nodes/composites/sequence_star.gd | 5 ++- docs/manual/composites.md | 11 +++-- examples/random_tree_example/RandomAction.gd | 12 +++--- examples/random_tree_example/RandomTree.gd | 40 +++++++++---------- test/nodes/composites/sequence_star_test.gd | 6 ++- 6 files changed, 45 insertions(+), 32 deletions(-) diff --git a/addons/beehave/nodes/composites/sequence_random.gd b/addons/beehave/nodes/composites/sequence_random.gd index 77928c82..a61bd929 100644 --- a/addons/beehave/nodes/composites/sequence_random.gd +++ b/addons/beehave/nodes/composites/sequence_random.gd @@ -50,6 +50,9 @@ func tick(actor: Node, blackboard: Blackboard) -> int: c.after_run(actor, blackboard) FAILURE: _children_bag.erase(c) + # Interrupt any child that was RUNNING before + # but do not reset! + super.interrupt(actor, blackboard) c.after_run(actor, blackboard) return FAILURE RUNNING: diff --git a/addons/beehave/nodes/composites/sequence_star.gd b/addons/beehave/nodes/composites/sequence_star.gd index d40cffdc..a6400e53 100644 --- a/addons/beehave/nodes/composites/sequence_star.gd +++ b/addons/beehave/nodes/composites/sequence_star.gd @@ -6,7 +6,7 @@ class_name SequenceStarComposite extends Composite ## `SUCCESS` in case all of the children report a `SUCCESS` status code. ## If at least one child reports a `FAILURE` status code, this node will also ## return `FAILURE` and tick again. -## In case a child returns `RUNNING` this node will restart. +## In case a child returns `RUNNING` this node will tick again. var successful_index: int = 0 @@ -32,6 +32,9 @@ func tick(actor: Node, blackboard: Blackboard) -> int: successful_index += 1 c.after_run(actor, blackboard) FAILURE: + # Interrupt any child that was RUNNING before + # but do not reset! + super.interrupt(actor, blackboard) c.after_run(actor, blackboard) return FAILURE RUNNING: diff --git a/docs/manual/composites.md b/docs/manual/composites.md index 68a1dfc7..a0682e58 100644 --- a/docs/manual/composites.md +++ b/docs/manual/composites.md @@ -18,10 +18,15 @@ With composite nodes, you can craft unique and dynamic Behavior Trees for your g ## Restarting a composite -If a parent node restarts a child node, it means that the parent node will start the child node from scratch the next time it is ticked. This means that any progress made by the child node will be reset, and it will start its execution from the beginning. +When a parent node restarts, it means the whole composite node begins its evaluation again from the beginning. This does not interrupt the running child node. ## Ticking again a composite -If a parent node ticks a child node again, it means that the parent node will immediately tick the child node again on the next frame, without waiting for the current frame to finish executing. This allows the child node to continue its execution from where it left off without resetting its progress. +When a composite node ticks again, it will "jump" to the currently `RUNNING` child node and tick it. -In other words, restarting a child node means that the parent node will give the child node a fresh start, while ticking it again means that the parent node will let the child node continue its execution from where it left off. +For [sequence nodes](sequence.md), on the first frame it starts ticking all its children in order, starting from the first one, until either all of them return `SUCCESS` or one of them returns `RUNNING`. If one of them returned `RUNNING`, on the next frame, the sequence will only tick the running child node. Not all of them, just the running one - and possibly the child nodes following it, if the running child has completed and returned `SUCCESS`. +For [selector nodes](selector.md), on the first frame it starts ticking each child in order until one of them returns `SUCCESS` or one of them returns `RUNNING`. If one of them returned `RUNNING`, on the next frame, the selector will only tick the running child node, skipping all the previous ones, and possibly the child nodes following it, if the running child has completed and returned `FAILURE`. + +## Interrupting child nodes + +A sequence may interrupt any `RUNNING` child node. The `interrupt` method will be called recursively on all descendant nodes of the interrupted child node. \ No newline at end of file diff --git a/examples/random_tree_example/RandomAction.gd b/examples/random_tree_example/RandomAction.gd index df7f22e8..0e9aa007 100644 --- a/examples/random_tree_example/RandomAction.gd +++ b/examples/random_tree_example/RandomAction.gd @@ -17,17 +17,17 @@ var weights = [3., 3., 1.] func _get_random_action(): var sum = 0. for w in weights: - sum += w + sum += w var rnd = randf_range(0, sum) for i in weights.size(): - if rnd <= weights[i]: - return i - rnd -= weights[i] + if rnd <= weights[i]: + return i + rnd -= weights[i] return weights.size() - 1 func tick(actor: Node, blackboard: Blackboard) -> int: var step = Time.get_ticks_msec() / reset_duration_msec if step != last_step: - action = _get_random_action() - last_step = step + action = _get_random_action() + last_step = step return action diff --git a/examples/random_tree_example/RandomTree.gd b/examples/random_tree_example/RandomTree.gd index 5b9b6545..d393174f 100644 --- a/examples/random_tree_example/RandomTree.gd +++ b/examples/random_tree_example/RandomTree.gd @@ -4,10 +4,10 @@ extends BeehaveTree @export_range(1, 100) var randomize_node_count: int = 20 @export var randomize_tree: bool = false: set(v): - if !v: - return - randomize_tree = false - _randomize_tree() + if !v: + return + randomize_tree = false + _randomize_tree() class TNode extends RefCounted: var children = [] @@ -17,7 +17,7 @@ var long_name_suffix = "LooooooooongNameSuffix" func get_name_suffix(): var r = randi_range(0, 0) if r: - return long_name_suffix + return long_name_suffix return "" var tree_root: TNode @@ -31,33 +31,33 @@ func _make_random_tree(): tree_root = TNode.new() tree_nodes = [tree_root] for i in randomize_node_count - 1: - var n = _get_random_node() - var new_node = TNode.new() - n.children.append(new_node) - tree_nodes.append(new_node) + var n = _get_random_node() + var new_node = TNode.new() + n.children.append(new_node) + tree_nodes.append(new_node) func _parse_tree_node(parent, node, index: int): var n if node.children.size() > 1: - n = SelectorReactiveComposite.new() - n.name = "SelectorReactiveComposite%s-%d" % [get_name_suffix(), index] + n = SelectorReactiveComposite.new() + n.name = "SelectorReactiveComposite%s-%d" % [get_name_suffix(), index] elif node.children.size() == 1: - n = InverterDecorator.new() - n.name = "InverterDecorator%s-%d" % [get_name_suffix(), index] + n = InverterDecorator.new() + n.name = "InverterDecorator%s-%d" % [get_name_suffix(), index] else: - n = RandomAction.new() - n.name = "RandomAction%s-%d" % [get_name_suffix(), index] + n = RandomAction.new() + n.name = "RandomAction%s-%d" % [get_name_suffix(), index] parent.add_child(n) if Engine.is_editor_hint(): - n.owner = get_tree().get_edited_scene_root() + n.owner = get_tree().get_edited_scene_root() var i = 0 for ch in node.children: - _parse_tree_node(n, ch, i) - i += 1 + _parse_tree_node(n, ch, i) + i += 1 func _randomize_tree(): for ch in get_children(): - remove_child(ch) - ch.queue_free() + remove_child(ch) + ch.queue_free() _make_random_tree() _parse_tree_node(self, tree_root, 0) diff --git a/test/nodes/composites/sequence_star_test.gd b/test/nodes/composites/sequence_star_test.gd index f9bf7a23..cadd6cc7 100644 --- a/test/nodes/composites/sequence_star_test.gd +++ b/test/nodes/composites/sequence_star_test.gd @@ -91,11 +91,13 @@ func test_keeps_running_child_until_failure() -> void: assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) assert_that(action1.count).is_equal(1) - assert_that(action2.count).is_equal(3) + # action2 will reset as it failed + assert_that(action2.count).is_equal(0) assert_that(tree.tick()).is_equal(BeehaveNode.FAILURE) assert_that(action1.count).is_equal(1) - assert_that(action2.count).is_equal(4) + # action2 has reset previously but sequence star will tick again + assert_that(action2.count).is_equal(1) func test_tick_again_when_child_returns_failure() -> void: