From 959f1b29adee14cee892a311622a815a15e21c65 Mon Sep 17 00:00:00 2001 From: bitbrain Date: Thu, 10 Apr 2025 13:34:09 +0100 Subject: [PATCH 1/2] Add manual mode to beehave tree --- addons/beehave/nodes/beehave_tree.gd | 3 +- test/beehave_tree_test.gd | 91 ++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/addons/beehave/nodes/beehave_tree.gd b/addons/beehave/nodes/beehave_tree.gd index ecdbe52c..3ee35053 100644 --- a/addons/beehave/nodes/beehave_tree.gd +++ b/addons/beehave/nodes/beehave_tree.gd @@ -6,7 +6,7 @@ class_name BeehaveTree extends Node enum { SUCCESS, FAILURE, RUNNING } -enum ProcessThread { IDLE, PHYSICS } +enum ProcessThread { IDLE, PHYSICS, MANUAL } signal tree_enabled signal tree_disabled @@ -45,6 +45,7 @@ signal tree_disabled @export var process_thread: ProcessThread = ProcessThread.PHYSICS: set(value): process_thread = value + self.enabled = self.enabled and process_thread != ProcessThread.MANUAL set_physics_process(enabled and process_thread == ProcessThread.PHYSICS) set_process(enabled and process_thread == ProcessThread.IDLE) diff --git a/test/beehave_tree_test.gd b/test/beehave_tree_test.gd index 3347a4fa..b340c12c 100644 --- a/test/beehave_tree_test.gd +++ b/test/beehave_tree_test.gd @@ -113,3 +113,94 @@ func test_actor_override() -> void: tree.actor = actor scene.add_child(tree) assert_that(tree.actor).is_equal(actor) + + +func test_manual_mode_does_not_auto_tick() -> void: + var scene = create_scene() + scene_runner(scene) + scene.beehave_tree.process_thread = BeehaveTree.ProcessThread.MANUAL + scene.beehave_tree.enabled = true + + # Set up count up action + scene.count_up_action.status = BeehaveNode.RUNNING + scene.beehave_tree.blackboard.set_value("custom_value", 0) + + # Wait a bit to verify no auto-ticks + await get_tree().create_timer(0.1).timeout + assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(0) + + +func test_manual_mode_can_tick_manually() -> void: + var scene = create_scene() + scene_runner(scene) + scene.beehave_tree.process_thread = BeehaveTree.ProcessThread.MANUAL + scene.beehave_tree.enabled = true + + # Set up count up action + scene.count_up_action.status = BeehaveNode.RUNNING + scene.beehave_tree.blackboard.set_value("custom_value", 0) + + # Manual tick should increase counter + scene.beehave_tree.tick() + assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(1) + + # Another manual tick should increase counter again + scene.beehave_tree.tick() + assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(2) + + +func test_manual_mode_respects_tick_rate() -> void: + var scene = create_scene() + scene_runner(scene) + scene.beehave_tree.process_thread = BeehaveTree.ProcessThread.MANUAL + scene.beehave_tree.tick_rate = 3 + scene.beehave_tree.enabled = true + + # Set up count up action + scene.count_up_action.status = BeehaveNode.RUNNING + scene.beehave_tree.blackboard.set_value("custom_value", 0) + + # First tick should increase counter + scene.beehave_tree.tick() + assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(1) + + # Second tick should increase counter (tick rate is only checked in _process_internally) + scene.beehave_tree.tick() + assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(2) + + # Third tick should increase counter + scene.beehave_tree.tick() + assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(3) + + # Fourth tick should increase counter + scene.beehave_tree.tick() + assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(4) + + +func test_manual_mode_can_be_disabled() -> void: + var scene = create_scene() + scene_runner(scene) + scene.beehave_tree.process_thread = BeehaveTree.ProcessThread.MANUAL + scene.beehave_tree.enabled = true + + # Set up count up action + scene.count_up_action.status = BeehaveNode.RUNNING + scene.beehave_tree.blackboard.set_value("custom_value", 0) + + # Should be able to tick when enabled + scene.beehave_tree.tick() + assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(1) + + # Disable the tree + scene.beehave_tree.disable() + + # Should not be able to tick when disabled + scene.beehave_tree.tick() + assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(1) # Value should not change + + # Re-enable the tree + scene.beehave_tree.enable() + + # Should be able to tick again + scene.beehave_tree.tick() + assert_that(scene.beehave_tree.blackboard.get_value("custom_value")).is_equal(2) From 31dffb7cbaabc829cbb4b45f619d4ab100faef2b Mon Sep 17 00:00:00 2001 From: bitbrain Date: Thu, 10 Apr 2025 13:39:44 +0100 Subject: [PATCH 2/2] Add process mode examples --- docs/manual/core_concepts.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/manual/core_concepts.md b/docs/manual/core_concepts.md index c84d6267..41fbc897 100644 --- a/docs/manual/core_concepts.md +++ b/docs/manual/core_concepts.md @@ -90,6 +90,31 @@ Understanding how behavior trees execute is crucial: This continual reevaluation allows the AI to respond to changing conditions in the game world. +### Execution Modes + +Beehave trees can run in three different modes, controlled by the `process_thread` property: + +- **PHYSICS** (default): The tree ticks automatically during the physics process, making it ideal for physics-based behaviors like movement and combat +- **IDLE**: The tree ticks automatically during the idle process, better suited for UI or non-physics behaviors +- **MANUAL**: The tree only ticks when you explicitly call the `tick()` method, giving you full control over when behaviors execute + +#### Manual Mode Use Cases + +Manual mode is particularly useful in scenarios where you want precise control over when behaviors execute: + +- **Turn-Based Games**: In games like roguelikes or strategy games, you might want behaviors to execute only when it's a character's turn +- **Event-Driven Systems**: When behaviors should only run in response to specific events rather than every frame +- **Performance Optimization**: For behaviors that don't need to run every frame, you can manually control the tick rate +- **Synchronized Behaviors**: When multiple trees need to execute in a specific order or at the same time + +Example of manual mode in a roguelike: +```gdscript +# In your turn manager +func process_turn(): + for character in characters: + character.behavior_tree.tick() +``` + ### Execution Example Consider this sequence: "Check if enemy is visible" → "Move to enemy" → "Attack enemy"