在游戏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 是工业级参考,而本文代码更适合理解原理与嵌入小型项目。
掌握行为树的设计与实现,是你构建智能系统的坚实一步。