在游戏AI、机器人控制和复杂系统调度中,行为树(Behavior Tree, BT) 已成为替代传统有限状态机(FSM)的主流架构。相比 FSM 的状态爆炸问题,行为树通过树形结构组合简单动作,实现了高度模块化、可读性强且易于调试的决策逻辑。
本文将从零开始,使用现代 C++(C++17 及以上)实现一个轻量级但功能完整的 行为树框架,涵盖核心节点类型、黑板通信机制、装饰器模式与并行执行支持,并提供最佳实践建议。
一、行为树基础:概念与优势
行为树是一种树状决策结构,每个节点返回三种状态:
SUCCESS
:任务成功完成FAILURE
:任务失败RUNNING
:任务正在执行(用于异步操作)
常见节点类型:
类型 | 说明 |
---|---|
Composite 节点 | 控制子节点执行顺序(如 Sequence、Selector) |
Action 节点 | 执行具体逻辑(如"移动到目标") |
Decorator 节点 | 修改单个子节点行为(如 Retry、Invert) |
Condition 节点 | 返回条件判断结果(本质是特殊的 Action) |
✅ 相比 FSM 的优势:
- 更易扩展和复用
- 支持并发与中断(如抢占式行为)
- 逻辑清晰,适合可视化编辑
二、核心设计:节点基类与枚举定义
我们首先定义基本的状态枚举和抽象节点类:
cpp
enum class NodeStatus {
SUCCESS,
FAILURE,
RUNNING
};
class BehaviorNode {
public:
virtual ~BehaviorNode() = default;
virtual NodeStatus tick() = 0; // 核心执行接口
virtual void reset() { /* 可选重置逻辑 */ } // 便于重复使用
};
所有具体节点都将继承自 BehaviorNode
并实现 tick()
方法。
三、组合节点实现
1. Sequence(顺序执行)
只有当所有子节点依次成功时才返回 SUCCESS
,任一失败则中断并返回 FAILURE
。
cpp
class Sequence : public BehaviorNode {
std::vector<std::unique_ptr<BehaviorNode>> children;
size_t current_index = 0;
public:
void add_child(std::unique_ptr<BehaviorNode> child) {
children.push_back(std::move(child));
}
NodeStatus tick() override {
while (current_index < children.size()) {
auto status = children[current_index]->tick();
if (status == NodeStatus::RUNNING) {
return NodeStatus::RUNNING;
}
if (status == NodeStatus::FAILURE) {
current_index = 0;
return NodeStatus::FAILURE;
}
++current_index; // 成功,继续下一个
}
current_index = 0;
return NodeStatus::SUCCESS;
}
void reset() override {
current_index = 0;
for (auto& child : children) {
child->reset();
}
}
};
2. Selector(选择执行)
尝试每个子节点,直到有一个成功为止。
cpp
class Selector : public BehaviorNode {
std::vector<std::unique_ptr<BehaviorNode>> children;
size_t current_index = 0;
public:
void add_child(std::unique_ptr<BehaviorNode> child) {
children.push_back(std::move(child));
}
NodeStatus tick() override {
while (current_index < children.size()) {
auto status = children[current_index]->tick();
if (status == NodeStatus::RUNNING) {
return NodeStatus::RUNNING;
}
if (status == NodeStatus::SUCCESS) {
current_index = 0;
return NodeStatus::SUCCESS;
}
++current_index;
}
current_index = 0;
return NodeStatus::FAILURE;
}
void reset() override {
current_index = 0;
for (auto& child : children) {
child->reset();
}
}
};
四、装饰器节点:增强行为控制
装饰器包装一个子节点,改变其行为逻辑。
示例:RetryUntilSuccess
重复执行子节点直到成功或达到最大次数。
cpp
class RetryNode : public BehaviorNode {
std::unique_ptr<BehaviorNode> child;
int max_retries;
int attempt_count = 0;
public:
RetryNode(std::unique_ptr<BehaviorNode> c, int retries)
: child(std::move(c)), max_retries(retries) {}
NodeStatus tick() override {
while (attempt_count <= max_retries) {
auto status = child->tick();
if (status == NodeStatus::SUCCESS) {
reset();
return NodeStatus::SUCCESS;
}
if (status == NodeStatus::RUNNING) {
return NodeStatus::RUNNING;
}
++attempt_count;
}
reset();
return NodeStatus::FAILURE;
}
void reset() override {
attempt_count = 0;
child->reset();
}
};
其他常见装饰器:Inverter
(反转结果)、Succeeder
(强制成功)、Timeout
等。
五、动作与条件节点示例
cpp
// 黑板(Blackboard)------跨节点共享数据
struct Blackboard {
bool has_target = false;
float health = 100.0f;
};
class IsLowHealth : public BehaviorNode {
Blackboard* bb;
public:
explicit IsLowHealth(Blackboard* b) : bb(b) {}
NodeStatus tick() override {
return (bb->health < 30.0f) ? NodeStatus::SUCCESS : NodeStatus::FAILURE;
}
};
class HealSelf : public BehaviorNode {
Blackboard* bb;
public:
explicit HealSelf(Blackboard* b) : bb(b) {}
NodeStatus tick() override {
bb->health = std::min(100.0f, bb->health + 10.0f);
std::cout << "Healing... Current health: " << bb->health << "\n";
return NodeStatus::SUCCESS;
}
};
六、构建完整行为树
cpp
int main() {
Blackboard bb{false, 25.0f};
auto heal_check = std::make_unique<IsLowHealth>(&bb);
auto heal_action = std::make_unique<HealSelf>(&bb);
auto healing_sequence = std::make_unique<Sequence>();
healing_sequence->add_child(std::move(heal_check));
healing_sequence->add_child(std::move(heal_action));
auto root = std::make_unique<RetryNode>(std::move(healing_sequence), 5);
// 模拟 AI 循环
while (bb.health < 100.0f) {
auto status = root->tick();
if (status == NodeStatus::RUNNING || status == NodeStatus::SUCCESS) {
continue;
} else {
std::cout << "Healing failed!\n";
break;
}
}
return 0;
}
输出:
Healing... Current health: 35
Healing... Current health: 45
...
Healing... Current health: 100
七、进阶特性与最佳实践
✅ 使用黑板(Blackboard)进行数据通信
避免全局变量,推荐使用键值式黑板:
cpp
class Blackboard {
std::unordered_map<std::string, std::any> data;
public:
template<typename T>
void set(const std::string& key, const T& value) {
data[key] = value;
}
template<typename T>
T get(const std::string& key) {
return std::any_cast<T>(data.at(key));
}
};
✅ 支持并行节点(Parallel Node)
允许多个子节点同时运行,并根据策略决定整体状态。
cpp
class ParallelNode : public BehaviorNode {
std::vector<std::unique_ptr<BehaviorNode>> children;
std::vector<NodeStatus> statuses;
public:
ParallelNode(size_t n) : statuses(n, NodeStatus::RUNNING) {}
NodeStatus tick() override {
bool all_finished = true;
for (size_t i = 0; i < children.size(); ++i) {
if (statuses[i] == NodeStatus::RUNNING) {
statuses[i] = children[i]->tick();
all_finished = false;
}
}
// 示例策略:全部成功才算成功
if (all_finished) {
for (auto s : statuses) {
if (s != NodeStatus::SUCCESS) return NodeStatus::FAILURE;
}
return NodeStatus::SUCCESS;
}
return NodeStatus::RUNNING;
}
};
✅ 异步任务支持
对于长时间运行的动作(如寻路),RUNNING
状态可用于暂停树执行,待事件回调后再恢复。
✅ 序列化与可视化
可为节点添加 name()
、to_json()
方法,便于集成编辑器(如 BTStudio 或 Groot)。
八、性能优化建议
- 使用对象池管理节点实例,减少动态分配
- 避免深度递归调用(可通过栈模拟优化)
- 对高频更新的条件节点做缓存或节流
九、总结
行为树不仅是游戏 AI 的核心技术,也适用于自动驾驶、工业自动化等需要复杂决策的场景。通过 C++ 实现一个行为树框架,你能深入理解:
- 组合模式(Composite Pattern)
- 状态机与协程思想
- 运行时与编译期设计权衡
- 数据驱动架构(黑板机制)
本文实现的框架虽简洁,但已具备生产可用的基础能力。你可以在此基础上扩展:
- 动态树重构
- 学习型节点(结合 ML)
- 多优先级中断机制(如 High Priority Interrupt)
💡 提示:开源项目如 BehaviorTree.CPP 是工业级参考,而本文代码更适合理解原理与嵌入小型项目。
掌握行为树的设计与实现,是你构建智能系统的坚实一步。