limboai

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

LimboAI

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.

相关 skill: ai-navigation 用于任务驱动的移动,state-machine 用于核心引擎 FSM(不需要插件时),godot-brainstorming 用于选择 AI 方案。
插件: 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. When to use LimboAI

1. 何时使用 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 requires Godot 4.6+ and is not usable on 4.3–4.5. 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.

方案最适合场景
核心引擎 FSM(
state-machine
skill)
简单 Agent、少于 5 个状态、无需插件
Beehave(GDScript 插件)轻量 BT、纯 GDScript 项目
LimboAI同时使用 BT 和 HSM、可视化编辑器、C++ 性能、C# 支持(模块构建版本)
当需要带有完善可视化调试器的行为树、希望将行为树与分层状态机结合使用(
BTState
可桥接两者),或需要 C++ 任务执行速度时,请选择 LimboAI。请注意:LimboAI 要求 Godot 4.6+,无法在 4.3–4.5 上使用。对于更简单的纯 GDScript 行为树,Beehave 是更轻量的替代方案。对于不含 BT 的普通状态机,请使用内置的
state-machine
skill。

2. Install & setup

2. 安装与设置

GDExtension (recommended — no custom engine)

GDExtension(推荐——无需自定义引擎)

  1. Godot AssetLib → search "LimboAI" → Download → Reload project.
    Or download from GitHub Releases and place
    addons/limboai/
    in
    res://addons/limboai/
    .
  2. Enable the plugin: Project → Project Settings → Plugins → tick LimboAI.
  3. The
    .gdextension
    manifest ships at
    res://addons/limboai/bin/
    :
ini
[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"
  1. Godot AssetLib → 搜索 "LimboAI" → 下载 → 重新加载项目。
    或从 GitHub Releases 下载,并将
    addons/limboai/
    放入
    res://addons/limboai/
    。
  2. 启用插件:项目 → 项目设置 → 插件 → 勾选 LimboAI。
  3. .gdextension
    清单文件位于
    res://addons/limboai/bin/
    :
ini
[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)

... (additional platform entries for linux arm64/rv64, android, iOS, web)


**GDExtension limitations:** no in-editor documentation tooltips; `BBParam` property editor not available in the inspector.

**GDExtension 限制:** 无编辑器内文档提示;Inspector 中不提供 `BBParam` 属性编辑器。

Module version (C# or full editor integration)

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

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#:
ini
undefined
从 GitHub Releases 下载预编译的编辑器版本和导出模板。导出时需要使用自定义引擎。v1.8.0 模块构建版本基于 Godot 4.7(GDExtension 支持 4.6+)。模块构建版本为 C# 提供 NuGet 包:
ini
undefined

Add local NuGet source to your project:

Add local NuGet source to your project:

dotnet nuget add source path/to/nupkgs --name LimboNugetSource

dotnet nuget add source path/to/nupkgs --name LimboNugetSource


---

---

3. Behavior trees

3. 行为树

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.
BehaviorTree
资源保存任务树。
BTPlayer
在每个物理帧运行它(或在 idle/manual 模式下运行)。将
BTPlayer
添加为 Agent 节点的子节点,并指定
BehaviorTree
资源。

GDScript

GDScript

gdscript
undefined
gdscript
undefined

EnemyAI.gd — assign behavior_tree in the Inspector or here

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). # Connect to updated(status) to react when the tree finishes. bt_player.updated.connect(_on_bt_updated)
func _on_bt_updated(status: int) -> void: if status == BT.SUCCESS: bt_player.restart() # loop the tree
undefined
extends CharacterBody2D
@onready var bt_player: BTPlayer = $BTPlayer
func _ready() -> void: # BTPlayer starts executing automatically (active = true by default). # Connect to updated(status) to react when the tree finishes. bt_player.updated.connect(_on_bt_updated)
func _on_bt_updated(status: int) -> void: if status == BT.SUCCESS: bt_player.restart() # loop the tree
undefined

C#

C#

csharp
// EnemyAI.cs
using Godot;

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

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

    private void OnBtUpdated(int status)
    {
        if (status == (int)BT.Status.Success)
            _btPlayer.Restart();
    }
}
BTPlayer.UpdateMode
controls when the tree ticks:
IDLE
(every
_process
),
PHYSICS
(every
_physics_process
, default), or
MANUAL
(call
bt_player.update(delta)
yourself).

csharp
// EnemyAI.cs
using Godot;

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

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

    private void OnBtUpdated(int status)
    {
        if (status == (int)BT.Status.Success)
            _btPlayer.Restart();
    }
}
BTPlayer.UpdateMode
控制树的 tick 时机:
IDLE
(每个
_process
)、
PHYSICS
(每个
_physics_process
,默认)或
MANUAL
(自行调用
bt_player.update(delta)
)。

4. Custom tasks

4. 自定义任务

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.
继承
BTAction
(多次 tick 执行的工作)或
BTCondition
(即时检查)。添加
@tool
注解,使
_generate_name()
和
_get_configuration_warnings()
能在编辑器中工作。将脚本放在
res://ai/tasks/
下;子文件夹会成为任务分类。

GDScript

GDScript

gdscript
@tool
extends BTAction
gdscript
@tool
extends BTAction

Moves the agent toward a blackboard position each tick.

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

```gdscript
@tool
extends BTCondition
@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

```gdscript
@tool
extends BTCondition

Returns SUCCESS if the agent is within range of a target node.

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: Node2D = blackboard.get_var(target_var, null) if not is_instance_valid(target): return FAILURE var in_range := agent.global_position.distance_squared_to( target.global_position) <= _max_sq return SUCCESS if in_range else FAILURE
undefined
@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: Node2D = blackboard.get_var(target_var, null) if not is_instance_valid(target): return FAILURE var in_range := agent.global_position.distance_squared_to( target.global_position) <= _max_sq return SUCCESS if in_range else FAILURE
undefined

C#

C#

csharp
// 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() { }
}
csharp
// 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.

csharp
// 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() { }
}
csharp
// 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()
在首次 tick 前执行一次 → 当状态从非 RUNNING 转为 RUNNING 时执行
_enter()
→ 每次执行时调用
_tick(delta)
→ SUCCESS 或 FAILURE 后执行
_exit()
。

5. Blackboard

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.
Blackboard
是一个由树中所有任务共享的
RefCounted
键/值存储区。使用
StringName
键(
&"key"
),并将其导出为任务属性,以便 Inspector 显示选择器。

GDScript

GDScript

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
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#

csharp
// 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.

csharp
// 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. Hierarchical state machine (LimboHSM)

6. 分层状态机(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).
LimboHSM
是一个
LimboState
节点,用于管理子级
LimboState
节点。当状态调用
dispatch(event)
时会触发转换。有关高级模式(任意状态转换、
BTState
、嵌套 HSM、guards),请参阅 references/hsm.md。

GDScript — scene-tree setup

GDScript——场景树设置

gdscript
undefined
gdscript
undefined

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

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)
undefined
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)
undefined

C# — scene-tree setup

C#——场景树设置

csharp
// Character.cs
using Godot;

public partial class Character : CharacterBody2D
{
    [Export] private LimboHSM _hsm;
    [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);
    }
}
csharp
// Character.cs
using Godot;

public partial class Character : CharacterBody2D
{
    [Export] private LimboHSM _hsm;
    [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

GDScript——状态脚本

gdscript
undefined
gdscript
undefined

IdleState.gd

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)
undefined
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)
undefined

C# — state script

C#——状态脚本

csharp
// 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);
    }
}

csharp
// 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
  • BBParam
    inspector binding: use module build; GDExtension lacks the param editor UI
  • 已将
    BTPlayer
    添加为 Agent 的子节点;已指定
    behavior_tree
    资源
  • 自定义任务已添加
    @tool
    (GDScript)或
    [Tool]
    (C#)注解,以便在编辑器中显示
  • 每个
    _tick
    都返回
    SUCCESS
    、
    FAILURE
    或
    RUNNING
    ——绝不返回
    void
    /
    null
  • Blackboard 键已记录为导出的
    StringName
    属性(后缀
    _var
    )
  • C# Blackboard 读取时显式转换
    Variant
    (
    (float)Blackboard.GetVar(...)
    )
  • C# 使用模块构建版本(而非 GDExtension)以获得支持
  • 使用
    BTPlayer.updated
    信号(而非已弃用的
    behavior_tree_finished
    )
  • HSM:在
    initialize()
    之前,已使用
    add_transition
    连接所有
    LimboState
    节点
  • HSM:在
    initialize()
    之后调用
    set_active(true)
  • HSM 转换是穷尽的——每个可达状态都有退出路径
  • BBParam
    Inspector 绑定:使用模块构建版本;GDExtension 缺少参数编辑器 UI