You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			225 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			GDScript
		
	
		
		
			
		
	
	
			225 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			GDScript
		
	
| 
											2 years ago
										 | ## 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"] |