Creating custom tasks in GDScript
By default, user tasks should be placed in the res://ai/tasks
directory. You can set an alternative location for user tasks in the
Project Settings → Limbo AI
(To see those options,
Advanced Settings
should be enabled in the Project Settings).
Each subdirectory within the user tasks directory is treated as a category. Therefore, if you create a subdirectory named “motion_and_physics,” your custom tasks in that directory will automatically be categorized under “Motion And Physics.”
When creating custom tasks, extend one of the following base classes: BTAction, BTCondition, BTDecorator, BTComposite. More on task types you can read in the Introduction to Behavior Trees.
🛈 Note: To help you write new tasks, you can add a script template to your project using “Misc → Create script template” menu option.
Using the Blackboard is covered in Accessing the Blackboard in a Task.
Task anatomy
@tool
extends BTAction
# Task parameters.
@export var parameter1: float
@export var parameter2: Vector2
## Note: Each method declaration is optional.
## At minimum, you only need to define the "_tick" method.
# Called to generate a display name for the task (requires @tool).
func _generate_name() -> String:
return "MyTask"
# Called to initialize the task.
func _setup() -> void:
pass
# Called when the task is entered.
func _enter() -> void:
pass
# Called when the task is exited.
func _exit() -> void:
pass
# Called each time this task is ticked (aka executed).
func _tick(delta: float) -> Status:
return SUCCESS
# Strings returned from this method are displayed as warnings in the editor.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
return warnings
Example 1: A simple action
@tool
extends BTAction
## Shows or hides a node and returns SUCCESS.
## Returns FAILURE if the node is not found.
# Task parameters.
@export var node_path: NodePath
@export var visible := true
# Called to generate a display name for the task (requires @tool).
func _generate_name() -> String:
return "SetVisible %s node_path: \"%s\"" % [visible, node_path]
# Called each time this task is ticked (aka executed).
func _tick(p_delta: float) -> Status:
var n: CanvasItem = scene_root.get_node_or_null(node_path)
if is_instance_valid(n):
n.visible = visible
return SUCCESS
return FAILURE
Example 2: InRange condition
@tool
extends BTCondition
## InRange condition checks if the agent is within a range of target,
## defined by distance_min and distance_max.
## Returns SUCCESS if the agent is within the defined range;
## otherwise, returns FAILURE.
@export var distance_min: float
@export var distance_max: float
@export var target_var: StringName = &"target"
var _min_distance_squared: float
var _max_distance_squared: float
# Called to generate a display name for the task.
func _generate_name() -> String:
return "InRange (%d, %d) of %s" % [distance_min, distance_max,
LimboUtility.decorate_var(target_var)]
# Called to initialize the task.
func _setup() -> void:
_min_distance_squared = distance_min * distance_min
_max_distance_squared = distance_max * distance_max
# Called when the task is executed.
func _tick(_delta: float) -> Status:
var target: Node2D = blackboard.get_var(target_var, null)
if not is_instance_valid(target):
return FAILURE
var dist_sq: float = agent.global_position.distance_squared_to(target.global_position)
if dist_sq >= _min_distance_squared and dist_sq <= _max_distance_squared:
return SUCCESS
else:
return FAILURE