Skip to content

行为树

行为树(BT)模块提供 JSON 驱动的行为树系统,支持黑板数据共享、输入/输出端口、子树、事件系统和结构化日志。适用于机器人、游戏 AI 和工作流自动化等复杂决策逻辑。

概述

行为树是一种模块化的替代状态机方案,用于编码复杂逻辑。它通过组合器(序列、选择器、装饰器)将简单的构建块(动作、条件)组合成层次树。

相比 FSM 的优势:

  • 可组合性 — 节点可复用和组合
  • 可读性 — 树结构直接映射决策逻辑
  • 可扩展性 — 通过添加子树扩展行为,无需重新布线

头文件

cpp
#include "xtils/fsm/behavior_tree.h"

核心概念

节点类型

类型角色示例
Composite控制子节点执行顺序Sequence, Selector
Action执行工作,返回状态MoveToTarget, SendMessage
Decorator修改子节点行为Inverter, Delay, Retry

状态

每次 tick 返回以下状态之一:

cpp
enum class Status {
  Success,   // 节点成功完成
  Failure,   // 节点失败
  Running,   // 节点需要更多 tick 才能完成
  Idle       // 节点尚未启动
};

Tick 生命周期

cpp
class Node {
  Status tick();                                // 由父节点每帧调用
  virtual Status OnStart() { return Running; }  // 首次 tick 初始化
  virtual Status OnTick() = 0;                  // 主逻辑(每次 tick 调用)
  virtual void OnStop() {}                      // Success/Failure 后的清理
};

内置节点

节点类型描述
SequenceComposite从左到右运行子节点;遇到第一个失败即失败
SelectorComposite从左到右运行子节点;遇到第一个成功即成功
InverterDecorator反转子节点结果(Success↔Failure)
DelayDecorator延迟子节点执行(delay_ms 端口)
RetryDecorator失败时重试子节点(最多 max_attempts 次)
SubTreeDecorator执行已注册的子树(tree_name 端口)
SimpleActionAction包装 std::function<Status()>
WaitForEventAction阻塞直到事件触发
EventGuardDecorator事件触发时中断子节点

BtFactory — 构建树

cpp
BtFactory factory;

// 注册自定义节点类型
factory.Register<MoveToTarget>("MoveToTarget");
factory.Register<CheckBattery>("CheckBattery");

// 注册简单内联动作
factory.RegisterSimpleAction([]() {
  LogI("来自 BT 的问候!");
  return Status::Success;
}, "SayHello");

// 从 JSON 文件加载
factory.LoadTreeFile("trees/patrol.json");
size_t count = factory.LoadTreesFromDirectory("./bt_trees");

// 构建并运行
auto blackboard = std::make_shared<AnyMap>();
blackboard->set("target_x", 10);
auto tree = factory.buildFromRegisteredTree("patrol", blackboard);

while (tree->tick() == Status::Running) {
  std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

JSON 树格式

json
{
  "name": "patrol",
  "root": {
    "type": "Sequence",
    "children": [
      {
        "type": "CheckBattery",
        "ports": { "min_level": "20" }
      },
      {
        "type": "MoveToTarget",
        "ports": { "target_x": "{waypoint_x}", "target_y": "{waypoint_y}" }
      }
    ]
  }
}

{大括号} 中的端口值引用黑板键。字面值直接传递。

端口与黑板

声明端口

cpp
class MoveToTarget : public ActionNode {
 public:
  MoveToTarget(const std::string& name = "") : ActionNode(name) {}

  static Ports getPorts() {
    return {
      InputPort<int>("target_x"),
      InputPort<int>("target_y"),
      OutputPort<bool>("reached")
    };
  }

  Status OnTick() override {
    auto x = getInput<int>("target_x");
    auto y = getInput<int>("target_y");
    if (!x || !y) return Status::Failure;
    
    // 移动逻辑...
    if (at_target) {
      setOutput("reached", true);
      return Status::Success;
    }
    return Status::Running;
  }
};

黑板(AnyMap)

黑板在树中的所有节点间共享:

cpp
AnyMap blackboard;

blackboard.set<int>("counter", 0);
blackboard.set<std::string>("target", "waypoint_a");

auto counter = blackboard.get<int>("counter");      // → std::optional<int>
bool exists = blackboard.has("counter");
auto keys = blackboard.keys();  // → vector<string>

BtTree 接口

cpp
class BtTree {
 public:
  Status tick();
  void reset();
  void shutdown();
  AnyMap& blackboard();
  std::string dump();
  Json dumpTree();

  // 暂停/恢复
  void pause();
  void resume();
  bool isPaused() const;

  // 事件系统
  void sendEvent(EventType type, const AnyData& data = {});
  std::optional<BtEvent> consumeEvent(EventType type);
  bool hasEvent(EventType type) const;
  void clearEvents();
};

子树

子树允许从较小的可复用树组合复杂行为:

json
{
  "name": "main",
  "root": {
    "type": "Sequence",
    "children": [
      { "type": "SubTree", "ports": { "tree_name": "navigate" } },
      { "type": "SubTree", "ports": { "tree_name": "interact" } }
    ]
  }
}

每个子树共享父树的黑板,实现树间数据流。

BtLogger

结构化日志,用于离线分析和实时调试:

cpp
#include "xtils/fsm/bt_logger.h"

auto file_logger = std::make_shared<BtFileLogger>("bt_trace.log");
auto inspect_logger = std::make_shared<BtInspectLogger>("/debug/bt");

auto logger = std::make_shared<BtCompositeLogger>();
logger->Add(file_logger);
logger->Add(inspect_logger);

auto tree = factory.buildFromRegisteredTree("main", blackboard, logger);

完整示例

cpp
#include <xtils/fsm/behavior_tree.h>
#include <xtils/logging/logger.h>

using namespace xtils;

class PrintMessage : public ActionNode {
 public:
  PrintMessage(const std::string& name = "") : ActionNode(name) {}
  static Ports getPorts() { return { InputPort<std::string>("text") }; }
  
  Status OnTick() override {
    auto text = getInput<std::string>("text");
    if (!text) return Status::Failure;
    LogI("BT 输出: %s", text->c_str());
    return Status::Success;
  }
};

class CountDown : public ActionNode {
 public:
  CountDown(const std::string& name = "") : ActionNode(name) {}
  static Ports getPorts() { return { InputPort<int>("from") }; }

  Status OnStart() override {
    count_ = getInput<int>("from").value_or(3);
    return Status::Running;
  }

  Status OnTick() override {
    LogI("  %d...", count_);
    if (--count_ <= 0) return Status::Success;
    return Status::Running;
  }

 private:
  int count_ = 0;
};

int main() {
  BtFactory factory;
  factory.Register<PrintMessage>("PrintMessage");
  factory.Register<CountDown>("CountDown");

  Json tree_json = Json::parse(R"({
    "name": "demo",
    "root": {
      "type": "Sequence",
      "children": [
        { "type": "PrintMessage", "ports": { "text": "开始倒计时!" } },
        { "type": "CountDown", "ports": { "from": "5" } },
        { "type": "PrintMessage", "ports": { "text": "完成!" } }
      ]
    }
  })").value();

  auto tree = factory.buildFromJson(tree_json);
  
  while (tree->tick() == Status::Running) {
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
  }
  
  return 0;
}

可视化工具

关于行为树的可视化设计和调试,请参阅 BT 编辑器与调试器 — 配套的浏览器端工具,支持:

  • 拖拽式树设计并导出 JSON
  • JSONL 日志回放用于离线执行分析
  • 实时 WebSocket 调试运行中的树

基于 MIT 许可证发布