Creating custom tasks

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

Creating tasks in C#

You can use the following script template for custom tasks:

using Godot;
using System;

[Tool]
public partial class _CLASS_ : _BASE_
{
    public override string _GenerateName()
    {
        return "_CLASS_";
    }

    public override void _Setup()
    {
    }

    public override void _Enter()
    {
    }

    public override void _Exit()
    {
    }

    public override Status _Tick(double delta)
    {
        return Status.Success;
    }

    public override string[] _GetConfigurationWarnings()
    {
        return Array.Empty<string>();
    }
}