stealthy/addons/beehave/nodes/beehave_tree.gd
2023-10-06 01:54:01 -07:00

224 lines
6.3 KiB
GDScript

## Controls the flow of execution of the entire behavior tree.
@tool
@icon("../icons/tree.svg")
class_name BeehaveTree extends Node
enum {
SUCCESS,
FAILURE,
RUNNING
}
signal tree_enabled
signal tree_disabled
## Wether this behavior tree should be enabled or not.
@export var enabled: bool = true:
set(value):
enabled = value
set_physics_process(enabled)
if value:
tree_enabled.emit()
else:
interrupt()
tree_disabled.emit()
get:
return enabled
## An optional node path this behavior tree should apply to.
@export_node_path var actor_node_path : NodePath
## Custom blackboard node. An internal blackboard will be used
## if no blackboard is provided explicitly.
@export var blackboard:Blackboard:
set(b):
blackboard = b
if blackboard and _internal_blackboard:
remove_child(_internal_blackboard)
_internal_blackboard.free()
_internal_blackboard = null
elif not blackboard and not _internal_blackboard:
_internal_blackboard = Blackboard.new()
add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK)
get:
return blackboard if blackboard else _internal_blackboard
## When enabled, this tree is tracked individually
## as a custom monitor.
@export var custom_monitor = false:
set(b):
custom_monitor = b
if custom_monitor and _process_time_metric_name != '':
Performance.add_custom_monitor(_process_time_metric_name, _get_process_time_metric_value)
BeehaveGlobalMetrics.register_tree(self)
else:
if _process_time_metric_name != '':
# Remove tree metric from the engine
Performance.remove_custom_monitor(_process_time_metric_name)
BeehaveGlobalMetrics.unregister_tree(self)
BeehaveDebuggerMessages.unregister_tree(get_instance_id())
var actor : Node
var status : int = -1
var _internal_blackboard: Blackboard
var _process_time_metric_name : String
var _process_time_metric_value : float = 0.0
var _can_send_message: bool = false
func _ready() -> void:
if Engine.is_editor_hint():
return
if self.get_child_count() > 0 and not self.get_child(0) is BeehaveNode:
push_warning("Beehave error: Root %s should have only one child of type BeehaveNode (NodePath: %s)" % [self.name, self.get_path()])
disable()
return
if not blackboard:
_internal_blackboard = Blackboard.new()
add_child(_internal_blackboard, false, Node.INTERNAL_MODE_BACK)
actor = get_parent()
if actor_node_path:
actor = get_node(actor_node_path)
# Get the name of the parent node name for metric
var parent_name = actor.name
_process_time_metric_name = "beehave [microseconds]/process_time_%s-%s" % [parent_name, get_instance_id()]
# Register custom metric to the engine
if custom_monitor:
Performance.add_custom_monitor(_process_time_metric_name, _get_process_time_metric_value)
BeehaveGlobalMetrics.register_tree(self)
set_physics_process(enabled)
BeehaveGlobalDebugger.register_tree(self)
BeehaveDebuggerMessages.register_tree(_get_debugger_data(self))
func _physics_process(delta: float) -> void:
if Engine.is_editor_hint():
return
# Start timing for metric
var start_time = Time.get_ticks_usec()
blackboard.set_value("can_send_message", _can_send_message)
if _can_send_message:
BeehaveDebuggerMessages.process_begin(get_instance_id())
if self.get_child_count() == 1:
tick()
if _can_send_message:
BeehaveDebuggerMessages.process_end(get_instance_id())
# Check the cost for this frame and save it for metric report
_process_time_metric_value = Time.get_ticks_usec() - start_time
func tick() -> int:
var child := self.get_child(0)
if status != RUNNING:
child.before_run(actor, blackboard)
status = child.tick(actor, blackboard)
if _can_send_message:
BeehaveDebuggerMessages.process_tick(child.get_instance_id(), status)
BeehaveDebuggerMessages.process_tick(get_instance_id(), status)
# Clear running action if nothing is running
if status != RUNNING:
blackboard.set_value("running_action", null, str(actor.get_instance_id()))
child.after_run(actor, blackboard)
return status
func _get_configuration_warnings() -> PackedStringArray:
var warnings:PackedStringArray = []
if get_children().any(func(x): return not (x is BeehaveNode)):
warnings.append("All children of this node should inherit from BeehaveNode class.")
if get_child_count() != 1:
warnings.append("BeehaveTree should have exactly one child node.")
return warnings
## Returns the currently running action
func get_running_action() -> ActionLeaf:
return blackboard.get_value("running_action", null, str(actor.get_instance_id()))
## Returns the last condition that was executed
func get_last_condition() -> ConditionLeaf:
return blackboard.get_value("last_condition", null, str(actor.get_instance_id()))
## Returns the status of the last executed condition
func get_last_condition_status() -> String:
if blackboard.has_value("last_condition_status", str(actor.get_instance_id())):
var status = blackboard.get_value("last_condition_status", null, str(actor.get_instance_id()))
if status == SUCCESS:
return "SUCCESS"
elif status == FAILURE:
return "FAILURE"
else:
return "RUNNING"
return ""
## interrupts this tree if anything was running
func interrupt() -> void:
if self.get_child_count() != 0:
var first_child = self.get_child(0)
if "interrupt" in first_child:
first_child.interrupt(actor, blackboard)
## Enables this tree.
func enable() -> void:
self.enabled = true
## Disables this tree.
func disable() -> void:
self.enabled = false
func _exit_tree() -> void:
if custom_monitor:
if _process_time_metric_name != '':
# Remove tree metric from the engine
Performance.remove_custom_monitor(_process_time_metric_name)
BeehaveGlobalMetrics.unregister_tree(self)
BeehaveDebuggerMessages.unregister_tree(get_instance_id())
# Called by the engine to profile this tree
func _get_process_time_metric_value() -> int:
return _process_time_metric_value
func _get_debugger_data(node: Node) -> Dictionary:
if not node is BeehaveTree and not node is BeehaveNode:
return {}
var data := { path = node.get_path(), name = node.name, type = node.get_class_name(), id = str(node.get_instance_id()) }
if node.get_child_count() > 0:
data.children = []
for child in node.get_children():
var child_data := _get_debugger_data(child)
if not child_data.is_empty():
data.children.push_back(child_data)
return data
func get_class_name() -> Array[StringName]:
return [&"BeehaveTree"]