Skip to content

Commit 499d974

Browse files
authored
Introduce MANUAL process mode (#387)
* Add manual mode to beehave tree * Add process mode examples
1 parent 32a4c7b commit 499d974

File tree

3 files changed

+118
-1
lines changed

3 files changed

+118
-1
lines changed

addons/beehave/nodes/beehave_tree.gd

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class_name BeehaveTree extends Node
66

77
enum { SUCCESS, FAILURE, RUNNING }
88

9-
enum ProcessThread { IDLE, PHYSICS }
9+
enum ProcessThread { IDLE, PHYSICS, MANUAL }
1010

1111
signal tree_enabled
1212
signal tree_disabled
@@ -45,6 +45,7 @@ signal tree_disabled
4545
@export var process_thread: ProcessThread = ProcessThread.PHYSICS:
4646
set(value):
4747
process_thread = value
48+
self.enabled = self.enabled and process_thread != ProcessThread.MANUAL
4849
set_physics_process(enabled and process_thread == ProcessThread.PHYSICS)
4950
set_process(enabled and process_thread == ProcessThread.IDLE)
5051

docs/manual/core_concepts.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,31 @@ Understanding how behavior trees execute is crucial:
9090

9191
This continual reevaluation allows the AI to respond to changing conditions in the game world.
9292

93+
### Execution Modes
94+
95+
Beehave trees can run in three different modes, controlled by the `process_thread` property:
96+
97+
- **PHYSICS** (default): The tree ticks automatically during the physics process, making it ideal for physics-based behaviors like movement and combat
98+
- **IDLE**: The tree ticks automatically during the idle process, better suited for UI or non-physics behaviors
99+
- **MANUAL**: The tree only ticks when you explicitly call the `tick()` method, giving you full control over when behaviors execute
100+
101+
#### Manual Mode Use Cases
102+
103+
Manual mode is particularly useful in scenarios where you want precise control over when behaviors execute:
104+
105+
- **Turn-Based Games**: In games like roguelikes or strategy games, you might want behaviors to execute only when it's a character's turn
106+
- **Event-Driven Systems**: When behaviors should only run in response to specific events rather than every frame
107+
- **Performance Optimization**: For behaviors that don't need to run every frame, you can manually control the tick rate
108+
- **Synchronized Behaviors**: When multiple trees need to execute in a specific order or at the same time
109+
110+
Example of manual mode in a roguelike:
111+
```gdscript
112+
# In your turn manager
113+
func process_turn():
114+
for character in characters:
115+
character.behavior_tree.tick()
116+
```
117+
93118
### Execution Example
94119

95120
Consider this sequence: "Check if enemy is visible" → "Move to enemy" → "Attack enemy"

test/beehave_tree_test.gd

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,94 @@ func test_actor_override() -> void:
113113
tree.actor = actor
114114
scene.add_child(tree)
115115
assert_that(tree.actor).is_equal(actor)
116+
117+
118+
func test_manual_mode_does_not_auto_tick() -> void:
119+
var scene = create_scene()
120+
scene_runner(scene)
121+
scene.beehave_tree.process_thread = BeehaveTree.ProcessThread.MANUAL
122+
scene.beehave_tree.enabled = true
123+
124+
# Set up count up action
125+
scene.count_up_action.status = BeehaveNode.RUNNING
126+
scene.beehave_tree.blackboard.set_value("custom_value", 0)
127+
128+
# Wait a bit to verify no auto-ticks
129+
await get_tree().create_timer(0.1).timeout
130+
assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(0)
131+
132+
133+
func test_manual_mode_can_tick_manually() -> void:
134+
var scene = create_scene()
135+
scene_runner(scene)
136+
scene.beehave_tree.process_thread = BeehaveTree.ProcessThread.MANUAL
137+
scene.beehave_tree.enabled = true
138+
139+
# Set up count up action
140+
scene.count_up_action.status = BeehaveNode.RUNNING
141+
scene.beehave_tree.blackboard.set_value("custom_value", 0)
142+
143+
# Manual tick should increase counter
144+
scene.beehave_tree.tick()
145+
assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(1)
146+
147+
# Another manual tick should increase counter again
148+
scene.beehave_tree.tick()
149+
assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(2)
150+
151+
152+
func test_manual_mode_respects_tick_rate() -> void:
153+
var scene = create_scene()
154+
scene_runner(scene)
155+
scene.beehave_tree.process_thread = BeehaveTree.ProcessThread.MANUAL
156+
scene.beehave_tree.tick_rate = 3
157+
scene.beehave_tree.enabled = true
158+
159+
# Set up count up action
160+
scene.count_up_action.status = BeehaveNode.RUNNING
161+
scene.beehave_tree.blackboard.set_value("custom_value", 0)
162+
163+
# First tick should increase counter
164+
scene.beehave_tree.tick()
165+
assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(1)
166+
167+
# Second tick should increase counter (tick rate is only checked in _process_internally)
168+
scene.beehave_tree.tick()
169+
assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(2)
170+
171+
# Third tick should increase counter
172+
scene.beehave_tree.tick()
173+
assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(3)
174+
175+
# Fourth tick should increase counter
176+
scene.beehave_tree.tick()
177+
assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(4)
178+
179+
180+
func test_manual_mode_can_be_disabled() -> void:
181+
var scene = create_scene()
182+
scene_runner(scene)
183+
scene.beehave_tree.process_thread = BeehaveTree.ProcessThread.MANUAL
184+
scene.beehave_tree.enabled = true
185+
186+
# Set up count up action
187+
scene.count_up_action.status = BeehaveNode.RUNNING
188+
scene.beehave_tree.blackboard.set_value("custom_value", 0)
189+
190+
# Should be able to tick when enabled
191+
scene.beehave_tree.tick()
192+
assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(1)
193+
194+
# Disable the tree
195+
scene.beehave_tree.disable()
196+
197+
# Should not be able to tick when disabled
198+
scene.beehave_tree.tick()
199+
assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(1) # Value should not change
200+
201+
# Re-enable the tree
202+
scene.beehave_tree.enable()
203+
204+
# Should be able to tick again
205+
scene.beehave_tree.tick()
206+
assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(2)

0 commit comments

Comments
 (0)