用C++实现一个高效可扩展的行为树(Behavior Tree)框架

在游戏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 是工业级参考,而本文代码更适合理解原理与嵌入小型项目。

掌握行为树的设计与实现,是你构建智能系统的坚实一步。

相关推荐
bkspiderx3 小时前
C++设计模式之行为型模式:模板方法模式(Template Method)
c++·设计模式·模板方法模式
码农阿树3 小时前
Java 离线视频目标检测性能优化:从 Graphics2D 到 OpenCV 原生绘图的 20 倍性能提升实战
java·yolo·目标检测·音视频
夫唯不争,故无尤也3 小时前
Maven创建Java项目实战全流程
java·数据仓库·hive·hadoop·maven
weixin_404551243 小时前
openrewrite Maven plugin configuration
java·maven·configuration·openrewrite
我是华为OD~HR~栗栗呀3 小时前
华为OD-23届考研-Java面经
java·c++·后端·python·华为od·华为·面试
yan8626592463 小时前
于 C++ 的虚函数多态 和 模板方法模式 的结合
java·开发语言·算法
Le1Yu3 小时前
服务注册、服务发现、OpenFeign及其OKHttp连接池实现
java·服务器
想ai抽3 小时前
深入starrocks-怎样实现多列联合统计信息
java·数据库·数据仓库
mit6.8243 小时前
pq|二维前缀和
c++