关于 beehave
Beehave 是面向 Godot 4.1+、以 GDScript 编写的轻量行为树插件,适合希望通过场景树组织游戏 AI 的项目。它提供组合节点、装饰器、黑板和运行时可视化调试器。
主要功能
- 01场景树节点式行为树
- 02组合节点与装饰器
- 03共享黑板键值存储
- 04运行时行为树可视化
- 05可调节树的执行频率
适用场景
- 01为游戏角色编排 AI 行为
- 02组合攻击条件与巡逻动作
- 03在多个行为树间共享状态
- 04运行时检查行为树节点执行
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jame581/ godotprompter --skill beehave安装后在 Claude Code 中输入/beehave确认已加载。
附带文件references/custom-nodes.md
详细介绍
Beehave
**相关技能:**移动叶节点使用 ai-navigation,核心引擎 FSM 使用 state-machine,更重量级的 C++ BT+HSM 替代方案使用 limboai,选择 AI 方案使用 godot-brainstorming。
**插件:**Beehave · 版本
v2.9.2· Godot 4.1+ · MIT · 源码:https://github.com/bitbrain/beehave · 使用 GDScript 编写(没有官方 C# API——本技能特意仅支持 GDScript)。
1. 何时使用 Beehave
| 方案 | 最适用于 |
|---|---|
核心引擎 FSM(state-machine 技能) | 简单 Agent、< 5 个状态、无需插件 |
| Beehave(GDScript 插件) | 轻量级 BT、仅使用 GDScript 的项目、快速迭代 |
| LimboAI | 同时使用 BT 和 HSM、可视化编辑器、C++ 性能、支持 C#(模块构建) |
当项目仅使用 GDScript、希望使用行为树而无需自定义引擎构建,并且重视简单的场景树节点创作工作流时,选择 Beehave。Beehave 树完全位于场景树中——每个复合节点、装饰器和叶节点都是常规的 Node 子节点。若要使用集成 HSM 的重量级 C++/C# 方案,请改用 limboai 技能。若要使用不带 BT 的普通状态机,请使用内置的 state-machine 技能。
**C# 说明:**Beehave 没有官方 C# API(addons/beehave/ 中没有任何 .cs 文件)。在 C# 中,你可以通过 Godot 跨语言互操作调用 GDScript API(GetNode<Node>(...).Call("tick", actor, blackboard)),但 Beehave 不提供强类型的 C# 类。
2. 安装与启用
- Godot AssetLib → 搜索“Beehave” → 下载 → 重新加载项目。
或者将 GitHub 发布页中的addons/beehave/文件夹复制到res://addons/beehave/。 - 启用插件:Project → Project Settings → Plugins → 勾选 Beehave。
系统会注册两个自动加载项:BeehaveGlobalMetrics和BeehaveGlobalDebugger。 - 可选——将插件中的
script_templates/复制到项目根目录,用作叶节点脚手架模板。
3. 树的构成
Beehave 树由三类节点构成,它们都作为常规场景树子节点放置:
| 角色 | 节点 | 行为 |
|---|---|---|
| 树根 | BeehaveTree | 每帧(或物理帧/手动)对其子节点执行 Tick;继承自 Node(而非 BeehaveNode) |
| 复合节点 | SequenceComposite、SelectorComposite、SimpleParallelComposite、… | 流程控制——AND / OR / 并行逻辑 |
| 装饰器 | InverterDecorator、CooldownDecorator、RepeaterDecorator、… | 包装一个子节点以修改其结果 |
| 叶节点 | ActionLeaf、ConditionLeaf 子类 | 你的自定义游戏逻辑 |
复合节点速查
| 类 | 逻辑 |
|---|---|
SequenceComposite | AND——所有子节点都必须成功;遇到第一个失败的子节点时失败 |
SequenceReactiveComposite | AND——运行期间,每次 Tick 都从第一个子节点开始重新评估 |
SelectorComposite | OR——遇到第一个成功的子节点时成功;所有子节点都失败时才失败 |
SelectorReactiveComposite | OR——运行期间,每次 Tick 都从第一个子节点开始重新评估 |
SimpleParallelComposite | 同时运行两个子节点;结果取决于主子节点(子节点 0) |
SequenceRandomComposite | 随机排序的 AND——按随机顺序执行子节点 |
SelectorRandomComposite | 随机排序的 OR——按随机顺序尝试子节点 |
装饰器速查表
| 类 | 效果 |
|---|---|
InverterDecorator | 翻转 SUCCESS ↔ FAILURE;RUNNING 状态原样传递 |
AlwaysSucceedDecorator | 强制返回 SUCCESS;RUNNING 状态原样传递 |
AlwaysFailDecorator | 强制返回 FAILURE;RUNNING 状态原样传递 |
RepeaterDecorator | 重新运行子节点,直到其成功达到 repetitions 次 |
LimiterDecorator | 将子节点限制为最多运行 max_count 个 tick,之后返回 FAILURE |
CooldownDecorator | 子节点结束后,在 wait_time 秒内阻止再次执行 |
TimeLimiterDecorator | 给子节点 wait_time 秒;如果仍在运行,则中断它 |
DelayDecorator | 等待 wait_time 秒后再首次执行子节点 |
UntilFailDecorator | 循环运行子节点,直到其返回 FAILURE,然后返回 SUCCESS |
最简场景树示例
# Scene tree:
# Enemy (CharacterBody2D)
# BeehaveTree ← tick_rate = 1, process_thread = PHYSICS
# SelectorComposite
# SequenceComposite ← "attack if in range"
# IsInRangeCondition
# AttackAction
# PatrolAction ← fallback
# BeehaveTree exports:
# @export var enabled: bool = true
# @export var tick_rate: int = 1 (1 = every frame; 3 = every 3 frames)
# @export var process_thread: ProcessThread = PHYSICS
# @export var blackboard: Blackboard (auto-created if not set)
# @export_node_path var actor_node_path (defaults to parent node)
# Access the tree from code if you need manual control:
@onready var bt: BeehaveTree = $BeehaveTree
func _ready() -> void:
# Reduce tick cost: evaluate AI every 3 physics frames
bt.tick_rate = 3
# Default process_thread is PHYSICS — switch to IDLE if actor uses _process
bt.process_thread = BeehaveTree.ProcessThread.IDLE
tick_rate 说明:
tick_rate = 1表示每帧都会进行求值;tick_rate = 3表示每 3 帧进行一次求值。对于距离较远或处于后台的 NPC,可调高该值以节省 CPU。默认处理线程为PHYSICS——如果角色脚本使用_process而非_physics_process,请将process_thread = IDLE,使它们保持同步。
4. 叶节点约定
叶节点负责承载你的游戏逻辑。对于需要跨多个 tick 执行的工作,继承 ActionLeaf;对于单帧检查,继承 ConditionLeaf,然后重写 tick(actor, blackboard)。
# IsInRangeCondition.gd
class_name IsInRangeCondition
extends ConditionLeaf
@export var detection_range: float = 150.0
func tick(actor: Node, blackboard: Blackboard) -> int:
# Beehave types `actor` as Node; cast to your concrete type for 2D members.
var body := actor as Node2D
var target: Node2D = blackboard.get_value("target")
if body == null or not is_instance_valid(target):
return FAILURE
var in_range := body.global_position.distance_to(target.global_position) <= detection_range
return SUCCESS if in_range else FAILURE
# AttackAction.gd
class_name AttackAction
extends ActionLeaf
@export var attack_duration: float = 0.5
func tick(actor: Node, blackboard: Blackboard) -> int:
var elapsed: float = blackboard.get_value("attack_elapsed", 0.0)
elapsed += get_physics_process_delta_time()
if elapsed >= attack_duration:
blackboard.erase_value("attack_elapsed")
# `actor` is typed Node; guard game-specific methods (or cast to your actor type).
if actor.has_method("play_attack_animation"):
actor.call("play_attack_animation")
return SUCCESS
blackboard.set_value("attack_elapsed", elapsed)
return RUNNING
func interrupt(actor: Node, blackboard: Blackboard) -> void:
# Clean up per-run state when the tree interrupts this action (after_run is not called then)
blackboard.erase_value("attack_elapsed")
super(actor, blackboard) # keeps the debugger's interrupt message
返回码(定义于 BeehaveNode):
SUCCESS— 动作完成/条件满足。FAILURE— 动作失败/条件不满足;由父复合节点决定下一步操作。RUNNING— 动作需要更多帧;行为树会在下一帧再次调用tick()(仅限ActionLeaf——ConditionLeaf不应返回RUNNING)。
可选重写方法:
before_run(actor, blackboard)— 在一次运行的首次 tick 之前调用一次。after_run(actor, blackboard)— 在返回SUCCESS/FAILURE的 tick 之后调用;中断时不会调用。interrupt(actor, blackboard)— 当行为树中断正在运行的节点时调用。
5. 黑板
Blackboard 节点是一个共享的键值存储,会传递给每次 tick() 调用。如果你没有指定外部 Blackboard 节点,BeehaveTree 会自动创建一个内部节点。
# Share one Blackboard across multiple BeehaveTrees on the same actor.
# Assign the same exported Blackboard node to each tree in the Inspector.
# Read / write from any leaf's tick():
func tick(actor: Node, blackboard: Blackboard) -> int:
# Write
blackboard.set_value("target", actor.get_nearest_enemy())
# Read with default
var speed: float = blackboard.get_value("move_speed", 200.0)
# Conditional check
if blackboard.has_value("stunned"):
return FAILURE
# Erase (sets key to null; has_value returns false after erase)
blackboard.erase_value("temp_flag")
return SUCCESS
命名空间:
set_value、get_value、has_value和erase_value接受可选参数blackboard_name: String(默认为"default")。你可以用它在同一个Blackboard节点上维护不同的命名空间,避免名称冲突(例如,按敌人区分状态与共享世界状态)。
内置表达式叶节点:
BlackboardSetAction、BlackboardEraseAction、BlackboardHasCondition和BlackboardCompareCondition可让你完全通过 Inspector 导出属性操作 Blackboard(无需 GDScript)。表达式通过 Godot 的Expression.execute([], blackboard)运行,因此你可以直接在表达式字符串中调用get_value("key")。
6. 可视化调试器
Beehave 附带一个 EditorDebuggerPlugin,在游戏运行时会在编辑器底部面板中添加 🐝 Beehave 标签页:
- 从 Godot 编辑器运行项目。
- 打开 Debugger 面板 → 点击 🐝 Beehave 标签页。
- 从列表中选择一棵树以启用实时可视化——每次 tick 时,活动节点都会高亮显示。
- 可选:点击分离按钮以浮动显示面板;或者将 Project Settings → beehave/debugger/start_detached = true 设为启用,使面板始终以分离状态启动。
若要在 Performance 面板中跟踪每棵树的 CPU 开销,请在 BeehaveTree 节点上将 custom_monitor = true。这会注册 beehave [microseconds]/process_time_<actor_name>-<id> 作为 Performance 监视器。
有关编写自定义装饰器和条件节点的教程,请参阅 references/custom-nodes.md。
实施清单
- 已将
addons/beehave/复制到项目中;已在 项目设置 → 插件 中启用插件 - 已将
BeehaveTree添加为 actor 的子节点;已设置actor_node_path(或留空以默认指向父节点) -
process_thread与 actor 的循环匹配:_physics_process使用PHYSICS,_process使用IDLE - 已调整
tick_rate——提高后台 NPC 的值(例如3),以降低每帧开销 - 每个
tick()重写都返回SUCCESS、FAILURE或RUNNING——绝不返回void/null -
ConditionLeaf子类绝不返回RUNNING - 每次运行的状态写入
Blackboard,而不是存储在叶节点本身 -
after_run或interrupt会清理该动作写入的所有 Blackboard 键 - 当多个
BeehaveTree节点需要共享相同数据时,导出并共享外部Blackboard节点 - 发货 AI 逻辑前,在运行时检查可视化调试器,验证 tick 流程