Add a behavior tree to the enemy

Add the Beehave addon.
Make the enemy do the same thing as before but with a behavior tree.
This commit is contained in:
Mathilde Grapin 2023-06-12 16:48:35 +02:00
parent 09f6925a00
commit 1aed988149
92 changed files with 4025 additions and 25 deletions

View file

@ -0,0 +1,41 @@
## Decorator nodes are used to transform the result received by its child.
## Must only have one child.
@tool
@icon("../../icons/category_decorator.svg")
class_name Decorator extends BeehaveNode
var running_child: BeehaveNode = null
func _ready():
if Engine.is_editor_hint():
return
if self.get_child_count() != 1:
push_warning("Beehave Error: Decorator %s should have only one child (NodePath: %s)" % [self.name, self.get_path()])
func _get_configuration_warnings() -> PackedStringArray:
var warnings: PackedStringArray = super._get_configuration_warnings()
if get_child_count() != 1:
warnings.append("Decorator should have exactly one child node.")
return warnings
func interrupt(actor: Node, blackboard: Blackboard) -> void:
if running_child != null:
running_child.interrupt(actor, blackboard)
running_child = null
func after_run(actor: Node, blackboard: Blackboard) -> void:
running_child = null
func get_class_name() -> Array[StringName]:
var classes := super()
classes.push_back(&"Decorator")
return classes

View file

@ -0,0 +1,34 @@
## A Failer node will always return a `FAILURE` status code.
@tool
@icon("../../icons/failer.svg")
class_name AlwaysFailDecorator extends Decorator
func tick(actor: Node, blackboard: Blackboard) -> int:
var c = get_child(0)
if c != running_child:
c.before_run(actor, blackboard)
var response = c.tick(actor, blackboard)
if can_send_message(blackboard):
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
if response == RUNNING:
running_child = c
if c is ActionLeaf:
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
return RUNNING
else:
c.after_run(actor, blackboard)
return FAILURE
func get_class_name() -> Array[StringName]:
var classes := super()
classes.push_back(&"AlwaysFailDecorator")
return classes

View file

@ -0,0 +1,42 @@
## An inverter will return `FAILURE` in case it's child returns a `SUCCESS` status
## code or `SUCCESS` in case its child returns a `FAILURE` status code.
@tool
@icon("../../icons/inverter.svg")
class_name InverterDecorator extends Decorator
func tick(actor: Node, blackboard: Blackboard) -> int:
var c = get_child(0)
if c != running_child:
c.before_run(actor, blackboard)
var response = c.tick(actor, blackboard)
if can_send_message(blackboard):
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
match response:
SUCCESS:
c.after_run(actor, blackboard)
return FAILURE
FAILURE:
c.after_run(actor, blackboard)
return SUCCESS
RUNNING:
running_child = c
if c is ActionLeaf:
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
return RUNNING
_:
push_error("This should be unreachable")
return -1
func get_class_name() -> Array[StringName]:
var classes := super()
classes.push_back(&"InverterDecorator")
return classes

View file

@ -0,0 +1,41 @@
## The limiter will execute its child `x` amount of times. When the number of
## maximum ticks is reached, it will return a `FAILURE` status code.
@tool
@icon("../../icons/limiter.svg")
class_name LimiterDecorator extends Decorator
@onready var cache_key = 'limiter_%s' % self.get_instance_id()
@export var max_count : float = 0
func tick(actor: Node, blackboard: Blackboard) -> int:
var child = self.get_child(0)
var current_count = blackboard.get_value(cache_key, 0, str(actor.get_instance_id()))
if current_count == 0:
child.before_run(actor, blackboard)
if current_count < max_count:
blackboard.set_value(cache_key, current_count + 1, str(actor.get_instance_id()))
var response = child.tick(actor, blackboard)
if can_send_message(blackboard):
BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response)
if child is ConditionLeaf:
blackboard.set_value("last_condition", child, str(actor.get_instance_id()))
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
if child is ActionLeaf and response == RUNNING:
running_child = child
blackboard.set_value("running_action", child, str(actor.get_instance_id()))
return response
else:
child.after_run(actor, blackboard)
return FAILURE
func get_class_name() -> Array[StringName]:
var classes := super()
classes.push_back(&"LimiterDecorator")
return classes

View file

@ -0,0 +1,34 @@
## A succeeder node will always return a `SUCCESS` status code.
@tool
@icon("../../icons/succeeder.svg")
class_name AlwaysSucceedDecorator extends Decorator
func tick(actor: Node, blackboard: Blackboard) -> int:
var c = get_child(0)
if c != running_child:
c.before_run(actor, blackboard)
var response = c.tick(actor, blackboard)
if can_send_message(blackboard):
BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)
if c is ConditionLeaf:
blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
if response == RUNNING:
running_child = c
if c is ActionLeaf:
blackboard.set_value("running_action", c, str(actor.get_instance_id()))
return RUNNING
else:
c.after_run(actor, blackboard)
return SUCCESS
func get_class_name() -> Array[StringName]:
var classes := super()
classes.push_back(&"AlwaysSucceedDecorator")
return classes

View file

@ -0,0 +1,46 @@
## The Time Limit Decorator will give its child a set amount of time to finish
## before interrupting it and return a `FAILURE` status code. The timer is reset
## every time before the node runs.
@tool
@icon("../../icons/limiter.svg")
class_name TimeLimiterDecorator extends Decorator
@export var wait_time: = 0.0
var time_left: = 0.0
@onready var child: BeehaveNode = get_child(0)
func tick(actor: Node, blackboard: Blackboard) -> int:
if time_left < wait_time:
time_left += get_physics_process_delta_time()
var response = child.tick(actor, blackboard)
if can_send_message(blackboard):
BeehaveDebuggerMessages.process_tick(child.get_instance_id(), response)
if child is ConditionLeaf:
blackboard.set_value("last_condition", child, str(actor.get_instance_id()))
blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))
if response == RUNNING:
running_child = child
if child is ActionLeaf:
blackboard.set_value("running_action", child, str(actor.get_instance_id()))
return response
else:
child.after_run(actor, blackboard)
interrupt(actor, blackboard)
return FAILURE
func before_run(actor: Node, blackboard: Blackboard) -> void:
time_left = 0.0
child.before_run(actor, blackboard)
func get_class_name() -> Array[StringName]:
var classes := super()
classes.push_back(&"TimeLimiterDecorator")
return classes