关于 limboai
LimboAI 是面向 Godot 游戏开发者的 AI 行为树与分层状态机插件。它结合可视化编辑器、黑板和自定义任务,并可通过 BTState 桥接两种控制方式。
主要功能
- 01行为树执行与调试
- 02分层状态机管理
- 03BTState 桥接行为树与状态机
- 04黑板变量与方案
- 05自定义任务和条件
- 06支持 GDScript 与 C++
适用场景
- 01构建游戏角色的行为树逻辑
- 02组合行为树与分层状态机
- 03用共享黑板传递 Agent 状态
- 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(推荐 — 无需自定义引擎)
- Godot AssetLib → 搜索“LimboAI” → 下载 → 重新加载项目。
或从 GitHub Releases 下载,并将addons/limboai/放到res://addons/limboai/。 - 无需启用插件:这里没有
plugin.cfg— 项目重新加载后,扩展会自行注册其编辑器插件。 .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 转换完备:每个可到达的状态都有退出路径