BehaviorTree行为树 【调试】 5

1、日志记录器

Logger 是什么?

  • 一个可以运行时动态添加到行为树的观察者类

  • 采用观察者模式的非侵入式实现

  • 每个节点状态改变时自动触发回调

回调函数参数

cpp 复制代码
void callback(
    BT::Duration timestamp,   // 状态变化发生的时间点
    const TreeNode& node,     // 状态变化的节点引用
    NodeStatus prev_status,   // 变化前的状态
    NodeStatus status);       // 变化后的状态

使用示例

cpp 复制代码
#include "behaviortree_cpp/bt_factory.h"
#include "behaviortree_cpp/loggers/bt_cout_logger.h"

// 自定义 Logger 类
class MyCustomLogger : public BT::StatusChangeLogger
{
public:
    MyCustomLogger(const BT::Tree& tree) : StatusChangeLogger(tree) {}
    
    virtual void callback(
        BT::Duration timestamp,
        const BT::TreeNode& node,
        BT::NodeStatus prev_status,
        BT::NodeStatus status) override
    {
        // 记录时间戳(毫秒)
        auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(timestamp);
        
        // 输出节点状态变化信息
        std::cout << "[" << ms.count() << "ms] "
                  << node.name() << ": "
                  << BT::toStr(prev_status) 
                  << " -> " 
                  << BT::toStr(status) 
                  << std::endl;
        
        // 可以添加更多自定义逻辑
        if(status == BT::NodeStatus::FAILURE) {
            std::cout << "  ⚠️ 节点 " << node.name() << " 执行失败!" << std::endl;
        }
    }
    
    virtual void flush() override {
        std::cout << "--- Logger 数据刷新 ---" << std::endl;
    }
};



int main()
{
    BT::BehaviorTreeFactory factory;
    
    // 注册节点(示例)
    factory.registerSimpleAction("SayHello", []{ 
        std::cout << "Hello!" << std::endl;
        return BT::NodeStatus::SUCCESS;
    });
    
    // 创建行为树
    auto tree = factory.createTreeFromText(`
        <root>
            <Sequence>
                <SayHello />
                <Wait duration="1000"/>
            </Sequence>
        </root>
    `);
    
    // 1. 使用内置的 StdCoutLogger(输出到控制台)
    BT::StdCoutLogger logger_cout(tree);
    
    // 2. 使用内置的 FileLogger(输出到文件)
    BT::FileLogger logger_file(tree, "bt_trace.fbl");
    
    // 3. 使用自定义 Logger
    MyCustomLogger logger_custom(tree);
    
    // 4. 使用多个 Logger(可同时添加多个)
    std::vector<std::unique_ptr<BT::StatusChangeLogger>> loggers;
    loggers.emplace_back(new BT::StdCoutLogger(tree));
    loggers.emplace_back(new BT::FileLogger(tree, "trace.fbl"));
    
    // 执行行为树
    tree.tickWhileRunning();
    
    return 0;
}

添加时机 :在行为树创建后、开始执行前添加 Logger

内置 Logger 类型

BT.CPP 提供的内置 Logger:

  • BT::StdCoutLogger:输出到标准控制台

  • BT::FileLogger:输出到二进制文件(可使用工具分析)

  • BT::MinitraceLogger:生成 Chrome Tracing 格式

  • BT::PublisherZMQ:通过 ZeroMQ 发布状态变化

这种设计使得行为树的调试、监控和性能分析变得非常灵活,无需修改节点实现即可获得完整的执行跟踪。

2、TreeObserver 设计分析

目的

TreeObserver 是一个行为树节点的观察者/日志记录器,主要用于:

  1. 单元测试:验证特定条件下节点是否按预期执行

  2. 性能监控:统计节点执行频率和结果分布

  3. 调试:追踪节点状态变化历史

  4. 行为分析:了解树的实际执行路径

NodeStatistics 结构体解析

cpp 复制代码
struct NodeStatistics {
    NodeStatus last_result;      // 最后一次有效结果(仅 SUCCESS/FAILURE)
    NodeStatus current_status;   // 当前状态(包括 IDLE/SKIPPED)
    unsigned transitions_count;  // 状态转换次数(不含 IDLE)
    unsigned success_count;      // 转换为 SUCCESS 的次数
    unsigned failure_count;      // 转换为 FAILURE 的次数
    unsigned skip_count;         // 转换为 SKIPPED 的次数
    Duration last_timestamp;     // 最后一次状态转换的时间戳
};

代码示例:

xml

cpp 复制代码
<root BTCPP_format="4">
  
  <BehaviorTree ID="MainTree">
    <Sequence>
     <Fallback>
       <AlwaysFailure name="failing_action"/>
       <SubTree ID="SubTreeA" name="mysub"/>
     </Fallback>
     <AlwaysSuccess name="last_action"/>
    </Sequence>
  </BehaviorTree>

  <BehaviorTree ID="SubTreeA">
    <Sequence>
      <AlwaysSuccess name="action_subA"/>
      <SubTree ID="SubTreeB" name="sub_nested"/>
      <SubTree ID="SubTreeB" />
    </Sequence>
  </BehaviorTree>

  <BehaviorTree ID="SubTreeB">
    <AlwaysSuccess name="action_subB"/>
  </BehaviorTree>
  
</root>

c++

cpp 复制代码
#include "behaviortreedeps/include/behaviortree_cpp/bt_factory.h"
#include "behaviortreedeps/include/behaviortree_cpp/loggers/bt_observer.h"

using namespace BT;
namespace chr = std::chrono;

int main()
{
    BT::BehaviorTreeFactory factory;

    factory.registerBehaviorTreeFromFile("./tree.xml");
    auto tree = factory.createTree("MainTree");
    tree.tickWhileRunning();

    // Helper function to print the tree.
    BT::printTreeRecursively(tree.rootNode());

    // The purpose of the observer is to save some statistics about the number of times
    // a certain node returns SUCCESS or FAILURE.
    // This is particularly useful to create unit tests and to check if
    // a certain set of transitions happened as expected
    BT::TreeObserver observer(tree);

    // Print the unique ID and the corresponding human readable path
    // Path is also expected to be unique.
    std::map<uint16_t, std::string> ordered_UID_to_path;
    for (const auto& [name, uid] : observer.pathToUID())
    {
        ordered_UID_to_path[uid] = name;
    }

    for (const auto& [uid, name] : ordered_UID_to_path)
    {
        std::cout << uid << " -> " << name << std::endl;
    }

    tree.tickWhileRunning();

    // You can access a specific statistic, using is full path or the UID
    const auto& last_action_stats = observer.getStatistics("last_action");
    assert(last_action_stats.transitions_count > 0);

    std::cout << "----------------" << std::endl;
    // print all the statistics
    for (const auto& [uid, name] : ordered_UID_to_path)
    {
        const auto& stats = observer.getStatistics(uid);

        std::cout << "[" << name << "] \tT/S/F:  " << stats.transitions_count << "/" << stats.success_count << "/" << stats.failure_count << std::endl;
    }

    return 0;
}

执行结果 :

总结:

Logger和TreeObserver都是树行为调试的工具 但是感觉可视化效果不太好

相关推荐
一拳不是超人5 分钟前
AI时代,35岁程序员焦虑终结:经验从负债变资产
人工智能·程序员
IT_陈寒1 小时前
Vite快得离谱?揭秘它比Webpack快10倍的5个核心原理
前端·人工智能·后端
风象南2 小时前
OpenClaw 登顶 GitHub Star 榜首:一个程序员 13 年后的"重新点火"故事
人工智能·后端
TF男孩11 小时前
重新认识Markdown:它不仅是排版工具,更是写Prompt的最佳结构
人工智能
想打游戏的程序猿11 小时前
AI时代的内容输出
人工智能
小兵张健12 小时前
Playwright MCP 截图标注方案调研:推荐方案 1
人工智能
凌杰14 小时前
AI 学习笔记:Agent 的能力体系
人工智能
IT_陈寒15 小时前
React状态管理终极对决:Redux vs Context API谁更胜一筹?
前端·人工智能·后端
舒一笑16 小时前
如何获取最新的技术趋势和热门技术
人工智能·程序员
聚客AI16 小时前
🎉OpenClaw深度解析:多智能体协同的三种模式、四大必装技能与自动化运维秘籍
人工智能·开源·agent