Skip to content
cccskills
FreePublished on GitHub

limboai

Use when using the LimboAI addon — behavior trees and hierarchical state machines (C++ GDExtension) with a visual editor, BTTask subclassing, and a blackboard

The English summary is being prepared. Showing the original description.

See how to install

Files included (2)

  • SKILL.md13.1 KB
  • references/hsm.md4.7 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

LimboAI

Related skills: ai-navigation for movement the tasks drive, state-machine for core-engine FSM (when you don't need an addon), godot-brainstorming for choosing an AI approach.

Addon: LimboAI · version v1.8.0 · Godot 4.6+ (GDExtension) · MIT · source: https://github.com/limbonaut/limboai · written in C++ (GDExtension; engine-module build also available). GDExtension exposes GDScript; C# requires the module build (not GDExtension in v1.8.0), and the v1.8.0 module build targets Godot 4.7.


1. When to use LimboAI

ApproachBest for
Core-engine FSM (state-machine skill)Simple agents, < 5 states, no addon
Beehave (GDScript addon)Lightweight BT, GDScript-only projects
LimboAIBT and HSM together, visual editor, C++ performance, C# support (module build)

Choose LimboAI when you need a behavior tree with a polished visual debugger, want to combine it with a hierarchical state machine (BTState bridges them), or need C++ task execution speed. Note: LimboAI v1.8.0 requires Godot 4.6+; on 4.3 the matching line is 1.2.0–1.3.x, whose API this skill does not cover. For a simpler GDScript-only behavior tree, Beehave is a lighter alternative. For plain state machines without a BT, use the built-in state-machine skill instead.


2. Install & setup

GDExtension (recommended — no custom engine)

  1. Godot AssetLib → search "LimboAI" → Download → Reload project.
    Or download from GitHub Releases and place addons/limboai/ in res://addons/limboai/.
  2. No plugin toggle: there is no plugin.cfg — the extension registers its editor plugin itself once the project reloads.
  3. The .gdextension manifest ships at res://addons/limboai/bin/:
[configuration]
entry_symbol = "limboai_init"
compatibility_minimum = "4.2"

[libraries]
windows.debug.x86_64   = "res://addons/limboai/bin/liblimboai.windows.editor.x86_64.dll"
windows.release.x86_64 = "res://addons/limboai/bin/liblimboai.windows.template_release.x86_64.dll"
linux.debug.x86_64     = "res://addons/limboai/bin/liblimboai.linux.editor.x86_64.so"
linux.release.x86_64   = "res://addons/limboai/bin/liblimboai.linux.template_release.x86_64.so"
macos.debug            = "res://addons/limboai/bin/liblimboai.macos.editor.framework"
macos.release          = "res://addons/limboai/bin/liblimboai.macos.template_release.framework"
# ... (additional platform entries for linux arm64/rv64, android, iOS, web)

GDExtension limitations: the LimboAI task palette shows no documentation tooltips (module-only in v1.8.0). The BBParam property editor was ported to GDExtension in 2025, although the docs still list it as missing. C# needs the module build (below).

Module version (C# or full editor integration)

Download pre-compiled editor + export templates from GitHub Releases. Requires the custom engine for export. v1.8.0 module builds are based on Godot 4.7 (GDExtension supports 4.6+). The module build ships a NuGet package for C#:

# Add local NuGet source to your project:
# dotnet nuget add source path/to/nupkgs --name LimboNugetSource

3. Behavior trees

A BehaviorTree resource holds the task tree. BTPlayer runs it each physics frame (or idle/manual). Add BTPlayer as a child of the agent node and assign a BehaviorTree resource.

GDScript

# EnemyAI.gd — assign behavior_tree in the Inspector or here
extends CharacterBody2D

@onready var bt_player: BTPlayer = $BTPlayer

func _ready() -> void:
    # BTPlayer starts executing automatically (active = true by default).
    # updated(status) fires after every tree update, not only when it finishes.
    bt_player.updated.connect(_on_bt_updated)

func _on_bt_updated(status: int) -> void:
    if status == BT.SUCCESS:
        # A finished tree starts over on the next update by itself; stop it instead.
        bt_player.active = false

C#

// EnemyAI.cs
using Godot;

public partial class EnemyAI : CharacterBody2D
{
    [Export] private BTPlayer _btPlayer;

    public override void _Ready()
    {
        _btPlayer.Updated += OnBtUpdated;
    }

    private void OnBtUpdated(long status) // int signal args are long in C#
    {
        if (status == (long)BT.Status.Success)
            _btPlayer.Active = false; // a finished tree starts over on its own
    }
}

BTPlayer.UpdateMode controls when the tree ticks: IDLE (every _process), PHYSICS (every _physics_process, default), or MANUAL (call bt_player.update(delta) yourself).


4. Custom tasks

Subclass BTAction (multi-tick work) or BTCondition (immediate check). Annotate with @tool so _generate_name() and _get_configuration_warnings() work in the editor. Place scripts under res://ai/tasks/; subfolders become task categories.

GDScript

@tool
extends BTAction
## Moves the agent toward a blackboard position each tick.

@export var target_pos_var: StringName = &"target_pos"
@export var speed: float = 200.0

func _generate_name() -> String:
    return "MoveToward %s" % LimboUtility.decorate_var(target_pos_var)

func _setup() -> void:
    pass  # one-time init; agent and blackboard are available here

func _enter() -> void:
    pass  # called when task transitions from non-RUNNING → RUNNING

func _tick(delta: float) -> Status:
    var target: Vector2 = blackboard.get_var(target_pos_var, Vector2.ZERO)
    if agent.global_position.distance_to(target) < 5.0:
        return SUCCESS
    agent.velocity = agent.global_position.direction_to(target) * speed
    agent.move_and_slide()
    return RUNNING

func _exit() -> void:
    pass  # cleanup after SUCCESS or FAILURE
@tool
extends BTCondition
## Returns SUCCESS if the agent is within range of a target node.

@export var target_var: StringName = &"target"
@export var distance_max: float = 150.0

var _max_sq: float

func _setup() -> void:
    _max_sq = distance_max * distance_max

func _tick(_delta: float) -> Status:
    var target = blackboard.get_var(target_var, null)  # untyped: see §5
    if not is_instance_valid(target):
        return FAILURE
    var in_range: bool = agent.global_position.distance_squared_to(
        target.global_position) <= _max_sq
    return SUCCESS if in_range else FAILURE

C#

// MoveTowardTask.cs — place in res://ai/tasks/
using Godot;

[Tool]
public partial class MoveTowardTask : BTAction
{
    [Export] public StringName TargetPosVar { get; set; } = "target_pos";
    [Export] public float Speed { get; set; } = 200f;

    public override string _GenerateName() =>
        $"MoveToward {LimboUtility.DecorateVar(TargetPosVar)}";

    public override void _Setup() { }

    public override void _Enter() { }

    public override Status _Tick(double delta)
    {
        var target = (Vector2)Blackboard.GetVar(TargetPosVar, Vector2.Zero);
        var body = (CharacterBody2D)Agent;
        if (body.GlobalPosition.DistanceTo(target) < 5f)
            return Status.Success;
        body.Velocity = body.GlobalPosition.DirectionTo(target) * Speed;
        body.MoveAndSlide();
        return Status.Running;
    }

    public override void _Exit() { }
}
// InRangeCondition.cs
using Godot;

[Tool]
public partial class InRangeCondition : BTCondition
{
    [Export] public StringName TargetVar { get; set; } = "target";
    [Export] public float DistanceMax { get; set; } = 150f;

    private float _maxSq;

    public override void _Setup() => _maxSq = DistanceMax * DistanceMax;

    public override Status _Tick(double delta)
    {
        var target = Blackboard.GetVar(TargetVar, default(Variant)).As<Node2D>();
        if (!GodotObject.IsInstanceValid(target))
            return Status.Failure;
        var agent2D = (Node2D)Agent;
        bool inRange = agent2D.GlobalPosition.DistanceSquaredTo(
            target.GlobalPosition) <= _maxSq;
        return inRange ? Status.Success : Status.Failure;
    }
}

Task lifecycle: _setup() once before first tick → _enter() when status transitions from non-RUNNING → _tick(delta) every execution → _exit() after SUCCESS or FAILURE.


5. Blackboard

The Blackboard is a RefCounted key/value store shared by all tasks in a tree. Use StringName keys (&"key") and export them as task properties so the inspector shows a picker.

GDScript

@tool
extends BTAction

@export var speed_var: StringName = &"speed"
@export var target_var: StringName = &"target"

func _tick(delta: float) -> Status:
    # Read with a default; use no type annotation for object vars
    # to avoid errors if the stored instance was freed.
    var speed: float = blackboard.get_var(speed_var, 100.0)
    var obj = blackboard.get_var(target_var, null)
    if not is_instance_valid(obj):
        return FAILURE

    # Write back
    blackboard.set_var(speed_var, speed * 1.1)
    return RUNNING

C#

// C# has no generic GetVar<T> — cast the returned Variant.
using Godot;

[Tool]
public partial class SampleTask : BTAction
{
    [Export] public StringName SpeedVar { get; set; } = "speed";
    [Export] public StringName TargetVar { get; set; } = "target";

    public override Status _Tick(double delta)
    {
        float speed = (float)Blackboard.GetVar(SpeedVar, 100f);
        var obj = Blackboard.GetVar(TargetVar, default(Variant)).As<GodotObject>();
        if (!GodotObject.IsInstanceValid(obj))
            return Status.Failure;

        Blackboard.SetVar(SpeedVar, speed * 1.1f);
        return Status.Running;
    }
}

Useful Blackboard methods: has_var(name), erase_var(name), list_vars(), get_vars_as_dict(), bind_var_to_property(name, obj, prop), link_var(name, target_bb, target_name), print_state() (debug).

BlackboardPlan defines the variable schema (types, defaults, hints) and is edited in the Inspector on BTPlayer or LimboHSM. Call blackboard_plan.create_blackboard(scene_root) to create a scoped Blackboard at runtime.


6. Hierarchical state machine (LimboHSM)

LimboHSM is a LimboState node that manages child LimboState nodes. Transitions fire when a state calls dispatch(event). See references/hsm.md for advanced patterns (any-state transitions, BTState, nested HSMs, guards).

GDScript — scene-tree setup

# Character.gd — scene tree: Character → LimboHSM → IdleState, MoveState
extends CharacterBody2D

@onready var hsm: LimboHSM = $LimboHSM
@onready var idle: LimboState = $LimboHSM/IdleState
@onready var move: LimboState = $LimboHSM/MoveState

func _ready() -> void:
    hsm.add_transition(idle, move, idle.EVENT_FINISHED)
    hsm.add_transition(move, idle, move.EVENT_FINISHED)
    hsm.initialize(self)
    hsm.set_active(true)

C# — scene-tree setup

// Character.cs
using Godot;

public partial class Character : CharacterBody2D
{
    [Export] private LimboHsm _hsm; // C# name of LimboHSM
    [Export] private LimboState _idle;
    [Export] private LimboState _move;

    public override void _Ready()
    {
        _hsm.AddTransition(_idle, _move, _idle.EVENTFINISHED);
        _hsm.AddTransition(_move, _idle, _move.EVENTFINISHED);
        _hsm.Initialize(this);
        _hsm.SetActive(true);
    }
}

GDScript — state script

# IdleState.gd
extends LimboState

func _setup() -> void:
    pass  # agent and blackboard available; runs once during hsm.initialize()

func _enter() -> void:
    agent.get_node("AnimationPlayer").play("idle")

func _exit() -> void:
    pass

func _update(delta: float) -> void:
    if Input.get_vector(&"ui_left", &"ui_right", &"ui_up", &"ui_down").length() > 0.1:
        dispatch(EVENT_FINISHED)

C# — state script

// IdleState.cs
using Godot;

public partial class IdleState : LimboState
{
    public override void _Setup() { }

    public override void _Enter()
    {
        Agent.GetNode<AnimationPlayer>("AnimationPlayer").Play("idle");
    }

    public override void _Exit() { }

    public override void _Update(double delta)
    {
        if (Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down").Length() > 0.1f)
            Dispatch(EVENTFINISHED);
    }
}

Implementation checklist

  • BTPlayer added as a child of the agent; behavior_tree resource assigned
  • Custom tasks annotated with @tool (GDScript) or [Tool] (C#) for editor display
  • Every _tick returns SUCCESS, FAILURE, or RUNNING — never void/null
  • Blackboard keys documented as exported StringName properties (suffix _var)
  • C# Blackboard reads cast the Variant explicitly ((float)Blackboard.GetVar(...))
  • C# uses module build (not GDExtension) for C# support
  • BTPlayer.updated signal used (not deprecated behavior_tree_finished)
  • HSM: all LimboState nodes wired with add_transition before initialize()
  • HSM: set_active(true) called after initialize()
  • HSM transitions are exhaustive — every reachable state has an exit path

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when working with 2D-specific systems — TileMaps, parallax scrolling, 2D lights and shadows, canvas layers, particles 2D, custom drawing, and 2D meshes in Godot 4.3+

The English summary is being prepared. Showing the original description.

jame581/GodotPrompter865Updated Oct 22, 2026

Use when working with 3D-specific systems — materials, lighting, shadows, environment, global illumination, fog, LOD, occlusion culling, and decals in Godot 4.3+

The English summary is being prepared. Showing the original description.

jame581/GodotPrompter865Updated Oct 22, 2026

Use when building character abilities — Resource-based abilities with cost/cooldown/cast, buffs/debuffs, stat modifiers, gameplay tags, and HUD binding

The English summary is being prepared. Showing the original description.

jame581/GodotPrompter865Updated Oct 22, 2026

Use when creating Godot editor plugins — EditorPlugin, @tool scripts, custom inspectors, and dock panels

The English summary is being prepared. Showing the original description.

jame581/GodotPrompter865Updated Oct 22, 2026

Use when implementing AI movement — NavigationAgent2D/3D, steering behaviors, behavior trees, and patrol patterns

The English summary is being prepared. Showing the original description.

jame581/GodotPrompter865Updated Oct 22, 2026

Use when implementing animations — AnimationPlayer, AnimationTree, blend trees, state machines, sprite animation, and code-driven animation

The English summary is being prepared. Showing the original description.

jame581/GodotPrompter865Updated Oct 22, 2026

See all skills by jame581

Report a problem with this skill