mcpField
L
limboai

limboai

github.com/jame581开发889137 次安装5 天前更新
代码仓库

关于 limboai

LimboAI 是面向 Godot 游戏开发者的 AI 行为树与分层状态机插件。它结合可视化编辑器、黑板和自定义任务,并可通过 BTState 桥接两种控制方式。

主要功能

  1. 01行为树执行与调试
  2. 02分层状态机管理
  3. 03BTState 桥接行为树与状态机
  4. 04黑板变量与方案
  5. 05自定义任务和条件
  6. 06支持 GDScript 与 C++

适用场景

  1. 01构建游戏角色的行为树逻辑
  2. 02组合行为树与分层状态机
  3. 03用共享黑板传递 Agent 状态
  4. 04为行为逻辑编写自定义任务

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jame581/godotprompter --skill limboai

安装后在 Claude Code 中输入/limboai确认已加载。

附带文件references/hsm.md

详细介绍

LimboAI

**相关技能:**移动任务驱动行为时使用 ai-navigation;使用核心引擎 FSM(不需要插件)时使用 state-machine;选择 AI 方案时使用 godot-brainstorming。

**插件:**LimboAI · 版本 v1.8.0 · Godot 4.6+(GDExtension)· MIT · 源码:https://github.com/limbonaut/limboai · 使用 C++ 编写(GDExtension;也提供引擎模块构建版本)。GDExtension 可使用 GDScript;C# 需要模块构建版本(v1.8.0 不支持 GDExtension),且 v1.8.0 模块构建版本面向 Godot 4.7。


1. 何时使用 LimboAI

方案最适合
核心引擎 FSM(state-machine 技能)简单 Agent、少于 5 个状态、无需插件
Beehave(GDScript 插件)轻量级 BT、仅使用 GDScript 的项目
LimboAI同时使用 BT 和 HSM、可视化编辑器、C++ 性能、C# 支持(模块构建版本)

当你需要带有完善可视化调试器的行为树、希望将其与分层状态机结合(BTState 可连接二者),或需要 C++ 任务执行速度时,选择 LimboAI。注意:LimboAI v1.8.0 需要 Godot 4.6+;在 4.3 上对应的版本系列是 1.2.0–1.3.x,本技能不涵盖该系列的 API。对于更简单、仅使用 GDScript 的行为树,Beehave 是更轻量的替代方案。对于不需要 BT 的普通状态机,请改用内置的 state-machine 技能。


2. 安装与设置

GDExtension(推荐 — 无需自定义引擎)
  1. Godot AssetLib → 搜索“LimboAI” → 下载 → 重新加载项目。
    或从 GitHub Releases 下载,并将 addons/limboai/ 放到 res://addons/limboai/。
  2. 无需启用插件:这里没有 plugin.cfg — 项目重新加载后,扩展会自行注册其编辑器插件。
  3. .gdextension 清单位于 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 的限制:**LimboAI 任务面板不会显示文档工具提示(v1.8.0 仅模块版本支持)。BBParam 属性编辑器已于 2025 年移植到 GDExtension,但文档仍将其列为缺失。C# 需要使用模块构建版本(见下文)。

模块版本(用于 C# 或完整的编辑器集成)

从 GitHub Releases 下载预编译的编辑器和导出模板。导出时需要自定义引擎。v1.8.0 模块构建版本基于 Godot 4.7(GDExtension 支持 4.6+)。模块构建版本附带用于 C# 的 NuGet 包:

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

3. 行为树

BehaviorTree 资源用于保存任务树。BTPlayer 会在每个物理帧运行该任务树(也可在空闲模式或手动运行)。将 BTPlayer 设为 Agent 节点的子节点,并为其指定 BehaviorTree 资源。

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 控制任务树的更新时机:IDLE(每次 _process)、PHYSICS(每次 _physics_process,默认值)或 MANUAL(自行调用 bt_player.update(delta))。


4. 自定义任务

继承 BTAction(多次更新的任务)或 BTCondition(立即检查)。添加 @tool 注解,以便在编辑器中使用 _generate_name() 和 _get_configuration_warnings()。将脚本放在 res://ai/tasks/ 下;子文件夹会成为任务分类。

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;
    }
}

任务生命周期:首次更新前调用一次 _setup() → 状态从非 RUNNING 转变时调用 _enter() → 每次执行时调用 _tick(delta) → SUCCESS 或 FAILURE 后调用 _exit()。


5. 黑板

Blackboard 是一个 RefCounted 键值存储,由树中的所有任务共享。使用 StringName 键(&"key"),并将其导出为任务属性,以便检查器显示选择器。

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;
    }
}

有用的 Blackboard 方法: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()(调试)。

BlackboardPlan 定义变量架构(类型、默认值、提示),并在 BTPlayer 或 LimboHSM 的 Inspector 中编辑。运行时调用 blackboard_plan.create_blackboard(scene_root),即可创建作用域为指定场景的 Blackboard。


6. 分层状态机(LimboHSM)

LimboHSM 是一个管理子级 LimboState 节点的 LimboState 节点。状态调用 dispatch(event) 时会触发转换。高级用法(任意状态转换、BTState、嵌套 HSM、守卫条件)请参阅 references/hsm.md。

GDScript — 场景树设置
# 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# — 场景树设置
// 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 — 状态脚本
# 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# — 状态脚本
// 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);
    }
}

实现检查清单

  • 将 BTPlayer 添加为 Agent 的子级;分配 behavior_tree 资源
  • 自定义任务使用 @tool(GDScript)或 [Tool](C#)注解,以便在编辑器中显示
  • 每个 _tick 都返回 SUCCESS、FAILURE 或 RUNNING,绝不返回 void/null
  • 将黑板键记录为导出的 StringName 属性(后缀为 _var)
  • C# 黑板读取时显式转换 Variant((float)Blackboard.GetVar(...))
  • C# 使用模块构建(而非 GDExtension)以支持 C#
  • 使用 BTPlayer.updated 信号(而非已弃用的 behavior_tree_finished)
  • HSM:在 initialize() 之前,使用 add_transition 为所有 LimboState 节点连接转换
  • HSM:在 initialize() 之后调用 set_active(true)
  • HSM 转换完备:每个可到达的状态都有退出路径

评论

支持 Markdown