└── addons └── behavior_tree ├── LICENSE ├── bt_agent.gd ├── bt_blackboard.gd ├── bt_debugger.gd ├── bt_editor_plugin.gd ├── bt_expression.gd ├── bt_item.gd ├── bt_node.gd ├── bt_running_tree.gd ├── composites ├── bt_composite.gd ├── bt_composite_utility.gd ├── bt_parallel.gd ├── bt_selector.gd ├── bt_sequence.gd ├── bt_utility_selector.gd └── bt_utility_sequence.gd ├── conditions ├── bt_blackboard_is.gd └── bt_condition.gd ├── graph ├── bt_editor.gd ├── bt_graph_edit.gd ├── bt_graph_header.gd ├── bt_graph_node.gd ├── editor.tscn ├── graph_node.tscn └── header.tscn ├── leaves ├── bt_blackboard_set.gd ├── bt_callable.gd ├── bt_leaf.gd ├── bt_particle_emitter.gd └── bt_subtree.gd ├── modifiers ├── bt_always_fail.gd ├── bt_always_succeed.gd ├── bt_inverter.gd └── bt_modifier.gd ├── plugin.cfg └── utilities ├── bt_blackboard_value.gd └── bt_utility.gd /addons/behavior_tree/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 MBoqui 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/behavior_tree/bt_agent.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTAgent 3 | extends Node 4 | 5 | 6 | 7 | @export var target_tree : BTNode 8 | 9 | 10 | var blackboard := BTBlackboard.new() 11 | 12 | var active_tree : BTRunningTree: 13 | set = _set_active_tree 14 | var debug := false 15 | 16 | var _running_tree := BTRunningTree.new() 17 | 18 | 19 | 20 | func _ready() -> void: 21 | _running_tree.initialize(self, target_tree) 22 | 23 | 24 | 25 | func tick(): 26 | _running_tree.result_reported.connect(_on_tree_result_reported) 27 | 28 | _running_tree.tick() 29 | 30 | active_tree = null 31 | 32 | _running_tree.result_reported.disconnect(_on_tree_result_reported) 33 | 34 | 35 | func set_active_subtree(subtree : BTRunningTree, subtree_node : BTNode = null) -> void: 36 | active_tree.set_active_subtree(subtree, subtree_node) 37 | 38 | 39 | func has_node_memory(node : BTNode) -> bool: 40 | return active_tree.has_node_memory(node) 41 | 42 | 43 | func set_node_memory(node : BTNode, value : Variant) -> void: 44 | active_tree.set_node_memory(node, value) 45 | 46 | 47 | func get_node_memory(node : BTNode) -> Variant: 48 | return active_tree.get_node_memory(node) 49 | 50 | 51 | func set_running_node(node : BTNode) -> void: 52 | active_tree.set_running_node(node) 53 | 54 | 55 | func remove_single_running_node(node : BTNode) -> void: 56 | active_tree.remove_single_running_node(node) 57 | 58 | 59 | func try_abort_running_node(node : BTNode, include_self := false) -> bool: 60 | return active_tree.try_abort_running_node(node, include_self) 61 | 62 | 63 | func get_monitor_is_dirty(monitor) -> bool: 64 | return active_tree.get_monitor_is_dirty(monitor) 65 | 66 | 67 | func set_active_monitor(monitor, value : Variant) -> void: 68 | active_tree.set_active_monitor(monitor, value) 69 | 70 | 71 | func remove_active_monitor(monitor) -> void: 72 | active_tree.remove_active_monitor(monitor) 73 | 74 | 75 | func register_debug_return(node_index : int, result : BTItem.BTState) -> void: 76 | active_tree.register_debug_return(node_index, result) 77 | 78 | 79 | 80 | func _on_tree_result_reported(_agent : BTAgent, result : BTItem.BTState) -> void: 81 | if result != BTItem.BTState.RUNNING: 82 | _running_tree.clear() 83 | 84 | 85 | if OS.is_debug_build() and debug: 86 | var debug_values = _running_tree.get_debug_return() 87 | 88 | EngineDebugger.send_message(BTDebugger.DEBUG_TREE_MESSAGE, debug_values) 89 | 90 | _running_tree.clear_debug_return() 91 | 92 | 93 | 94 | func _set_active_tree(tree : BTRunningTree) -> void: 95 | if active_tree != null: 96 | active_tree.activate(false) 97 | 98 | active_tree = tree 99 | 100 | if active_tree != null: 101 | active_tree.activate(true) 102 | -------------------------------------------------------------------------------- /addons/behavior_tree/bt_blackboard.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTBlackboard 3 | extends RefCounted 4 | 5 | 6 | 7 | signal dirty_key_reported(key) 8 | 9 | 10 | var _blackboard: Dictionary = {} 11 | 12 | 13 | func set_value(key: Variant, value: Variant) -> void: 14 | _blackboard[key] = value 15 | dirty_key_reported.emit(key) 16 | 17 | 18 | func get_value(key: Variant, default_value: Variant = null) -> Variant: 19 | return _blackboard.get(key, default_value) 20 | 21 | 22 | func has_value(key: Variant) -> bool: 23 | if not _blackboard.has(key): return false 24 | 25 | return _blackboard[key] != null 26 | 27 | 28 | func has_key(key: Variant) -> bool: 29 | return _blackboard.has(key) 30 | 31 | 32 | func erase_value(key: Variant) -> void: 33 | if _blackboard.has(key): 34 | _blackboard[key] = null 35 | dirty_key_reported.emit(key) 36 | 37 | 38 | func erase_key(key: Variant) -> void: 39 | _blackboard.erase(key) 40 | dirty_key_reported.emit(key) 41 | -------------------------------------------------------------------------------- /addons/behavior_tree/bt_debugger.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTDebugger 3 | extends EditorDebuggerPlugin 4 | 5 | 6 | 7 | const PLUGIN_MESSAGE := "behavior_tree" 8 | const DEBUG_TREE_MESSAGE := PLUGIN_MESSAGE + ":debug_tree" 9 | 10 | 11 | signal debug_tree(data) 12 | signal debug_mode_changed(value) 13 | 14 | 15 | func _has_capture(prefix : String) -> bool: 16 | return prefix == PLUGIN_MESSAGE 17 | 18 | 19 | func _capture(message : String, data : Array, session_id : int) -> bool: 20 | match message: 21 | DEBUG_TREE_MESSAGE: 22 | debug_tree.emit(data) 23 | return true 24 | 25 | return false 26 | 27 | 28 | func _setup_session(session_id: int) -> void: 29 | var session = get_session(session_id) 30 | session.started.connect(_on_session_started) 31 | session.stopped.connect(_on_session_ended.bind(session)) 32 | 33 | 34 | 35 | func _on_session_started() -> void: 36 | for session in get_sessions(): 37 | if session.is_active(): 38 | debug_mode_changed.emit(true) 39 | return 40 | 41 | 42 | func _on_session_ended(ended_session : EditorDebuggerSession) -> void: 43 | for session in get_sessions(): 44 | if session.is_active(): 45 | return 46 | 47 | debug_mode_changed.emit(false) 48 | -------------------------------------------------------------------------------- /addons/behavior_tree/bt_editor_plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTEditorPlugin 3 | extends EditorPlugin 4 | 5 | 6 | 7 | var bt_node_resources : Array[BTNode] 8 | 9 | 10 | var _paths : Array[String] = [ 11 | "res://addons/behavior_tree", 12 | ] 13 | 14 | var _editor : BTEditor = preload ("graph/editor.tscn").instantiate() 15 | var _bottom_panel_button : Button 16 | 17 | var _file_dialog : EditorFileDialog 18 | var _type_popup : PopupMenu 19 | 20 | var _debugger : BTDebugger 21 | var _editor_interface : EditorInterface 22 | 23 | 24 | 25 | func _enter_tree(): 26 | _debugger = BTDebugger.new() 27 | _debugger.debug_tree.connect(_on_Debugger_debug_tree) 28 | _debugger.debug_mode_changed.connect(_on_Debugger_debug_mode_changed) 29 | add_debugger_plugin(_debugger) 30 | 31 | _bottom_panel_button = add_control_to_bottom_panel(_editor, "Behavior Tree") 32 | 33 | _file_dialog = EditorFileDialog.new() 34 | _file_dialog.access = EditorFileDialog.ACCESS_RESOURCES 35 | _file_dialog.add_filter("*.tres, *.res", "Resources") 36 | _file_dialog.get_cancel_button().pressed.connect(_on_FileDialog_canceled) 37 | 38 | _type_popup = PopupMenu.new() 39 | _type_popup.close_requested.connect(_on_TypePopup_close_requested) 40 | 41 | _load_bt_resources() 42 | 43 | _editor_interface = get_editor_interface() 44 | var base_control = _editor_interface.get_base_control() 45 | base_control.add_child(_file_dialog) 46 | base_control.add_child(_type_popup) 47 | 48 | _editor.plugin = self 49 | 50 | 51 | func _exit_tree(): 52 | remove_debugger_plugin(_debugger) 53 | remove_control_from_bottom_panel(_editor) 54 | 55 | _file_dialog.queue_free() 56 | _type_popup.queue_free() 57 | 58 | 59 | 60 | func inspect_object(object : Object, for_property := "", inspector_only := false) -> void: 61 | _editor_interface.inspect_object(object, for_property, inspector_only) 62 | 63 | 64 | func request_file_path(load_mode : bool) -> String: 65 | _file_dialog.visible = true 66 | 67 | var screen_size : Vector2i = get_viewport().get_visible_rect().end 68 | 69 | _file_dialog.size = Vector2i(700, 400) 70 | _file_dialog.position = (screen_size - _file_dialog.size) / 2 71 | 72 | if load_mode: 73 | _file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE 74 | _file_dialog.title = "Load Behavior Tree" 75 | else: 76 | _file_dialog.file_mode = EditorFileDialog.FILE_MODE_SAVE_FILE 77 | _file_dialog.title = "Save Behavior Tree as" 78 | 79 | var path : String = await _file_dialog.file_selected 80 | 81 | return path 82 | 83 | 84 | func request_bt_node_type(position : Vector2, has_leaves := true) -> BTNode: 85 | _type_popup.visible = true 86 | _type_popup.position = position 87 | 88 | _reload_type_popup(has_leaves) 89 | 90 | var index : int = await _type_popup.index_pressed 91 | 92 | if index == -1: return null 93 | 94 | return bt_node_resources[index].duplicate() 95 | 96 | 97 | 98 | func _load_bt_resources(): 99 | bt_node_resources = [] 100 | 101 | for path in _paths: 102 | _load_resources_in_folder(path) 103 | 104 | 105 | func _load_resources_in_folder(folder_path : String): 106 | if not DirAccess.dir_exists_absolute(folder_path): 107 | push_error("Behavior Tree: path %s was not found." %folder_path) 108 | return 109 | 110 | var dir = DirAccess.open(folder_path) 111 | dir.list_dir_begin() 112 | var file_name = dir.get_next() 113 | 114 | while(file_name!=""): 115 | var full_path = folder_path+"/"+file_name 116 | 117 | if dir.current_is_dir(): 118 | _load_resources_in_folder(full_path) 119 | file_name = dir.get_next() 120 | continue 121 | 122 | if not ResourceLoader.exists(full_path): 123 | file_name = dir.get_next() 124 | continue 125 | 126 | var file = load(full_path) 127 | 128 | if not file.has_method("new"): 129 | file_name = dir.get_next() 130 | continue 131 | 132 | var resource = file.new() 133 | 134 | if (resource as BTNode) == null: 135 | file_name = dir.get_next() 136 | continue 137 | 138 | var is_valid_type : bool = resource.get_bt_type_name() != "" 139 | 140 | if is_valid_type: 141 | bt_node_resources.append(resource) 142 | 143 | file_name = dir.get_next() 144 | 145 | 146 | func _reload_type_popup(has_leaves : bool) -> void: 147 | _type_popup.clear() 148 | 149 | for bt_node in bt_node_resources: 150 | var node_name = bt_node.get_bt_type_name() 151 | 152 | if bt_node is BTLeaf and not has_leaves: continue 153 | 154 | _type_popup.add_item(node_name) 155 | 156 | 157 | 158 | func _on_FileDialog_canceled() -> void: 159 | _file_dialog.file_selected.emit("") 160 | 161 | 162 | func _on_TypePopup_close_requested() -> void: 163 | _type_popup.index_pressed.emit(-1) 164 | 165 | 166 | func _on_Debugger_debug_tree(data : Array) -> void: 167 | _editor.debug_tree(data) 168 | 169 | 170 | func _on_Debugger_debug_mode_changed(value : bool) -> void: 171 | _editor.debug_mode = value 172 | -------------------------------------------------------------------------------- /addons/behavior_tree/bt_expression.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTExpression 3 | 4 | 5 | 6 | var text : String = "": 7 | set(value): 8 | if value == text: return 9 | 10 | text = value 11 | _parse_expression(text) 12 | 13 | 14 | var _expression : Expression 15 | 16 | 17 | 18 | func is_valid() -> bool: 19 | return _expression != null 20 | 21 | 22 | func execute(agent : BTAgent) -> Variant: 23 | var arguments : Array[Variant] = [agent, agent.blackboard] 24 | return _execute_expression(arguments) 25 | 26 | 27 | 28 | func _parse_expression(input : String) -> void: 29 | if input == "": input = "null" 30 | 31 | var new_expression := Expression.new() 32 | var error_code := new_expression.parse(input, ["agent", "blackboard"]) 33 | 34 | if error_code != OK: 35 | printerr("BTExpression: Unable to parse expression '%s' : %s" % [input, new_expression.get_error_text()]) 36 | _expression = null 37 | return 38 | 39 | _expression = new_expression 40 | 41 | 42 | func _execute_expression(arguments : Array[Variant]) -> Variant: 43 | if _expression == null: 44 | _parse_expression(text) 45 | 46 | if _expression == null: return null 47 | 48 | var result = _expression.execute(arguments, self, true) 49 | if _expression.has_execute_failed(): 50 | printerr("BTExpression: Unable to execute expression '%s' : %s" % [text, _expression.get_error_text()]) 51 | 52 | return result 53 | -------------------------------------------------------------------------------- /addons/behavior_tree/bt_item.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTItem 3 | extends Resource 4 | 5 | 6 | 7 | enum BTState { 8 | FAILURE, 9 | SUCCESS, 10 | RUNNING, 11 | } 12 | 13 | enum BTMonitorType { 14 | NONE, 15 | BOTH, 16 | SELF, 17 | LOWER_PRIORITY, 18 | } 19 | 20 | enum BTUtilityCompoundMode{ 21 | MULTIPLY, 22 | AVERAGE, 23 | MIN, 24 | MAX, 25 | } 26 | 27 | 28 | @export var name : String = "": 29 | set(value): 30 | name = value 31 | emit_changed() 32 | 33 | 34 | 35 | func _init() -> void: 36 | name = get_bt_type_name() 37 | 38 | 39 | 40 | func get_bt_type_name() -> String: 41 | return _get_bt_type_name() 42 | 43 | 44 | 45 | func _get_bt_type_name() -> String: 46 | return "" 47 | -------------------------------------------------------------------------------- /addons/behavior_tree/bt_node.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTNode 3 | extends BTItem 4 | 5 | 6 | 7 | signal result_reported(agent, result) 8 | signal interrupted(agent, try_abort, trigger_child) 9 | 10 | 11 | @export var utilities : Array[BTUtility] = []: 12 | set = _set_utilities 13 | @export var conditions : Array[BTCondition] = []: 14 | set = _set_conditions 15 | @export var modifiers : Array[BTModifier] = []: 16 | set = _set_modifiers 17 | @export var utility_compound_mode : BTUtilityCompoundMode 18 | 19 | 20 | 21 | var parent : BTComposite: 22 | set = _set_parent 23 | var graph_position : Vector2: 24 | set = _set_graph_position 25 | 26 | var root : BTNode = null 27 | 28 | 29 | var sibling_index : int: 30 | set = _set_sibling_index 31 | var tree_index : int = -1 32 | 33 | ## Stores an array of the monitors affected by a blackboard key. 34 | ## Compiled at initialization time. Monitors can be BTCompositeUtility or BTCondition. 35 | var _keys_monitors : Dictionary = {} 36 | var _is_initialized := false 37 | 38 | 39 | 40 | func _get_property_list() -> Array: 41 | var properties = [] 42 | 43 | properties.append({ 44 | "name": "graph_position", 45 | "type": TYPE_VECTOR2, 46 | "usage": PROPERTY_USAGE_STORAGE, 47 | }) 48 | 49 | properties.append({ 50 | "name": "sibling_index", 51 | "type": TYPE_INT, 52 | "usage": PROPERTY_USAGE_STORAGE, 53 | }) 54 | 55 | properties.append({ 56 | "name": "tree_index", 57 | "type": TYPE_INT, 58 | "usage": PROPERTY_USAGE_STORAGE, 59 | }) 60 | 61 | return properties 62 | 63 | 64 | 65 | func duplicate_deep() -> BTNode: 66 | var new_node := duplicate(true) 67 | 68 | for i in len(conditions): 69 | var condition := conditions[i] 70 | new_node.conditions[i] = condition.duplicate(true) 71 | 72 | for i in len(modifiers): 73 | var modifier := modifiers[i] 74 | new_node.modifiers[i] = modifier.duplicate(true) 75 | 76 | return new_node 77 | 78 | 79 | 80 | ## Executed when node is called to tick. 81 | func _execute_tick(_agent : BTAgent) -> void: 82 | pass 83 | 84 | 85 | ## Executed when there is an interruption on a monitor or in parallel nodes in First Return mode. 86 | ## Use this method to cleanly close the execution of running nodes in case of interruptions or erly parallel return. 87 | ## Outside of these cases this cleanup should occur in the _execute_... methods. 88 | ## You need to load the node memory inside your abort code if you need those values, 89 | ## they will not be loaded and may cause unexpected behavior. 90 | func _abort(_agent : BTAgent) -> void: 91 | pass 92 | 93 | 94 | ## Executed when there is an interruption on a monitor. 95 | ## Executed only on the nodes on the path from root to the node with the monitor that triggered the interruption. 96 | func _interrupt(_agent : BTAgent, _trigger_child : BTNode) -> void: 97 | pass 98 | 99 | 100 | ## Called before a node is ticked. 101 | ## Use this method to setup the necessary variables for execution of the node. 102 | func _setup_execution(_agent : BTAgent) -> void: 103 | pass 104 | 105 | 106 | ## Called when a node is ending its execution for this tick but is still running. 107 | ## Use this to store the current running variables of the node. 108 | ## This is necessary because this Resource might be reused in other trees and any value might be overriden. 109 | func _save_memory(_agent : BTAgent) -> void: 110 | pass 111 | 112 | 113 | 114 | func tick(agent : BTAgent) -> void: 115 | agent.set_running_node(self) 116 | 117 | _setup_execution(agent) 118 | 119 | if not _conditions_satisfied(agent): 120 | _report_result(agent, BTState.FAILURE) 121 | return 122 | 123 | _execute_tick(agent) 124 | 125 | 126 | func abort(agent : BTAgent) -> void: 127 | _abort(agent) 128 | agent.remove_single_running_node(self) 129 | 130 | 131 | func initialize(is_root := true) -> void: 132 | if _is_initialized: return 133 | _is_initialized = true 134 | 135 | if is_root: 136 | root = self 137 | 138 | for condition in conditions: 139 | if condition.monitor_type != BTMonitorType.NONE: 140 | condition._register_monitored_keys(root) 141 | 142 | condition.interrupted.connect(_on_interrupted) 143 | condition._initialize() 144 | 145 | for utility in utilities: 146 | utility._initialize() 147 | 148 | 149 | func recalculate_tree_index(value := 0) -> int: 150 | tree_index = value 151 | return tree_index + 1 152 | 153 | 154 | func get_keys_monitors() -> Dictionary: 155 | return _keys_monitors 156 | 157 | 158 | func register_key_monitor(monitor, key : Variant) -> void: 159 | if not _keys_monitors.has(key): 160 | _keys_monitors[key] = [] 161 | 162 | _keys_monitors[key].append(monitor) 163 | 164 | 165 | func get_utility_value(agent : BTAgent) -> float: 166 | if utilities == null or utilities.is_empty(): 167 | return 0 168 | 169 | var utility_values : PackedFloat64Array 170 | 171 | for utility in utilities: 172 | var value := utility._get_utility_value(agent) 173 | value = clampf(value, 0, 1) 174 | utility_values.append(value) 175 | 176 | var result := compound_utilities(utility_compound_mode, utility_values) 177 | 178 | return clamp(result, 0, 1) 179 | 180 | 181 | func compound_utilities(mode : BTUtilityCompoundMode, utilities : PackedFloat64Array) -> float: 182 | var result : float 183 | 184 | match mode: 185 | BTUtilityCompoundMode.MULTIPLY: 186 | result = 1 187 | 188 | for value in utilities: 189 | result *= value 190 | 191 | if result == 0: 192 | break 193 | 194 | BTUtilityCompoundMode.AVERAGE: 195 | result = 0 196 | 197 | for value in utilities: 198 | result += value 199 | 200 | result /= len(utilities) 201 | 202 | BTUtilityCompoundMode.MIN: 203 | result = 1 204 | 205 | for value in utilities: 206 | if value < result: 207 | result = value 208 | 209 | if result == 0: 210 | break 211 | 212 | BTUtilityCompoundMode.MAX: 213 | result = 0 214 | 215 | for value in utilities: 216 | if value > result: 217 | result = value 218 | 219 | if result == 1: 220 | break 221 | 222 | return result 223 | 224 | 225 | 226 | func _conditions_satisfied(agent : BTAgent) -> bool: 227 | var result : bool 228 | for condition in conditions: 229 | result = condition._check_condition(agent) 230 | 231 | if ( 232 | condition.monitor_type == BTMonitorType.BOTH 233 | or condition.monitor_type == BTMonitorType.SELF 234 | ): 235 | agent.set_active_monitor(condition, result) 236 | 237 | if not result: 238 | return false 239 | 240 | return true 241 | 242 | 243 | func _apply_modifiers(agent : BTAgent, result : BTState) -> BTState: 244 | for modifier in modifiers: 245 | result = modifier._apply_modifier(agent, result) 246 | return result 247 | 248 | 249 | func _report_result(agent : BTAgent, result : BTState) -> void: 250 | result = _apply_modifiers(agent, result) 251 | 252 | _terminate(agent, result) 253 | 254 | if OS.is_debug_build() and agent.debug: 255 | agent.register_debug_return(tree_index, result) 256 | 257 | result_reported.emit(agent, result) 258 | 259 | 260 | func _terminate(agent : BTAgent, result : BTState) -> void: 261 | if result == BTState.RUNNING: 262 | _save_memory(agent) 263 | return 264 | 265 | agent.remove_single_running_node(self) 266 | 267 | for condition in conditions: 268 | match condition.monitor_type: 269 | BTMonitorType.SELF: 270 | agent.remove_active_monitor(condition) 271 | BTMonitorType.LOWER_PRIORITY: 272 | agent.set_active_monitor(condition, condition._check_condition(agent)) 273 | 274 | 275 | 276 | func _on_interrupted(agent : BTAgent, try_abort : bool, trigger_child : BTNode = null) -> void: 277 | if try_abort: 278 | # if managed to abort at this node, stop trying to abort 279 | try_abort = not agent.try_abort_running_node(self) 280 | 281 | _interrupt(agent, trigger_child) 282 | 283 | interrupted.emit(agent, try_abort, self) 284 | 285 | 286 | 287 | func _set_utilities(value : Array[BTUtility]) -> void: 288 | for utility in utilities: 289 | if utility == null: continue 290 | utility.changed.disconnect(emit_changed) 291 | 292 | utilities = value 293 | 294 | for utility in utilities: 295 | if utility == null: continue 296 | utility.changed.connect(emit_changed) 297 | 298 | emit_changed() 299 | 300 | 301 | func _set_conditions(value : Array[BTCondition]): 302 | for condition in conditions: 303 | if condition == null: continue 304 | condition.changed.disconnect(emit_changed) 305 | 306 | conditions = value 307 | 308 | for condition in conditions: 309 | if condition == null: continue 310 | condition.changed.connect(emit_changed) 311 | 312 | emit_changed() 313 | 314 | 315 | func _set_modifiers(value : Array[BTModifier]): 316 | for modifier in modifiers: 317 | if modifier == null: continue 318 | modifier.changed.disconnect(emit_changed) 319 | 320 | modifiers = value 321 | 322 | for modifier in modifiers: 323 | if modifier == null: continue 324 | modifier.changed.connect(emit_changed) 325 | 326 | emit_changed() 327 | 328 | 329 | func _set_parent(value : BTComposite) -> void: 330 | if parent == value: return 331 | 332 | if parent != null: 333 | parent.children.erase(self) 334 | parent.reorder_children() 335 | 336 | parent = value 337 | 338 | if parent != null: 339 | if not parent.children.has(self): 340 | parent.children.append(self) 341 | parent.reorder_children() 342 | else: 343 | tree_index = -1 344 | 345 | emit_changed() 346 | 347 | 348 | func _set_graph_position(value : Vector2) -> void: 349 | graph_position = value 350 | 351 | if parent != null: 352 | parent.reorder_children() 353 | else: 354 | sibling_index = -1 355 | 356 | emit_changed() 357 | 358 | 359 | func _set_sibling_index(value): 360 | sibling_index = value 361 | emit_changed() 362 | -------------------------------------------------------------------------------- /addons/behavior_tree/bt_running_tree.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTRunningTree 3 | extends RefCounted 4 | 5 | 6 | 7 | signal result_reported(agent, result) 8 | signal interrupted(agent, try_abort, trigger_child) 9 | signal monitors_have_dirtied() 10 | 11 | 12 | var tree : BTNode 13 | 14 | var _running_nodes : Array = [] 15 | var _running_nodes_memory : Dictionary = {} 16 | var _running_leaves : Array = [] 17 | 18 | var _active_monitors : Array = [] 19 | var _monitor_memory : Dictionary = {} 20 | var _keys_monitors : Dictionary 21 | var _monitors_is_dirty : Dictionary = {} 22 | 23 | var subtrees : Dictionary = {} 24 | 25 | ## Runtime dictionary of values returned from each node in the tree. Used for debugging tree. 26 | var _debug_return_values : Dictionary = {} 27 | 28 | 29 | var _agent : BTAgent 30 | var _active_subtree : BTRunningTree 31 | var _active_subtree_node : BTNode 32 | 33 | 34 | 35 | func initialize(agent : BTAgent, target_tree : BTNode) -> void: 36 | tree = target_tree 37 | tree.initialize() 38 | _keys_monitors = tree.get_keys_monitors() 39 | 40 | _agent = agent 41 | _agent.blackboard.dirty_key_reported.connect(_on_blackboard_dirty_key_reported) 42 | 43 | 44 | func activate(value : bool) -> void: 45 | if value: 46 | tree.result_reported.connect(_on_tree_result_reported) 47 | tree.interrupted.connect(_on_tree_interrupted) 48 | else: 49 | tree.result_reported.disconnect(_on_tree_result_reported) 50 | tree.interrupted.disconnect(_on_tree_interrupted) 51 | 52 | 53 | func set_active_subtree(subtree : BTRunningTree, subtree_node : BTNode = null) -> void: 54 | if _active_subtree != null: 55 | _active_subtree.result_reported.disconnect(_on_subtree_result_reported) 56 | _active_subtree.interrupted.disconnect(_on_subtree_interrupted) 57 | 58 | _active_subtree = subtree 59 | if subtree_node != null: 60 | _active_subtree_node = subtree_node 61 | 62 | if subtrees.get(subtree_node) != null: 63 | subtree.monitors_have_dirtied.disconnect(_set_monitor_is_dirty.bind(subtree_node)) 64 | 65 | subtrees[subtree_node] = subtree 66 | subtree.monitors_have_dirtied.connect(_set_monitor_is_dirty.bind(subtree_node)) 67 | 68 | if _active_subtree != null: 69 | _active_subtree.result_reported.connect(_on_subtree_result_reported) 70 | _active_subtree.interrupted.connect(_on_subtree_interrupted) 71 | 72 | 73 | func tick(): 74 | _agent.active_tree = self 75 | 76 | _check_monitors_interrupts() 77 | 78 | if _running_leaves.is_empty(): 79 | tree.tick(_agent) 80 | else: 81 | _running_leaves[0].tick(_agent) 82 | 83 | 84 | func has_node_memory(node : BTNode) -> bool: 85 | return _running_nodes_memory.has(node) 86 | 87 | 88 | func set_node_memory(node : BTNode, value : Variant) -> void: 89 | _running_nodes_memory[node] = value 90 | 91 | 92 | func get_node_memory(node : BTNode) -> Variant: 93 | return _running_nodes_memory[node] 94 | 95 | 96 | func set_running_node(node : BTNode) -> void: 97 | if _running_nodes.has(node): return 98 | 99 | _running_nodes.append(node) 100 | 101 | if not node is BTLeaf: return 102 | 103 | if _running_leaves.has(node): return 104 | 105 | _running_leaves.append(node) 106 | 107 | 108 | func remove_single_running_node(node : BTNode) -> void: 109 | _running_nodes.erase(node) 110 | 111 | _running_nodes_memory.erase(node) 112 | 113 | if node is BTLeaf: 114 | _running_leaves.erase(node) 115 | 116 | 117 | func try_abort_running_node(node : BTNode, include_self := false) -> bool: 118 | var index = _running_nodes.find(node) 119 | if index == -1 : return false 120 | 121 | var include_self_offset = 0 if include_self else 1 122 | var i = index + include_self_offset 123 | while i < len(_running_nodes): 124 | node = _running_nodes[i] 125 | node._abort(_agent) 126 | 127 | _running_nodes_memory.erase(node) 128 | if node is BTLeaf: 129 | _running_leaves.erase(node) 130 | 131 | i += 1 132 | _running_nodes.resize(index + 1) 133 | 134 | return true 135 | 136 | 137 | func get_monitor_is_dirty(monitor) -> bool: 138 | return _monitors_is_dirty[monitor] as bool 139 | 140 | 141 | func set_active_monitor(monitor, value : Variant) -> void: 142 | if not _active_monitors.has(monitor): 143 | _active_monitors.append(monitor) 144 | 145 | _monitor_memory[monitor] = value 146 | _set_monitor_is_dirty(monitor, false) 147 | 148 | 149 | func remove_active_monitor(monitor) -> void: 150 | _active_monitors.erase(monitor) 151 | _monitor_memory.erase(monitor) 152 | 153 | 154 | func has_active_monitors() -> bool: 155 | return not _active_monitors.is_empty() 156 | 157 | 158 | func register_debug_return(node_index : int, result : BTItem.BTState) -> void: 159 | _debug_return_values[node_index] = result 160 | 161 | 162 | func get_debug_return() -> Array: 163 | var subtrees_debug : Dictionary = {} 164 | for subtree_node in subtrees: 165 | var subtree = subtrees[subtree_node] as BTRunningTree 166 | subtrees_debug[subtree_node.tree_index] = subtree.get_debug_return() 167 | 168 | return [tree.resource_path, _debug_return_values, subtrees_debug] 169 | 170 | 171 | func clear_debug_return() -> void: 172 | _debug_return_values = {} 173 | 174 | for subtree_node in subtrees: 175 | subtrees[subtree_node].clear_debug_return() 176 | 177 | 178 | func clear() -> void: 179 | for subtree in subtrees: 180 | subtrees[subtree].clear() 181 | 182 | _active_monitors.clear() 183 | _monitor_memory.clear() 184 | 185 | subtrees.clear() 186 | 187 | 188 | func _check_monitors_interrupts() -> bool: 189 | var local_active_monitors := _active_monitors.duplicate() 190 | var local_monitor_memory := _monitor_memory.duplicate() 191 | 192 | for i in len(local_active_monitors): 193 | var monitor = local_active_monitors[i] 194 | 195 | if not get_monitor_is_dirty(monitor): continue 196 | 197 | if monitor is BTSubtree: 198 | if monitor.check_monitor_interrupt(_agent, local_monitor_memory[monitor]): 199 | return true 200 | else: 201 | continue 202 | 203 | var new_value = monitor.get_monitor_value(_agent) 204 | var old_value = local_monitor_memory[monitor] 205 | 206 | if old_value != new_value: 207 | _interrupt_monitor(i) 208 | if ( 209 | monitor.monitor_type == BTItem.BTMonitorType.BOTH 210 | or monitor.monitor_type == BTItem.BTMonitorType.SELF 211 | ): 212 | set_active_monitor(monitor, new_value) 213 | return true 214 | else: 215 | _set_monitor_is_dirty(monitor, false) 216 | 217 | return false 218 | 219 | 220 | func _interrupt_monitor(index : int) -> void: 221 | var i = index 222 | while i < len(_active_monitors): 223 | var monitor = _active_monitors[i] 224 | _monitor_memory.erase(monitor) 225 | i += 1 226 | 227 | var monitor = _active_monitors[index] 228 | _active_monitors.resize(index) 229 | monitor.interrupt(_agent) 230 | 231 | 232 | func _set_monitor_is_dirty(monitor, dirty := true) -> void: 233 | _monitors_is_dirty[monitor] = dirty 234 | 235 | 236 | 237 | func _on_blackboard_dirty_key_reported(key : Variant) -> void: 238 | if not _keys_monitors.has(key): return 239 | 240 | for monitor in _keys_monitors[key]: 241 | _set_monitor_is_dirty(monitor, true) 242 | 243 | monitors_have_dirtied.emit() 244 | 245 | 246 | func _on_tree_result_reported(agent : BTAgent, result : BTItem.BTState) -> void: 247 | result_reported.emit(agent, result) 248 | 249 | 250 | func _on_tree_interrupted(agent : BTAgent, try_abort : bool, trigger_child : BTNode = null) -> void: 251 | interrupted.emit(agent, try_abort) 252 | 253 | 254 | func _on_subtree_result_reported(agent : BTAgent, result : BTItem.BTState) -> void: 255 | _agent.active_tree = self 256 | 257 | set_active_subtree(null) 258 | 259 | _active_subtree_node.subtree_response(agent, result) 260 | 261 | 262 | func _on_subtree_interrupted(agent : BTAgent, try_abort : bool, trigger_child : BTNode = null) -> void: 263 | _agent.active_tree = self 264 | 265 | set_active_subtree(null) 266 | 267 | _active_subtree_node._on_interrupted(agent, try_abort) 268 | -------------------------------------------------------------------------------- /addons/behavior_tree/composites/bt_composite.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTComposite 3 | extends BTNode 4 | 5 | 6 | 7 | ## Use the children utility values as the utilities of this node. 8 | ## Ignored if any utility is set for this node. 9 | ## Ignores children that have no [BTUtility], unless inherit_children_utility is true for the child. 10 | ## CAUTION: activating this mode can cause performance problems since the same BTUtility will be 11 | ## called every time a node with inherit_children_utility == true above it is called. 12 | ## This will be optimized sometime in the future, but for now, beware of using this too much or 13 | ## nesting nodes with this mode on. 14 | @export var inherit_children_utility := false 15 | 16 | var children : Array[BTNode] = [] 17 | 18 | 19 | func _get_property_list() -> Array: 20 | var properties = [] 21 | 22 | properties.append({ 23 | "name": "children", 24 | "type": TYPE_ARRAY, 25 | "usage": PROPERTY_USAGE_STORAGE, 26 | "hint": PROPERTY_HINT_RESOURCE_TYPE, 27 | "hint_string": "BTNode", 28 | }) 29 | 30 | return properties 31 | 32 | 33 | 34 | func _execute_response(_agent : BTAgent, _result : BTState) -> void: 35 | pass 36 | 37 | 38 | 39 | func duplicate_deep() -> BTNode: 40 | var new_node := super.duplicate_deep() 41 | 42 | for i in len(children): 43 | var child := children[i] 44 | new_node.children[i] = child.duplicate_deep() 45 | 46 | return new_node 47 | 48 | 49 | func initialize(is_root := true) -> void: 50 | if _is_initialized: return 51 | 52 | super.initialize(is_root) 53 | 54 | for child in children: 55 | child.interrupted.connect(_on_interrupted) 56 | child.result_reported.connect(_on_child_result_reported) 57 | 58 | child.root = self.root 59 | 60 | child.initialize(false) 61 | 62 | 63 | func abort(agent : BTAgent) -> void: 64 | super.abort(agent) 65 | 66 | for child in children: 67 | child.abort(agent) 68 | 69 | 70 | 71 | func get_utility_value(agent : BTAgent) -> float: 72 | if not inherit_children_utility or (utilities != null and not utilities.is_empty()): 73 | return super.get_utility_value(agent) 74 | 75 | var utility_values : PackedFloat64Array 76 | 77 | for child in children: 78 | var value := child.get_utility_value(agent) 79 | value = clampf(value, 0, 1) 80 | utility_values.append(value) 81 | 82 | var result := compound_utilities(utility_compound_mode, utility_values) 83 | 84 | return clamp(result, 0, 1) 85 | 86 | 87 | func recalculate_tree_index(value := 0) -> int: 88 | var next_index = super.recalculate_tree_index(value) 89 | 90 | for child in children: 91 | next_index = child.recalculate_tree_index(next_index) 92 | 93 | return next_index 94 | 95 | 96 | func reorder_children() -> void: 97 | children.sort_custom(func(a : BTNode, b : BTNode): return a.graph_position.y < b.graph_position.y) 98 | 99 | for i in len(children): 100 | children[i].sibling_index = i 101 | 102 | 103 | 104 | func _on_child_result_reported(agent : BTAgent, result : BTState) -> void: 105 | _setup_execution(agent) 106 | 107 | _execute_response(agent, result) 108 | -------------------------------------------------------------------------------- /addons/behavior_tree/composites/bt_composite_utility.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTCompositeUtility 3 | extends BTComposite 4 | 5 | 6 | 7 | @export var monitor_type : BTMonitorType 8 | 9 | 10 | func tick(agent : BTAgent) -> void: 11 | agent.set_running_node(self) 12 | 13 | _setup_execution(agent) 14 | 15 | if not _conditions_satisfied(agent): 16 | _report_result(agent, BTState.FAILURE) 17 | return 18 | 19 | if (monitor_type == BTMonitorType.BOTH or monitor_type == BTMonitorType.SELF): 20 | agent.set_active_monitor(self, get_monitor_value(agent)) 21 | 22 | _execute_tick(agent) 23 | 24 | 25 | func initialize(is_root := true) -> void: 26 | super.initialize(is_root) 27 | 28 | if monitor_type == BTMonitorType.NONE: return 29 | 30 | for child in children: 31 | for utility in child.utilities: 32 | utility._register_monitored_keys(self, root) 33 | 34 | 35 | func interrupt(agent : BTAgent) -> void: 36 | _on_interrupted(agent, true) 37 | 38 | 39 | func get_monitor_value(agent : BTAgent) -> Variant: 40 | return get_children_utility_order(agent) 41 | 42 | 43 | func get_children_utility_order(agent : BTAgent) -> PackedInt32Array: 44 | var children_utilities : PackedFloat64Array = [] 45 | 46 | for child in children: 47 | var value : float = child.get_utility_value(agent) 48 | children_utilities.append(value) 49 | 50 | var utilities_sorted := children_utilities.duplicate() 51 | utilities_sorted.sort() 52 | 53 | var children_utility_order : PackedInt32Array = [] 54 | 55 | for utility in utilities_sorted: 56 | var child_index := children_utilities.rfind(utility) 57 | 58 | while children_utility_order.has(child_index): 59 | child_index = children_utilities.rfind(utility, child_index - 1) 60 | 61 | if child_index == -1: 62 | push_error("Behavior Tree: something went wrong calculating utility order.") 63 | return [] 64 | 65 | children_utility_order.append(child_index) 66 | 67 | children_utility_order.reverse() 68 | 69 | return children_utility_order 70 | 71 | 72 | 73 | func _terminate(agent : BTAgent, result : BTState) -> void: 74 | super._terminate(agent, result) 75 | 76 | if result == BTState.RUNNING: return 77 | 78 | match monitor_type: 79 | BTMonitorType.SELF: 80 | agent.remove_active_monitor(self) 81 | BTMonitorType.LOWER_PRIORITY: 82 | agent.set_active_monitor(self, get_monitor_value(agent)) 83 | -------------------------------------------------------------------------------- /addons/behavior_tree/composites/bt_parallel.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTParallel 3 | extends BTComposite 4 | 5 | ## The Parallel node runs all its children in their sibling_index order every frame. 6 | ## It only returns a result after all its children have been run. 7 | ## The result it returns depends on the [member parallel_mode] chosen. 8 | 9 | 10 | 11 | enum BTParallelMode { 12 | FIRST_RETURN, ## Returns the result of the first child. When the first child terminates execution, aborts other children. 13 | HAS_SUCCESS, ## Return RUNNING if any child returns running. Otherwise, return SUCCESS if at least one child returns SUCCESS. 14 | HAS_FAILURE, ## Return RUNNING if any child returns running. Otherwise, return SUCCESS if at least one child returns FAILURE. 15 | ALL_SUCCESS, ## Return RUNNING if any child returns running. Otherwise, return FAILURE if at least one child returns FAILURE. 16 | ALL_FAILURE, ## Return RUNNING if any child returns running. Otherwise, return FAILURE if at least one child returns SUCCESS. 17 | } 18 | 19 | @export var parallel_mode : BTParallelMode 20 | 21 | var _children_results : PackedInt32Array = [] 22 | var _current_child_index := 0 23 | 24 | 25 | func _get_bt_type_name() -> String: 26 | return "Parallel" 27 | 28 | 29 | func _execute_tick(agent : BTAgent) -> void: 30 | _save_memory(agent) 31 | children[_current_child_index].tick(agent) 32 | 33 | 34 | func _execute_response(agent : BTAgent, result : BTState) -> void: 35 | _children_results[_current_child_index] = result 36 | 37 | _current_child_index += 1 38 | 39 | while _current_child_index < len(children): 40 | if _children_results[_current_child_index] != BTState.RUNNING: 41 | _current_child_index += 1 42 | continue 43 | 44 | _save_memory(agent) 45 | children[_current_child_index].tick(agent) 46 | return 47 | 48 | var final_result : BTState 49 | match parallel_mode: 50 | BTParallelMode.FIRST_RETURN: 51 | final_result = _children_results[0] 52 | 53 | if final_result != BTState.RUNNING: 54 | for i in len(children): 55 | var child := children[i] 56 | var child_result := _children_results[i] 57 | if child_result == BTState.RUNNING: 58 | child.abort(agent) 59 | 60 | BTParallelMode.HAS_SUCCESS: 61 | final_result = BTState.FAILURE 62 | 63 | for child_result in _children_results: 64 | if child_result == BTState.RUNNING: 65 | final_result = BTState.RUNNING 66 | break 67 | 68 | if child_result == BTState.SUCCESS: 69 | final_result = BTState.SUCCESS 70 | 71 | BTParallelMode.HAS_FAILURE: 72 | final_result = BTState.FAILURE 73 | 74 | for child_result in _children_results: 75 | if child_result == BTState.RUNNING: 76 | final_result = BTState.RUNNING 77 | break 78 | 79 | if child_result == BTState.FAILURE: 80 | final_result = BTState.SUCCESS 81 | 82 | BTParallelMode.ALL_SUCCESS: 83 | final_result = BTState.SUCCESS 84 | 85 | for child_result in _children_results: 86 | if child_result == BTState.RUNNING: 87 | final_result = BTState.RUNNING 88 | break 89 | 90 | if child_result == BTState.FAILURE: 91 | final_result = BTState.FAILURE 92 | 93 | BTParallelMode.ALL_FAILURE: 94 | final_result = BTState.SUCCESS 95 | 96 | for child_result in _children_results: 97 | if child_result == BTState.RUNNING: 98 | final_result = BTState.RUNNING 99 | break 100 | 101 | if child_result == BTState.SUCCESS: 102 | final_result = BTState.FAILURE 103 | 104 | if final_result != BTState.RUNNING: 105 | _report_result(agent, final_result) 106 | return 107 | 108 | for i in len(_children_results): 109 | if _children_results[i] == BTState.RUNNING: 110 | _current_child_index = i 111 | break 112 | 113 | _report_result(agent, BTState.RUNNING) 114 | 115 | 116 | 117 | func _interrupt(agent : BTAgent, trigger_child : BTNode) -> void: 118 | if trigger_child == null: return 119 | 120 | _children_results[trigger_child.sibling_index] = -1 121 | _save_memory(agent) 122 | 123 | 124 | func _setup_execution(agent : BTAgent) -> void: 125 | if agent.has_node_memory(self): 126 | var memory = agent.get_node_memory(self) 127 | _children_results = memory[0] 128 | _current_child_index = memory[1] 129 | else: 130 | _current_child_index = 0 131 | _children_results = [] 132 | 133 | for child in children: 134 | _children_results.append(BTState.RUNNING) 135 | 136 | 137 | func _save_memory(agent : BTAgent) -> void: 138 | agent.set_node_memory(self, [_children_results, _current_child_index]) 139 | -------------------------------------------------------------------------------- /addons/behavior_tree/composites/bt_selector.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTSelector 3 | extends BTComposite 4 | 5 | 6 | 7 | var _current_child_index := 0 8 | 9 | 10 | 11 | func _get_bt_type_name() -> String: 12 | return "Selector" 13 | 14 | 15 | func _execute_tick(agent : BTAgent) -> void: 16 | _save_memory(agent) 17 | children[_current_child_index].tick(agent) 18 | 19 | 20 | func _execute_response(agent : BTAgent, result : BTState) -> void: 21 | if result != BTState.FAILURE: 22 | _report_result(agent, result) 23 | return 24 | 25 | _current_child_index += 1 26 | 27 | if _current_child_index >= len(children): 28 | _report_result(agent, BTState.FAILURE) 29 | return 30 | 31 | _save_memory(agent) 32 | children[_current_child_index].tick(agent) 33 | 34 | 35 | func _interrupt(agent : BTAgent, trigger_child : BTNode) -> void: 36 | if trigger_child == null: return 37 | 38 | _current_child_index = trigger_child.sibling_index 39 | _save_memory(agent) 40 | 41 | 42 | func _setup_execution(agent : BTAgent) -> void: 43 | if agent.has_node_memory(self): 44 | _current_child_index = agent.get_node_memory(self) 45 | else: 46 | _current_child_index = 0 47 | 48 | 49 | func _save_memory(agent : BTAgent) -> void: 50 | agent.set_node_memory(self, _current_child_index) 51 | -------------------------------------------------------------------------------- /addons/behavior_tree/composites/bt_sequence.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTSequence 3 | extends BTComposite 4 | 5 | 6 | var _current_child_index := 0 7 | 8 | 9 | 10 | func _get_bt_type_name() -> String: 11 | return "Sequence" 12 | 13 | 14 | func _execute_tick(agent : BTAgent) -> void: 15 | _save_memory(agent) 16 | children[_current_child_index].tick(agent) 17 | 18 | 19 | func _execute_response(agent : BTAgent, result : BTState) -> void: 20 | if result != BTState.SUCCESS: 21 | _report_result(agent, result) 22 | return 23 | 24 | _current_child_index += 1 25 | 26 | if _current_child_index >= len(children): 27 | _report_result(agent, BTState.SUCCESS) 28 | return 29 | 30 | _save_memory(agent) 31 | children[_current_child_index].tick(agent) 32 | 33 | 34 | func _interrupt(agent : BTAgent, trigger_child : BTNode) -> void: 35 | if trigger_child == null: return 36 | 37 | _current_child_index = trigger_child.sibling_index 38 | _save_memory(agent) 39 | 40 | 41 | func _setup_execution(agent : BTAgent) -> void: 42 | if agent.has_node_memory(self): 43 | _current_child_index = agent.get_node_memory(self) 44 | else: 45 | _current_child_index = 0 46 | 47 | 48 | func _save_memory(agent : BTAgent) -> void: 49 | agent.set_node_memory(self, _current_child_index) 50 | -------------------------------------------------------------------------------- /addons/behavior_tree/composites/bt_utility_selector.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTUtilitySelector 3 | extends BTCompositeUtility 4 | 5 | ## This node is very similar to [BTSelector], but instead of ticking its children according to 6 | ## their [member sibling_index] order, it ticks them according to their utility values, in 7 | ## decreasing order. If two children have the same utility value, their sibling_index breaks ties. 8 | 9 | 10 | 11 | var _children_utility_order : PackedInt32Array 12 | var _current_order_index := 0 13 | 14 | 15 | 16 | func _get_bt_type_name() -> String: 17 | return "Utility Selector" 18 | 19 | 20 | func _execute_tick(agent : BTAgent) -> void: 21 | _save_memory(agent) 22 | var child_index = _children_utility_order[_current_order_index] 23 | children[child_index].tick(agent) 24 | 25 | 26 | func _execute_response(agent : BTAgent, result : BTState) -> void: 27 | if result != BTState.FAILURE: 28 | _report_result(agent, result) 29 | return 30 | 31 | _current_order_index += 1 32 | 33 | if _current_order_index >= len(_children_utility_order): 34 | _report_result(agent, BTState.FAILURE) 35 | return 36 | 37 | _save_memory(agent) 38 | var child_index = _children_utility_order[_current_order_index] 39 | children[child_index].tick(agent) 40 | 41 | 42 | func _interrupt(agent : BTAgent, trigger_child : BTNode) -> void: 43 | if trigger_child == null: 44 | _children_utility_order = get_children_utility_order(agent) 45 | _current_order_index = 0 46 | else: 47 | _current_order_index = _children_utility_order.find(trigger_child.sibling_index) 48 | 49 | _save_memory(agent) 50 | 51 | 52 | func _setup_execution(agent : BTAgent) -> void: 53 | if agent.has_node_memory(self): 54 | var memory = agent.get_node_memory(self) 55 | 56 | _children_utility_order = memory[0] 57 | _current_order_index = memory[1] 58 | return 59 | 60 | _children_utility_order = get_children_utility_order(agent) 61 | _current_order_index = 0 62 | 63 | 64 | func _save_memory(agent : BTAgent) -> void: 65 | agent.set_node_memory(self, [_children_utility_order, _current_order_index]) 66 | -------------------------------------------------------------------------------- /addons/behavior_tree/composites/bt_utility_sequence.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTUtilitySequence 3 | extends BTCompositeUtility 4 | 5 | ## This node is very similar to [BTSequence], but instead of ticking its children according to 6 | ## their [member sibling_index] order, it ticks them according to their utility values, in 7 | ## decreasing order. If two children have the same utility value, their sibling_index breaks ties. 8 | 9 | 10 | var _children_utility_order : PackedInt32Array 11 | var _current_order_index := 0 12 | 13 | 14 | 15 | func _get_bt_type_name() -> String: 16 | return "Utility Sequence" 17 | 18 | 19 | func _execute_tick(agent : BTAgent) -> void: 20 | _save_memory(agent) 21 | var child_index = _children_utility_order[_current_order_index] 22 | children[child_index].tick(agent) 23 | 24 | 25 | func _execute_response(agent : BTAgent, result : BTState) -> void: 26 | if result != BTState.SUCCESS: 27 | _report_result(agent, result) 28 | return 29 | 30 | _current_order_index += 1 31 | 32 | if _current_order_index >= len(_children_utility_order): 33 | _report_result(agent, BTState.SUCCESS) 34 | return 35 | 36 | _save_memory(agent) 37 | var child_index = _children_utility_order[_current_order_index] 38 | children[child_index].tick(agent) 39 | 40 | 41 | func _interrupt(agent : BTAgent, trigger_child : BTNode) -> void: 42 | if trigger_child == null: 43 | _children_utility_order = get_children_utility_order(agent) 44 | _current_order_index = 0 45 | else: 46 | _current_order_index = _children_utility_order.find(trigger_child.sibling_index) 47 | 48 | _save_memory(agent) 49 | 50 | 51 | func _setup_execution(agent : BTAgent) -> void: 52 | if agent.has_node_memory(self): 53 | var memory = agent.get_node_memory(self) 54 | 55 | _children_utility_order = memory[0] 56 | _current_order_index = memory[1] 57 | return 58 | 59 | _children_utility_order = get_children_utility_order(agent) 60 | _current_order_index = 0 61 | 62 | 63 | func _save_memory(agent : BTAgent) -> void: 64 | agent.set_node_memory(self, [_children_utility_order, _current_order_index]) 65 | -------------------------------------------------------------------------------- /addons/behavior_tree/conditions/bt_blackboard_is.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTBlackboardIs 3 | extends BTCondition 4 | 5 | 6 | @export var key : String = "" 7 | 8 | @export_multiline var expression : String = "": 9 | set(value): 10 | if value == expression: return 11 | 12 | expression = value 13 | _expression.text = expression 14 | 15 | 16 | 17 | var _expression := BTExpression.new() 18 | 19 | 20 | 21 | func _get_bt_type_name() -> String: 22 | return "Blackboard Is" 23 | 24 | 25 | func _check_condition(agent : BTAgent) -> bool: 26 | var stored_value = agent.blackboard.get_value(key) 27 | var target_value = _expression.execute(agent) 28 | 29 | if stored_value == target_value: 30 | return true 31 | 32 | return false 33 | 34 | 35 | func _initialize() -> void: 36 | if not _expression.is_valid(): 37 | push_error("Expression is not valid") 38 | if key == null or key.is_empty(): 39 | push_error("Key must be set") 40 | 41 | 42 | func _register_monitored_keys(root : BTNode) -> void: 43 | root.register_key_monitor(self, key) 44 | -------------------------------------------------------------------------------- /addons/behavior_tree/conditions/bt_condition.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTCondition 3 | extends BTItem 4 | 5 | 6 | 7 | signal interrupted(agent, try_abort) 8 | 9 | 10 | @export var monitor_type : BTMonitorType 11 | 12 | 13 | func _check_condition(_agent : BTAgent) -> bool: 14 | return true 15 | 16 | 17 | func _initialize() -> void: 18 | pass 19 | 20 | 21 | func _register_monitored_keys(_root : BTNode) -> void: 22 | pass 23 | 24 | 25 | func interrupt(agent : BTAgent) -> void: 26 | interrupted.emit(agent, true) 27 | 28 | 29 | func get_monitor_value(agent : BTAgent) -> Variant: 30 | return _check_condition(agent) 31 | -------------------------------------------------------------------------------- /addons/behavior_tree/graph/bt_editor.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTEditor 3 | extends VBoxContainer 4 | 5 | 6 | 7 | var plugin : BTEditorPlugin: 8 | set(value): 9 | plugin = value 10 | _graph_edit.plugin = value 11 | 12 | var debug_mode := false: 13 | set = _set_debug_mode 14 | 15 | 16 | @onready var _graph_edit := $GraphEdit as BTGraphEdit 17 | @onready var _title := $TopBar/Title as Label 18 | @onready var _new_button := $TopBar/Right/NewButton as Button 19 | @onready var _load_button := $TopBar/Right/LoadButton as Button 20 | @onready var _save_button := $TopBar/Right/SaveButton as Button 21 | @onready var _save_as_button := $TopBar/Right/SaveAsButton as Button 22 | @onready var _back_button := $TopBar/Left/BackButton as Button 23 | 24 | 25 | 26 | func _ready() -> void: 27 | _save_button.pressed.connect(_on_SaveButton_pressed) 28 | _save_as_button.pressed.connect(_on_SaveAsButton_pressed) 29 | _load_button.pressed.connect(_on_LoadButton_pressed) 30 | _new_button.pressed.connect(_on_NewButton_pressed) 31 | _back_button.pressed.connect(_on_BackButton_pressed) 32 | 33 | _graph_edit.loaded_changed.connect(_on_GraphEdit_loaded_changed) 34 | 35 | 36 | 37 | func debug_tree(data : Array) -> void: 38 | _graph_edit.open_tree(BTGraphEdit.OpenTreeMode.DEBUG, data) 39 | 40 | 41 | 42 | func _save(path := "") -> void: 43 | var compiled_tree = _graph_edit.compile_tree() 44 | 45 | if compiled_tree == null: return 46 | 47 | if path == "": 48 | path = await plugin.request_file_path(false) 49 | 50 | if path == "": return 51 | 52 | ResourceSaver.save(compiled_tree, path) 53 | _graph_edit.current_path = path 54 | 55 | 56 | func _on_SaveButton_pressed() -> void: 57 | _save(_graph_edit.current_path) 58 | 59 | 60 | 61 | func _on_SaveAsButton_pressed() -> void: 62 | _save() 63 | 64 | 65 | func _on_LoadButton_pressed() -> void: 66 | var path : String = await plugin.request_file_path(true) 67 | 68 | if path == "": return 69 | 70 | _graph_edit.open_tree(BTGraphEdit.OpenTreeMode.LOAD, path) 71 | 72 | 73 | func _on_NewButton_pressed() -> void: 74 | _graph_edit.open_tree(BTGraphEdit.OpenTreeMode.NEW) 75 | 76 | 77 | func _on_BackButton_pressed() -> void: 78 | _graph_edit.open_tree(BTGraphEdit.OpenTreeMode.BACK) 79 | 80 | 81 | func _on_GraphEdit_loaded_changed(number_pages : int) -> void: 82 | var text : String = _graph_edit.current_path 83 | 84 | if text == "": 85 | text = "new Behavior Tree" 86 | 87 | _title.text = text 88 | 89 | _back_button.visible = number_pages > 1 90 | 91 | 92 | 93 | func _set_debug_mode(value : bool) -> void: 94 | if debug_mode == value: return 95 | 96 | debug_mode = value 97 | 98 | _save_button.disabled = debug_mode 99 | _save_as_button.disabled = debug_mode 100 | _load_button.disabled = debug_mode 101 | _new_button.disabled = debug_mode 102 | 103 | _graph_edit.debug_mode = debug_mode 104 | -------------------------------------------------------------------------------- /addons/behavior_tree/graph/bt_graph_edit.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTGraphEdit 3 | extends GraphEdit 4 | 5 | 6 | 7 | signal loaded_changed(number_pages) 8 | 9 | 10 | enum OpenTreeMode{ 11 | NEW, 12 | BACK, 13 | LOAD, 14 | SUBTREE, 15 | DEBUG, 16 | } 17 | 18 | 19 | const _PORT := 0 20 | const _GraphNode = preload("graph_node.tscn") 21 | 22 | const _UNTICKED_COLOR := Color.WHITE 23 | const _FAILURE_COLOR := Color.RED 24 | const _SUCCESS_COLOR := Color.GREEN 25 | const _RUNNING_COLOR := Color.YELLOW 26 | 27 | 28 | var plugin : BTEditorPlugin 29 | var debug_mode : bool: 30 | set = _set_debug_mode 31 | var current_path : String: 32 | set = _set_current_path 33 | 34 | ## Holds a BTNode to BTGraphNode reference. 35 | ## Used for drawing and debugging. 36 | var _nodes_reference : Dictionary 37 | ## Pages are Array of the format [tree_path, subtree_index, debug_draw_results, debug_subtree_pages] 38 | var _graph_pages : Array[Array] 39 | 40 | 41 | 42 | func _ready() -> void: 43 | connection_request.connect(_on_connection_request) 44 | disconnection_request.connect(_on_disconnection_request) 45 | node_selected.connect(_on_node_selected) 46 | connection_to_empty.connect(_on_connection_to_empty) 47 | connection_from_empty.connect(_on_connection_from_empty) 48 | delete_nodes_request.connect(_on_delete_nodes_request) 49 | popup_request.connect(_on_popup_request) 50 | 51 | open_tree(OpenTreeMode.NEW) 52 | 53 | 54 | 55 | func add_node(bt_node : BTNode, position := Vector2.ZERO) -> BTGraphNode: 56 | var graph_node := _GraphNode.instantiate() as BTGraphNode 57 | add_child(graph_node) 58 | 59 | graph_node.bt_node = bt_node 60 | graph_node.position_offset = position 61 | 62 | _nodes_reference[bt_node] = graph_node 63 | 64 | graph_node.draggable = not debug_mode 65 | graph_node.show_close = not debug_mode 66 | 67 | graph_node.close_request.connect(_on_BTGraphNode_close_requested.bind(graph_node)) 68 | 69 | return graph_node 70 | 71 | 72 | func open_tree(mode : OpenTreeMode, tree_data = null) -> void: 73 | match mode: 74 | OpenTreeMode.NEW: 75 | _graph_pages = [["", null, {}, {}]] 76 | _clear_graph() 77 | 78 | OpenTreeMode.BACK: 79 | _graph_pages.pop_back() 80 | _load_tree(_graph_pages[-1][0]) 81 | _draw_debug(_graph_pages[-1][2]) 82 | 83 | OpenTreeMode.LOAD: 84 | _graph_pages = [[tree_data, null, {}, {}]] 85 | _load_tree(tree_data) 86 | 87 | OpenTreeMode.SUBTREE: 88 | var subtrees : Dictionary = _graph_pages[-1][3] 89 | 90 | var debug_data = subtrees.get(tree_data[1]) 91 | 92 | var new_page : Array 93 | if debug_data == null: 94 | new_page = [tree_data[0], tree_data[1], {}, {}] 95 | else: 96 | new_page = [debug_data[0], tree_data[1], debug_data[1], debug_data[2]] 97 | 98 | _graph_pages.append(new_page) 99 | _load_tree(_graph_pages[-1][0]) 100 | _draw_debug(_graph_pages[-1][2]) 101 | 102 | OpenTreeMode.DEBUG: 103 | var debug_data = tree_data 104 | var i := 0 105 | var page = _graph_pages[i] 106 | while true: 107 | var is_same_subtree : bool = debug_data != null 108 | var is_same_tree : bool = is_same_subtree and page[0] == debug_data[0] 109 | 110 | if not is_same_tree: 111 | if i == 0: 112 | _graph_pages = [[debug_data[0], null, debug_data[1], debug_data[2]]] 113 | break 114 | 115 | _graph_pages.resize(i) 116 | break 117 | 118 | _graph_pages[i][2] = debug_data[1] 119 | _graph_pages[i][3] = debug_data[2] 120 | 121 | i += 1 122 | if i >= len(_graph_pages): 123 | break 124 | 125 | page = _graph_pages[i] 126 | var subtree_index = page[1] 127 | debug_data = debug_data[2].get(subtree_index) 128 | 129 | _load_tree(_graph_pages[-1][0]) 130 | _draw_debug(_graph_pages[-1][2]) 131 | 132 | loaded_changed.emit(len(_graph_pages)) 133 | 134 | 135 | func compile_tree() -> BTNode: 136 | var root : BTNode 137 | for child in get_children(): 138 | child = child as BTGraphNode 139 | if child == null: continue 140 | 141 | var bt_node = child.bt_node 142 | 143 | if bt_node is BTComposite: 144 | if bt_node.children.is_empty(): 145 | push_error("Behavior Tree: all BTComposites should have at least one child.") 146 | return 147 | 148 | if bt_node.parent != null: continue 149 | 150 | if root != null: 151 | push_error("Behavior Tree: Trees should have only one root.") 152 | return 153 | 154 | root = bt_node 155 | 156 | if root == null: 157 | push_error("Behavior Tree: Trees should have at least one node.") 158 | return 159 | 160 | root.recalculate_tree_index() 161 | 162 | return root.duplicate_deep() 163 | 164 | 165 | 166 | func _clear_graph() -> void: 167 | current_path = "" 168 | _nodes_reference.clear() 169 | clear_connections() 170 | for child in get_children(): 171 | if child is BTGraphNode: 172 | child.queue_free() 173 | 174 | 175 | func _load_tree(path : String) -> void: 176 | if not ResourceLoader.exists(path): 177 | push_warning("Behavior Tree: load path does not exist.") 178 | return 179 | 180 | var tree_template := ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_IGNORE) as BTNode 181 | if tree_template == null: 182 | push_warning("Behavior Tree: load path is not a Behavior Tree.") 183 | return 184 | 185 | _clear_graph() 186 | 187 | current_path = path 188 | 189 | var root := tree_template.duplicate_deep() 190 | 191 | _load_node_recursive(root) 192 | 193 | for child in get_children(): 194 | if child.is_queued_for_deletion(): continue 195 | if child is BTGraphNode: 196 | _draw_connections(child) 197 | 198 | 199 | func _draw_debug(results : Dictionary) -> void: 200 | for child in get_children(): 201 | child = child as BTGraphNode 202 | 203 | var index = child.bt_node.tree_index 204 | var result = results.get(index) 205 | 206 | var color : Color 207 | match result: 208 | null: 209 | color = _UNTICKED_COLOR 210 | BTItem.BTState.FAILURE: 211 | color = _FAILURE_COLOR 212 | BTItem.BTState.SUCCESS: 213 | color = _SUCCESS_COLOR 214 | BTItem.BTState.RUNNING: 215 | color = _RUNNING_COLOR 216 | 217 | child.set_slot_color_left(2, color) 218 | 219 | 220 | func _load_node_recursive(bt_node : BTNode) -> void: 221 | add_node(bt_node, bt_node.graph_position) 222 | 223 | if not bt_node is BTComposite: return 224 | 225 | for child in bt_node.children: 226 | _load_node_recursive(child) 227 | child.parent = bt_node 228 | 229 | bt_node.reorder_children() 230 | 231 | 232 | func _add_bt_parent_by_name(parent_name : String, child_name : String) -> void: 233 | var parent := get_node(parent_name) as BTGraphNode 234 | var child := get_node(child_name) as BTGraphNode 235 | child.bt_node.parent = parent.bt_node 236 | 237 | 238 | func _draw_connections(graph_node : BTGraphNode): 239 | var bt_node := graph_node.bt_node 240 | var parent := bt_node.parent 241 | 242 | if parent != null: 243 | var graph_parent = _nodes_reference[parent] 244 | connect_node(graph_parent.name, _PORT, graph_node.name, _PORT) 245 | 246 | if not bt_node is BTComposite: return 247 | bt_node = bt_node as BTComposite 248 | 249 | for child in bt_node.children: 250 | var graph_child = _nodes_reference[child] 251 | connect_node(graph_node.name, _PORT, graph_child.name, _PORT) 252 | 253 | 254 | func _clear_node_connections(node : BTGraphNode, parent_only := false) -> void: 255 | var connections = get_connection_list() 256 | for connection in connections: 257 | var clear_parent = connection.to == node.name 258 | var clear_children = not parent_only and connection.from == node.name 259 | if clear_parent or clear_children: 260 | disconnect_node(connection.from, connection.from_port, connection.to, connection.to_port) 261 | 262 | 263 | 264 | func _on_connection_request(from : String, from_slot : int, to : String, to_slot : int) -> void: 265 | if debug_mode: return 266 | 267 | var graph_node := get_node(to) as BTGraphNode 268 | 269 | _clear_node_connections(graph_node, true) 270 | 271 | connect_node(from, from_slot, to, to_slot) 272 | _add_bt_parent_by_name(from, to) 273 | 274 | 275 | func _on_disconnection_request(from : String, from_slot : int, to : String, to_slot : int) -> void: 276 | if debug_mode: return 277 | 278 | disconnect_node(from, from_slot, to, to_slot) 279 | var node := get_node(to) as BTGraphNode 280 | node.bt_node.parent = null 281 | 282 | 283 | func _on_node_selected(node : GraphNode) -> void: 284 | plugin.inspect_object(node.bt_node) 285 | 286 | 287 | func _on_connection_to_empty(from : String, from_slot : int, release_position : Vector2) -> void: 288 | if debug_mode: return 289 | 290 | var mouse_position := get_viewport().get_mouse_position() 291 | var bt_node := await plugin.request_bt_node_type(mouse_position) 292 | if bt_node == null: return 293 | 294 | var node := add_node(bt_node) 295 | node.position_offset = release_position - Vector2(0, node.size.y/2) 296 | connect_node(from, from_slot, node.name, 0) 297 | _add_bt_parent_by_name(from, node.name) 298 | 299 | 300 | func _on_connection_from_empty(to : String, to_slot : int, release_position : Vector2) -> void: 301 | if debug_mode: return 302 | 303 | var mouse_position := get_viewport().get_mouse_position() 304 | var bt_node := await plugin.request_bt_node_type(mouse_position, false) 305 | if bt_node == null: return 306 | 307 | var new_node := add_node(bt_node) 308 | new_node.position_offset = release_position - Vector2(new_node.size.x, new_node.size.y/2) 309 | connect_node(new_node.name, 0, to, to_slot) 310 | _add_bt_parent_by_name(new_node.name, to) 311 | 312 | 313 | func _on_delete_nodes_request(nodes : Array) -> void: 314 | if debug_mode: return 315 | 316 | for child in get_children(): 317 | if nodes.has(child.name): 318 | child.delete_node() 319 | _clear_node_connections(child) 320 | _nodes_reference.erase(child.bt_node) 321 | 322 | 323 | func _on_popup_request(position : Vector2) -> void: 324 | if debug_mode: return 325 | 326 | var mouse_position := get_viewport().get_mouse_position() 327 | var bt_node : BTNode = await plugin.request_bt_node_type(mouse_position) 328 | 329 | if bt_node == null: return 330 | 331 | for child in get_children(): 332 | child = child as BTGraphNode 333 | 334 | if child == null: continue 335 | if not child.has_point(position): continue 336 | 337 | _clear_node_connections(child) 338 | _nodes_reference.erase(bt_node) 339 | child.bt_node = bt_node 340 | _nodes_reference[bt_node] = child 341 | _draw_connections(child) 342 | return 343 | 344 | add_node(bt_node, position) 345 | 346 | 347 | func _on_BTGraphNode_close_requested(graph_node : BTGraphNode) -> void: 348 | _clear_node_connections(graph_node) 349 | _nodes_reference.erase(graph_node.bt_node) 350 | 351 | 352 | 353 | func _set_current_path(value : String) -> void: 354 | current_path = value 355 | loaded_changed.emit(len(_graph_pages)) 356 | 357 | 358 | func _set_debug_mode(value : bool) -> void: 359 | if debug_mode == value: return 360 | 361 | debug_mode = value 362 | 363 | for child in get_children(): 364 | child = child as BTGraphNode 365 | if child == null: continue 366 | 367 | child.draggable = not debug_mode 368 | child.show_close = not debug_mode 369 | 370 | if debug_mode: continue 371 | 372 | child.set_slot_color_left(2, _UNTICKED_COLOR) 373 | -------------------------------------------------------------------------------- /addons/behavior_tree/graph/bt_graph_header.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTGraphHeader 3 | extends ColorRect 4 | 5 | 6 | 7 | var _node : BTGraphNode 8 | 9 | 10 | func _ready() -> void: 11 | _find_graph_node() 12 | 13 | 14 | func _gui_input(event: InputEvent) -> void: 15 | if not event is InputEventMouseButton: 16 | return 17 | 18 | if event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed(): 19 | _node.node_selected.emit() 20 | 21 | 22 | 23 | func update(title : String, color : Color) -> void: 24 | $Title.text = title 25 | self.color = color 26 | 27 | 28 | 29 | func _find_graph_node() -> void: 30 | var parent = get_parent() 31 | 32 | if parent is BTGraphNode: 33 | _node = parent 34 | 35 | var grandparent = parent.get_parent() 36 | 37 | if grandparent is BTGraphNode: 38 | _node = grandparent 39 | -------------------------------------------------------------------------------- /addons/behavior_tree/graph/bt_graph_node.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTGraphNode 3 | extends GraphNode 4 | 5 | 6 | 7 | const Header := preload("header.tscn") 8 | 9 | const _MAIN_COLOR := Color.DARK_GRAY 10 | const _UTILITY_COLOR := Color.DODGER_BLUE 11 | const _CONDITION_COLOR := Color.DARK_ORANGE 12 | const _MODIFIER_COLOR := Color.DARK_OLIVE_GREEN 13 | 14 | 15 | var bt_node : BTNode: 16 | set = _set_bt_node 17 | 18 | 19 | func _ready() -> void: 20 | close_request.connect(_on_close_request) 21 | position_offset_changed.connect(_on_position_offset_changed) 22 | 23 | 24 | func _gui_input(event: InputEvent) -> void: 25 | if not event is InputEventMouseButton: 26 | return 27 | 28 | if event.button_index == MOUSE_BUTTON_LEFT and event.double_click and bt_node is BTSubtree: 29 | get_parent().open_tree(BTGraphEdit.OpenTreeMode.SUBTREE, [bt_node.subtree.resource_path, bt_node.tree_index]) 30 | 31 | 32 | func delete_node(): 33 | bt_node.parent = null 34 | 35 | if bt_node is BTComposite: 36 | for child in bt_node.children.duplicate(): 37 | child.parent = null 38 | 39 | queue_free() 40 | 41 | 42 | func has_point(point : Vector2) -> bool: 43 | return Rect2(position_offset, size).has_point(point) 44 | 45 | 46 | 47 | func _refresh_visuals() -> void: 48 | title = "%s - %s" % [bt_node.sibling_index, bt_node.get_bt_type_name()] 49 | $Main.update(bt_node.name, _MAIN_COLOR) 50 | 51 | _refresh_decorator_container($Utilities, bt_node.utilities, _UTILITY_COLOR) 52 | _refresh_decorator_container($Conditions, bt_node.conditions, _CONDITION_COLOR) 53 | _refresh_decorator_container($Modifiers, bt_node.modifiers, _MODIFIER_COLOR) 54 | 55 | 56 | func _refresh_decorator_container(container : Node, elements : Array, color : Color) -> void: 57 | var i := 0 58 | var current_list = container.get_children() 59 | while i < len(current_list): 60 | var header := current_list[i] as BTGraphHeader 61 | 62 | if i >= len(elements): 63 | header.queue_free() 64 | else: 65 | var decorator = elements[i] 66 | header.update(decorator.name, color) 67 | i += 1 68 | 69 | while i < len(elements): 70 | if elements[i] == null: break 71 | 72 | var header := Header.instantiate() 73 | container.add_child(header) 74 | var decorator = elements[i] 75 | header.update(decorator.name, color) 76 | i += 1 77 | 78 | 79 | 80 | func _on_close_request() -> void: 81 | delete_node() 82 | 83 | 84 | func _on_position_offset_changed() -> void: 85 | bt_node.graph_position = position_offset 86 | 87 | 88 | 89 | func _set_bt_node(value : BTNode) -> void: 90 | if value == null: 91 | delete_node() 92 | return 93 | 94 | if bt_node != null: 95 | bt_node.changed.disconnect(_refresh_visuals) 96 | 97 | value.parent = bt_node.parent 98 | bt_node.parent = null 99 | value.conditions = bt_node.conditions 100 | value.modifiers = bt_node.modifiers 101 | 102 | if (value is BTComposite and bt_node is BTComposite): 103 | for child in bt_node.children.duplicate(): 104 | child.parent = value 105 | 106 | bt_node = value 107 | 108 | set_slot_enabled_right(2, bt_node is BTComposite) 109 | 110 | if (bt_node is BTComposite): 111 | bt_node.reorder_children() 112 | 113 | bt_node.changed.connect(_refresh_visuals, CONNECT_DEFERRED) 114 | _refresh_visuals() 115 | -------------------------------------------------------------------------------- /addons/behavior_tree/graph/editor.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://cc6xxx0w5tnn4"] 2 | 3 | [ext_resource type="Script" path="res://addons/behavior_tree/graph/bt_editor.gd" id="1_ese4v"] 4 | [ext_resource type="Script" path="res://addons/behavior_tree/graph/bt_graph_edit.gd" id="2_xfej3"] 5 | 6 | [node name="Editor" type="VBoxContainer"] 7 | custom_minimum_size = Vector2(0, 200) 8 | anchors_preset = 15 9 | anchor_right = 1.0 10 | anchor_bottom = 1.0 11 | grow_horizontal = 2 12 | grow_vertical = 2 13 | script = ExtResource("1_ese4v") 14 | 15 | [node name="TopBar" type="HBoxContainer" parent="."] 16 | layout_mode = 2 17 | alignment = 1 18 | 19 | [node name="Left" type="HBoxContainer" parent="TopBar"] 20 | layout_mode = 2 21 | size_flags_horizontal = 3 22 | 23 | [node name="BackButton" type="Button" parent="TopBar/Left"] 24 | visible = false 25 | layout_mode = 2 26 | text = "Back" 27 | 28 | [node name="Title" type="Label" parent="TopBar"] 29 | layout_mode = 2 30 | size_flags_horizontal = 3 31 | text = "new Behavior Tree" 32 | horizontal_alignment = 1 33 | vertical_alignment = 1 34 | 35 | [node name="Right" type="HBoxContainer" parent="TopBar"] 36 | layout_mode = 2 37 | size_flags_horizontal = 3 38 | alignment = 2 39 | 40 | [node name="SaveButton" type="Button" parent="TopBar/Right"] 41 | layout_mode = 2 42 | text = "Save" 43 | 44 | [node name="SaveAsButton" type="Button" parent="TopBar/Right"] 45 | layout_mode = 2 46 | text = "Save As" 47 | 48 | [node name="LoadButton" type="Button" parent="TopBar/Right"] 49 | layout_mode = 2 50 | text = "Load" 51 | 52 | [node name="NewButton" type="Button" parent="TopBar/Right"] 53 | layout_mode = 2 54 | text = "New" 55 | 56 | [node name="GraphEdit" type="GraphEdit" parent="."] 57 | layout_mode = 2 58 | size_flags_vertical = 3 59 | right_disconnects = true 60 | arrange_nodes_button_hidden = true 61 | script = ExtResource("2_xfej3") 62 | -------------------------------------------------------------------------------- /addons/behavior_tree/graph/graph_node.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://dlyq1rwhjku21"] 2 | 3 | [ext_resource type="Script" path="res://addons/behavior_tree/graph/bt_graph_node.gd" id="1_3un74"] 4 | [ext_resource type="PackedScene" uid="uid://cd6jjtpbvvv4b" path="res://addons/behavior_tree/graph/header.tscn" id="2_qd3ex"] 5 | 6 | [node name="GraphNode" type="GraphNode"] 7 | offset_right = 152.0 8 | offset_bottom = 62.0 9 | title = "BT Node" 10 | show_close = true 11 | slot/0/left_enabled = false 12 | slot/0/left_type = 0 13 | slot/0/left_color = Color(1, 1, 1, 1) 14 | slot/0/left_icon = null 15 | slot/0/right_enabled = false 16 | slot/0/right_type = 0 17 | slot/0/right_color = Color(1, 1, 1, 1) 18 | slot/0/right_icon = null 19 | slot/0/draw_stylebox = true 20 | slot/1/left_enabled = false 21 | slot/1/left_type = 0 22 | slot/1/left_color = Color(1, 1, 1, 1) 23 | slot/1/left_icon = null 24 | slot/1/right_enabled = false 25 | slot/1/right_type = 0 26 | slot/1/right_color = Color(1, 1, 1, 1) 27 | slot/1/right_icon = null 28 | slot/1/draw_stylebox = true 29 | slot/2/left_enabled = true 30 | slot/2/left_type = 0 31 | slot/2/left_color = Color(1, 1, 1, 1) 32 | slot/2/left_icon = null 33 | slot/2/right_enabled = true 34 | slot/2/right_type = 0 35 | slot/2/right_color = Color(1, 1, 1, 1) 36 | slot/2/right_icon = null 37 | slot/2/draw_stylebox = true 38 | slot/3/left_enabled = false 39 | slot/3/left_type = 0 40 | slot/3/left_color = Color(1, 1, 1, 1) 41 | slot/3/left_icon = null 42 | slot/3/right_enabled = false 43 | slot/3/right_type = 0 44 | slot/3/right_color = Color(1, 1, 1, 1) 45 | slot/3/right_icon = null 46 | slot/3/draw_stylebox = true 47 | script = ExtResource("1_3un74") 48 | 49 | [node name="Utilities" type="VBoxContainer" parent="."] 50 | layout_mode = 2 51 | size_flags_vertical = 3 52 | 53 | [node name="Conditions" type="VBoxContainer" parent="."] 54 | layout_mode = 2 55 | 56 | [node name="Main" parent="." instance=ExtResource("2_qd3ex")] 57 | layout_mode = 2 58 | 59 | [node name="Modifiers" type="VBoxContainer" parent="."] 60 | layout_mode = 2 61 | -------------------------------------------------------------------------------- /addons/behavior_tree/graph/header.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://cd6jjtpbvvv4b"] 2 | 3 | [ext_resource type="Script" path="res://addons/behavior_tree/graph/bt_graph_header.gd" id="1_uoxum"] 4 | 5 | [sub_resource type="LabelSettings" id="LabelSettings_n4lv6"] 6 | font_size = 12 7 | font_color = Color(0, 0, 0, 1) 8 | 9 | [node name="Header" type="ColorRect"] 10 | custom_minimum_size = Vector2(0, 20) 11 | offset_right = 119.0 12 | offset_bottom = 20.0 13 | mouse_filter = 1 14 | script = ExtResource("1_uoxum") 15 | 16 | [node name="Title" type="Label" parent="."] 17 | layout_mode = 1 18 | anchors_preset = 15 19 | anchor_right = 1.0 20 | anchor_bottom = 1.0 21 | grow_horizontal = 2 22 | grow_vertical = 2 23 | text = "Title" 24 | label_settings = SubResource("LabelSettings_n4lv6") 25 | vertical_alignment = 1 26 | text_overrun_behavior = 3 27 | -------------------------------------------------------------------------------- /addons/behavior_tree/leaves/bt_blackboard_set.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTBlackboardSet 3 | extends BTLeaf 4 | 5 | 6 | 7 | @export var key : String = "": 8 | set(value): 9 | key = value 10 | 11 | @export_multiline var expression : String = "": 12 | set(value): 13 | if value == expression: return 14 | 15 | expression = value 16 | _expression.text = expression 17 | 18 | 19 | 20 | var _expression := BTExpression.new() 21 | 22 | 23 | 24 | func _get_bt_type_name() -> String: 25 | return "Blackboard Set" 26 | 27 | 28 | func _execute_tick(agent : BTAgent) -> void: 29 | var value = _expression.execute(agent) 30 | agent.blackboard.set_value(key, value) 31 | 32 | _report_result(agent, BTState.SUCCESS) 33 | 34 | 35 | func _check_string_validity(value : String) -> bool: 36 | return value != null and not value.is_empty() 37 | -------------------------------------------------------------------------------- /addons/behavior_tree/leaves/bt_callable.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTCallable 3 | extends BTLeaf 4 | 5 | 6 | 7 | @export var method_name : String = "": 8 | set(value): 9 | method_name = value 10 | 11 | #@export var method_arguments : Array[String]: 12 | # set(value): 13 | # method_arguments = value 14 | # _arguments_dirty = true 15 | 16 | @export_multiline var expression : String = "": # Temporary while multiple args disabled 17 | set(value): 18 | if value == expression: return 19 | 20 | expression = value 21 | _expression.text = expression 22 | 23 | 24 | 25 | #var _arguments_expressions : Array[BTExpression] 26 | var _arguments_dirty := true 27 | 28 | 29 | 30 | var _expression := BTExpression.new() # Temporary while multiple args disabled 31 | 32 | 33 | 34 | func _get_bt_type_name() -> String: 35 | return "Callable" 36 | 37 | 38 | func _execute_tick(agent : BTAgent) -> void: 39 | # _update_arguments_expressions() 40 | # 41 | # var arguments := _get_arguments(agent) 42 | # 43 | # var result : Variant = agent.callv(method_name, arguments) 44 | 45 | var argument = _expression.execute(agent) # Temporary while multiple args disabled 46 | var result : Variant # Temporary while multiple args disabled 47 | if argument == null: 48 | result = agent.call(method_name) # Temporary while multiple args disabled 49 | else: 50 | result = agent.call(method_name, argument) # Temporary while multiple args disabled 51 | 52 | if result is bool: 53 | _report_result(agent, result as int) 54 | return 55 | if result is int: 56 | _report_result(agent, result) 57 | return 58 | 59 | _report_result(agent, BTState.SUCCESS) 60 | 61 | 62 | func _check_validity(prop) -> bool: 63 | return prop != null and not prop.is_empty() 64 | 65 | 66 | #func _update_arguments_expressions() -> void: 67 | # if not _arguments_dirty: return 68 | # 69 | # _arguments_expressions.clear() 70 | # for text_argument in method_arguments: 71 | # var expression := BTExpression.new() 72 | # expression.text = text_argument 73 | # _arguments_expressions.append(expression) 74 | # 75 | # _arguments_dirty = false 76 | # 77 | # 78 | #func _get_arguments(agent : BTAgent) -> Array[Variant]: 79 | # var get_argument := func(agent : BTAgent, expression : BTExpression) -> Variant: 80 | # return expression.execute(agent) 81 | # return _arguments_expressions.map(get_argument) 82 | -------------------------------------------------------------------------------- /addons/behavior_tree/leaves/bt_leaf.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTLeaf 3 | extends BTNode 4 | -------------------------------------------------------------------------------- /addons/behavior_tree/leaves/bt_particle_emitter.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTParticleEmitter 3 | extends BTLeaf 4 | 5 | ## Emits particles in the [GPUParticles2D], [GPUParticles3D], [CPUParticles2D] or [CPUParticles3D] 6 | ## found in the [member BTAgent.blackboard] with [member key]. 7 | ## Returns SUCCESS if the particles have finished emitting and FAILURE if no particle emitter is found. 8 | ## Returns RUNNING while the particle emitter is emitting, if it is not one_shot, will keep RUNNING 9 | ## until it's aborted. 10 | 11 | 12 | 13 | @export var key : String = "" 14 | @export var stop_on_abort := true 15 | 16 | var _particle_system 17 | var _started : bool 18 | 19 | 20 | 21 | func _get_bt_type_name() -> String: 22 | return "Particle Emitter" 23 | 24 | 25 | func _execute_tick(agent : BTAgent) -> void: 26 | if _particle_system == null: 27 | _report_result(agent, BTState.FAILURE) 28 | return 29 | 30 | if not _started: 31 | _particle_system.emitting = true 32 | _started = true 33 | 34 | if _particle_system.emitting: 35 | _report_result(agent, BTState.RUNNING) 36 | else: 37 | _report_result(agent, BTState.SUCCESS) 38 | 39 | 40 | func _abort(agent : BTAgent) -> void: 41 | if not stop_on_abort: return 42 | 43 | _particle_system = agent.get_node_memory(self) 44 | _particle_system.emitting = false 45 | 46 | 47 | func _setup_execution(agent : BTAgent) -> void: 48 | if agent.has_node_memory(self): 49 | _particle_system = agent.get_node_memory(self) 50 | _started = true 51 | else: 52 | _particle_system = agent.blackboard.get_value(key) 53 | _started = false 54 | 55 | if ( 56 | not _particle_system is GPUParticles2D 57 | and not _particle_system is GPUParticles3D 58 | and not _particle_system is CPUParticles2D 59 | and not _particle_system is CPUParticles3D 60 | ): 61 | push_error("BTParticleEmitter: key is not a particle emitter.") 62 | _particle_system = null 63 | 64 | 65 | func _save_memory(agent : BTAgent) -> void: 66 | agent.set_node_memory(self, _particle_system) 67 | -------------------------------------------------------------------------------- /addons/behavior_tree/leaves/bt_subtree.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTSubtree 3 | extends BTLeaf 4 | 5 | ## The Subtree node runs another tree saved to disk on the same agent and reports 6 | ## the result of the subtree to the parent of this node. 7 | 8 | 9 | #@export var key : String = "" # Adding subtrees set dynamically from blackboard key is planned. 10 | @export_file var path : String = "" 11 | 12 | var subtree : BTNode: 13 | get: 14 | return load(path) 15 | 16 | var _running_tree : BTRunningTree 17 | 18 | 19 | 20 | func _get_bt_type_name() -> String: 21 | return "Subtree" 22 | 23 | 24 | func _execute_tick(agent : BTAgent) -> void: 25 | _running_tree.tick() 26 | 27 | 28 | func _abort(agent : BTAgent) -> void: 29 | _running_tree = agent.active_tree.subtrees[self] 30 | 31 | var current_tree = agent.active_tree 32 | 33 | agent.set_active_subtree(_running_tree, self) 34 | agent.active_tree = _running_tree 35 | 36 | _running_tree.try_abort_running_node(_running_tree.tree, true) 37 | 38 | agent.active_tree = current_tree 39 | agent.set_active_subtree(null) 40 | 41 | 42 | func _setup_execution(agent : BTAgent) -> void: 43 | _running_tree = agent.active_tree.subtrees.get(self) 44 | 45 | if _running_tree == null: 46 | _running_tree = BTRunningTree.new() 47 | _running_tree.initialize(agent, subtree) 48 | 49 | agent.set_active_subtree(_running_tree, self) 50 | agent.set_active_monitor(self, _running_tree) 51 | 52 | 53 | func _terminate(agent : BTAgent, result : BTState) -> void: 54 | super._terminate(agent, result) 55 | 56 | if result == BTState.RUNNING: return 57 | 58 | if not _running_tree.has_active_monitors(): 59 | agent.remove_active_monitor(self) 60 | 61 | 62 | 63 | func subtree_response(agent : BTAgent, result : BTState) -> void: 64 | _setup_execution(agent) 65 | 66 | _report_result(agent, result) 67 | 68 | 69 | func check_monitor_interrupt(agent : BTAgent, running_tree : BTRunningTree) -> bool: 70 | var current_tree = agent.active_tree 71 | 72 | agent.set_active_subtree(running_tree, self) 73 | agent.active_tree = running_tree 74 | 75 | var was_interrupted = running_tree._check_monitors_interrupts() 76 | 77 | agent.active_tree = current_tree 78 | agent.set_active_subtree(null) 79 | 80 | return was_interrupted 81 | -------------------------------------------------------------------------------- /addons/behavior_tree/modifiers/bt_always_fail.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTAlwaysFail 3 | extends BTModifier 4 | 5 | 6 | 7 | func _get_bt_type_name() -> String: 8 | return "Always Fail" 9 | 10 | 11 | func _apply_modifier(_agent : BTAgent, result : BTState) -> BTState: 12 | if result == BTState.RUNNING: 13 | return BTState.RUNNING 14 | 15 | return BTState.FAILURE 16 | -------------------------------------------------------------------------------- /addons/behavior_tree/modifiers/bt_always_succeed.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTAlwaysSucceed 3 | extends BTModifier 4 | 5 | 6 | 7 | func _get_bt_type_name() -> String: 8 | return "Always Succeed" 9 | 10 | 11 | func _apply_modifier(_agent : BTAgent, result : BTState) -> BTState: 12 | if result == BTState.RUNNING: 13 | return BTState.RUNNING 14 | 15 | return BTState.SUCCESS 16 | -------------------------------------------------------------------------------- /addons/behavior_tree/modifiers/bt_inverter.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTInverter 3 | extends BTModifier 4 | 5 | 6 | 7 | func _get_bt_type_name() -> String: 8 | return "Inverter" 9 | 10 | 11 | func _apply_modifier(_agent : BTAgent, result : BTState) -> BTState: 12 | match result: 13 | BTState.SUCCESS: 14 | return BTState.FAILURE 15 | BTState.FAILURE: 16 | return BTState.SUCCESS 17 | 18 | return BTState.RUNNING 19 | -------------------------------------------------------------------------------- /addons/behavior_tree/modifiers/bt_modifier.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTModifier 3 | extends BTItem 4 | 5 | 6 | 7 | func _apply_modifier(_agent : BTAgent, result : BTState) -> BTState: 8 | return result 9 | -------------------------------------------------------------------------------- /addons/behavior_tree/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Behavior Tree" 4 | description="An event-driven behavior tree for godot 4.0." 5 | author="Matheus Boquimpani" 6 | version="1.0" 7 | script="bt_editor_plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/behavior_tree/utilities/bt_blackboard_value.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends BTUtility 3 | class_name BTBlackboardValue 4 | 5 | 6 | 7 | @export var key : String = "" 8 | 9 | 10 | 11 | func _get_bt_type_name() -> String: 12 | return "Blackboard Value" 13 | 14 | 15 | func _get_utility_value(agent : BTAgent) -> float: 16 | if not agent.blackboard.has_key(key): 17 | return 0 18 | 19 | return agent.blackboard.get_value(key) as float 20 | 21 | 22 | func _register_monitored_keys(monitor : BTNode, root : BTNode) -> void: 23 | root.register_key_monitor(monitor, key) 24 | -------------------------------------------------------------------------------- /addons/behavior_tree/utilities/bt_utility.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BTUtility 3 | extends BTItem 4 | 5 | 6 | 7 | func _get_utility_value(agent : BTAgent) -> float: 8 | return 0 9 | 10 | 11 | func _initialize() -> void: 12 | pass 13 | 14 | 15 | func _register_monitored_keys(_monitor : BTNode, _root : BTNode) -> void: 16 | pass 17 | --------------------------------------------------------------------------------