[{"data":1,"prerenderedAt":8019},["ShallowReactive",2],{"wiki-page-\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0300-di-yi-ge-wan-zheng-cheng-xu":3,"wiki-doc-items-2023-12-30-ros2-tutorial":7600,"wiki:author-members":8007},{"id":4,"title":5,"authors":6,"body":8,"chapterDepth":244,"chapterOrder":7588,"date":7589,"description":140,"docKey":7590,"docRoot":7591,"docTitle":7592,"extension":7593,"isWikiDoc":839,"isWikiIndex":7594,"meta":7595,"navigation":839,"path":7596,"seo":7597,"stem":7598,"wikiDepth":201,"__hash__":7599},"wiki\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0300-第一个完整程序.md","第一个完整程序",[7],"董佳辉",{"type":9,"value":10,"toc":7546},"minimark",[11,16,49,52,55,58,62,65,130,133,143,146,161,178,181,183,187,190,356,359,365,370,466,477,488,491,493,497,500,506,509,566,569,575,585,589,592,626,632,635,638,642,649,660,663,666,668,672,675,678,731,740,747,751,754,888,899,902,906,912,934,940,951,953,956,959,1002,1009,1011,1015,1018,1081,1084,1088,1104,1151,1159,1165,1168,1172,1178,1214,1220,1229,1234,1244,1263,1268,1271,1277,1280,1283,1299,1302,1365,1391,1404,1433,1436,1439,1450,1810,1820,1827,1833,1853,1856,1859,1866,1978,1983,1993,2003,2007,2014,2293,2299,2309,2312,2316,2322,2331,2337,2340,2417,2420,2493,2505,2530,2536,2538,2607,2610,2653,2667,2676,2688,2701,2705,2711,2768,2776,2780,2794,2803,2810,2815,2820,2823,2833,2838,2848,2853,2864,2867,2873,2878,2881,2887,2893,2896,2914,2928,2933,2935,2939,2952,3090,3102,3123,3129,3131,3196,3199,3238,3247,3251,3351,3354,3360,3374,3743,3746,3752,3757,3774,3780,3785,3800,3812,3814,3817,3824,3827,3830,3840,3859,3873,4190,4193,4245,4254,4257,4303,4320,4328,4334,4340,4385,4395,4401,4404,4467,4473,4496,4514,4539,4543,4552,4792,4803,4809,4811,4851,4854,4897,4906,4916,4920,4924,5029,5713,5720,5722,5728,5744,5751,5756,5761,5763,5767,5777,5781,5787,6024,6032,6055,6061,6069,6078,6081,6137,6182,6197,6201,6206,6330,7257,7263,7265,7271,7276,7279,7285,7303,7306,7309,7312,7326,7329,7356,7359,7362,7383,7386,7389,7395,7406,7408,7412,7417,7466,7479,7482,7487,7490,7503,7520,7535,7542],[12,13,15],"h2",{"id":14},"从节点xml-到第一个完整程序","从节点、XML 到第一个完整程序",[17,18,19],"blockquote",{},[20,21,22,23,30,31,36,37,42,43,48],"p",{},"对应 BehaviorTree.CPP 3.8 官方文档：",[24,25,29],"a",{"href":26,"rel":27},"https:\u002F\u002Fwww.behaviortree.dev\u002Fdocs\u002F3.8\u002Flearn-the-basics\u002Fmain_concepts\u002F",[28],"nofollow","Learn the Basic Concepts → Main Concepts","、",[24,32,35],{"href":33,"rel":34},"https:\u002F\u002Fwww.behaviortree.dev\u002Fdocs\u002F3.8\u002Flearn-the-basics\u002Fxml_format\u002F",[28],"The XML schema","，以及 Tutorial - Basics 中的 ",[24,38,41],{"href":39,"rel":40},"https:\u002F\u002Fwww.behaviortree.dev\u002Fdocs\u002F3.8\u002Ftutorial-basics\u002Ftutorial_01_first_tree\u002F",[28],"Your first Behavior Tree"," 和 ",[24,44,47],{"href":45,"rel":46},"https:\u002F\u002Fwww.behaviortree.dev\u002Fdocs\u002F3.8\u002Ftutorial-basics\u002Ftutorial_02_basic_ports\u002F",[28],"Blackboard and ports","。",[20,50,51],{},"前面已经能读懂节点与控制规则。接下来把它们变成可运行的程序，同时补齐 C++、XML、Ports 和 Blackboard 的基础关系。",[20,53,54],{},"本篇前半部分完成阶段 1 的概念学习，后半部分进入阶段 2 的编程实践。\n实践部分在同一个独立 C++ 工程中逐步增加功能：先运行一个最小动作，再加入电量检查和目标传递。实际导航任务留到异步动作章节实现。",[56,57],"hr",{},[12,59,61],{"id":60},"c-实现节点xml-组织节点","C++ 实现节点，XML 组织节点",[20,63,64],{},"可以把一个程序分成三部分：",[66,67,68,84],"table",{},[69,70,71],"thead",{},[72,73,74,78,81],"tr",{},[75,76,77],"th",{},"部分",[75,79,80],{},"负责什么",[75,82,83],{},"例子",[85,86,87,99,114],"tbody",{},[72,88,89,93,96],{},[90,91,92],"td",{},"C++ 节点",[90,94,95],{},"实现具体检查或操作，声明输入输出接口",[90,97,98],{},"检查电量、选择目标、打印目标",[72,100,101,104,107],{},[90,102,103],{},"XML",[90,105,106],{},"描述树的结构，配置节点参数和数据连接",[90,108,109,110],{},"把三个节点放进 ",[111,112,113],"code",{},"Sequence",[72,115,116,119,122],{},[90,117,118],{},"外部程序",[90,120,121],{},"注册节点、创建树、驱动执行",[90,123,124,30,127],{},[111,125,126],{},"BehaviorTreeFactory",[111,128,129],{},"tickRoot()",[20,131,132],{},"例如：",[134,135,141],"pre",{"className":136,"code":138,"language":139,"meta":140},[137],"language-text","C++ 注册可用的节点类型\n    ↓\n工厂读取 XML，创建具体节点实例并连接成树\n    ↓\n外部 tick 根节点\n    ↓\n按树的规则调用各节点的回调\n","text","",[111,142,138],{"__ignoreMap":140},[20,144,145],{},"“注册节点”是告诉工厂：遇到某个类型标识时，怎样创建对应的 C++ 节点。它不会立即执行任务。",[20,147,148,149,152,153,156,157,160],{},"例如 ",[111,150,151],{},"factory.registerNodeType\u003CPrintGoal>(\"PrintGoal\")"," 将字符串 ",[111,154,155],{},"PrintGoal"," 与 C++ 类关联；之后 XML 中每出现一次 ",[111,158,159],{},"\u003CPrintGoal\u002F>","，工厂就可以创建一个实例。注册类型和创建实例是两个步骤，同一类型可以创建多个实例。",[20,162,163,164,30,167,170,171,174,175,48],{},"普通自定义节点通常通过继承实现，例如继承 ",[111,165,166],{},"ConditionNode",[111,168,169],{},"SyncActionNode","。简单检查也可以通过 ",[111,172,173],{},"registerSimpleCondition()"," 包装函数或 lambda，简单同步动作可以用 ",[111,176,177],{},"registerSimpleAction()",[20,179,180],{},"树通常在程序初始化时创建一次，后续反复 tick，而不是每轮重新解析 XML。调整已有节点的排列或配置后，可以重新运行程序加载新的 XML；新增 C++ 节点实现时，仍要重新编译。",[56,182],{},[12,184,186],{"id":185},"用-xml-描述一棵完整的树","用 XML 描述一棵完整的树",[20,188,189],{},"下面是本篇逐步扩展后的 XML，用来认识文件结构。实践部分会先运行更小的树，最后再得到这个版本：",[134,191,195],{"className":192,"code":193,"language":194,"meta":140,"style":140},"language-xml shiki shiki-themes github-light github-dark","\u003Croot main_tree_to_execute=\"MainTree\">\n    \u003CBehaviorTree ID=\"MainTree\">\n        \u003CSequence name=\"mission_sequence\">\n            \u003CCheckBattery name=\"check_battery\"\u002F>\n            \u003CSelectGoal name=\"select_goal\" goal=\"{mission_goal}\"\u002F>\n            \u003CPrintGoal name=\"print_goal\" goal=\"{mission_goal}\"\u002F>\n        \u003C\u002FSequence>\n    \u003C\u002FBehaviorTree>\n\u003C\u002Froot>\n","xml",[111,196,197,224,242,260,279,304,326,336,346],{"__ignoreMap":140},[198,199,202,206,210,214,217,221],"span",{"class":200,"line":201},"line",1,[198,203,205],{"class":204},"sVt8B","\u003C",[198,207,209],{"class":208},"s9eBZ","root",[198,211,213],{"class":212},"sScJk"," main_tree_to_execute",[198,215,216],{"class":204},"=",[198,218,220],{"class":219},"sZZnC","\"MainTree\"",[198,222,223],{"class":204},">\n",[198,225,227,230,233,236,238,240],{"class":200,"line":226},2,[198,228,229],{"class":204},"    \u003C",[198,231,232],{"class":208},"BehaviorTree",[198,234,235],{"class":212}," ID",[198,237,216],{"class":204},[198,239,220],{"class":219},[198,241,223],{"class":204},[198,243,245,248,250,253,255,258],{"class":200,"line":244},3,[198,246,247],{"class":204},"        \u003C",[198,249,113],{"class":208},[198,251,252],{"class":212}," name",[198,254,216],{"class":204},[198,256,257],{"class":219},"\"mission_sequence\"",[198,259,223],{"class":204},[198,261,263,266,269,271,273,276],{"class":200,"line":262},4,[198,264,265],{"class":204},"            \u003C",[198,267,268],{"class":208},"CheckBattery",[198,270,252],{"class":212},[198,272,216],{"class":204},[198,274,275],{"class":219},"\"check_battery\"",[198,277,278],{"class":204},"\u002F>\n",[198,280,282,284,287,289,291,294,297,299,302],{"class":200,"line":281},5,[198,283,265],{"class":204},[198,285,286],{"class":208},"SelectGoal",[198,288,252],{"class":212},[198,290,216],{"class":204},[198,292,293],{"class":219},"\"select_goal\"",[198,295,296],{"class":212}," goal",[198,298,216],{"class":204},[198,300,301],{"class":219},"\"{mission_goal}\"",[198,303,278],{"class":204},[198,305,307,309,311,313,315,318,320,322,324],{"class":200,"line":306},6,[198,308,265],{"class":204},[198,310,155],{"class":208},[198,312,252],{"class":212},[198,314,216],{"class":204},[198,316,317],{"class":219},"\"print_goal\"",[198,319,296],{"class":212},[198,321,216],{"class":204},[198,323,301],{"class":219},[198,325,278],{"class":204},[198,327,329,332,334],{"class":200,"line":328},7,[198,330,331],{"class":204},"        \u003C\u002F",[198,333,113],{"class":208},[198,335,223],{"class":204},[198,337,339,342,344],{"class":200,"line":338},8,[198,340,341],{"class":204},"    \u003C\u002F",[198,343,232],{"class":208},[198,345,223],{"class":204},[198,347,349,352,354],{"class":200,"line":348},9,[198,350,351],{"class":204},"\u003C\u002F",[198,353,209],{"class":208},[198,355,223],{"class":204},[20,357,358],{},"它对应：",[134,360,363],{"className":361,"code":362,"language":139,"meta":140},[137],"Sequence\n├── CheckBattery\n├── SelectGoal      输出选定的目标\n└── PrintGoal       读取并打印该目标\n",[111,364,362],{"__ignoreMap":140},[366,367,369],"h3",{"id":368},"分清-xml-中几种不同的标识","分清 XML 中几种不同的标识",[66,371,372,382],{},[69,373,374],{},[72,375,376,379],{},[75,377,378],{},"写法",[75,380,381],{},"含义",[85,383,384,394,404,418,428,438,448],{},[72,385,386,391],{},[90,387,388],{},[111,389,390],{},"\u003Croot>",[90,392,393],{},"XML 文档的外层容器，不是可执行的行为树节点",[72,395,396,401],{},[90,397,398],{},[111,399,400],{},"main_tree_to_execute=\"MainTree\"",[90,402,403],{},"指定默认执行哪棵树",[72,405,406,411],{},[90,407,408],{},[111,409,410],{},"\u003CBehaviorTree ID=\"MainTree\">",[90,412,413,414,417],{},"定义一棵名叫 ",[111,415,416],{},"MainTree"," 的树",[72,419,420,425],{},[90,421,422],{},[111,423,424],{},"\u003CSequence>",[90,426,427],{},"这棵树实际的根节点",[72,429,430,435],{},[90,431,432],{},[111,433,434],{},"\u003CPrintGoal>",[90,436,437],{},"注册到工厂的节点类型标识，必须能找到对应实现",[72,439,440,445],{},[90,441,442],{},[111,443,444],{},"name=\"print_goal\"",[90,446,447],{},"当前节点实例的名称，便于日志和调试，可省略",[72,449,450,455],{},[90,451,452],{},[111,453,454],{},"goal=\"{mission_goal}\"",[90,456,457,458,461,462,465],{},"把节点的 ",[111,459,460],{},"goal"," 端口连接到 Blackboard 的 ",[111,463,464],{},"mission_goal"," 条目",[20,467,468,469,472,473,476],{},"因此，树的 ",[111,470,471],{},"ID","、节点类型的注册标识、实例的 ",[111,474,475],{},"name"," 和数据条目的名称，各自有不同用途。",[20,478,479,480,483,484,487],{},"一个 XML 文件可以定义多棵 ",[111,481,482],{},"\u003CBehaviorTree>","；每棵树下面应有一个实际根节点。\n只有一棵树时，可以省略 ",[111,485,486],{},"main_tree_to_execute","；\n有多棵树、需要从文件直接确定默认入口时，应明确填写。",[20,489,490],{},"XML 中的父子关系仍遵守前面的结构约束：控制节点包含一个或多个子节点，装饰节点包含一个，条件和动作节点没有子节点。",[56,492],{},[12,494,496],{"id":495},"ports-与-blackboard让数据在节点之间流动","Ports 与 Blackboard：让数据在节点之间流动",[20,498,499],{},"执行状态和业务数据走的是不同的通道：",[134,501,504],{"className":502,"code":503,"language":139,"meta":140},[137],"SUCCESS \u002F FAILURE \u002F RUNNING   告诉父节点任务的执行情况\nPorts + Blackboard           传递目标、路径、观测值等业务数据\n",[111,505,503],{"__ignoreMap":140},[20,507,508],{},"节点的输入输出端口类似函数接口；\nBlackboard 则用键值条目保存数据：",[66,510,511,521],{},[69,512,513],{},[72,514,515,518],{},[75,516,517],{},"概念",[75,519,520],{},"本例中的作用",[85,522,523,539,550],{},[72,524,525,531],{},[90,526,527,528],{},"输出端口 ",[111,529,530],{},"OutputPort",[90,532,533,535,536,538],{},[111,534,286],{}," 通过 ",[111,537,460],{}," 端口写出目标",[72,540,541,544],{},[90,542,543],{},"Blackboard 条目",[90,545,546,547,549],{},"使用键 ",[111,548,464],{}," 保存目标值",[72,551,552,558],{},[90,553,554,555],{},"输入端口 ",[111,556,557],{},"InputPort",[90,559,560,562,563,565],{},[111,561,155],{}," 通过自己的 ",[111,564,460],{}," 端口读取目标",[20,567,568],{},"连接过程是：",[134,570,573],{"className":571,"code":572,"language":139,"meta":140},[137],"SelectGoal 的输出端口 goal\n    ↓ setOutput()\nBlackboard[\"mission_goal\"] = \"loading_area\"\n    ↓ getInput()\nPrintGoal 的输入端口 goal\n",[111,574,572],{"__ignoreMap":140},[20,576,577,578,580,581,584],{},"两个端口恰好都叫 ",[111,579,460],{},"，但真正把它们连接起来的是 XML 中相同的 ",[111,582,583],{},"{mission_goal}","。\n生产者和消费者的端口名称可以不同，只要它们映射到同一个条目。",[366,586,588],{"id":587},"固定值与-blackboard-引用","固定值与 Blackboard 引用",[20,590,591],{},"下面两种输入配置含义不同：",[134,593,595],{"className":192,"code":594,"language":194,"meta":140,"style":140},"\u003CPrintGoal goal=\"loading_area\"\u002F>\n\u003CPrintGoal goal=\"{mission_goal}\"\u002F>\n",[111,596,597,612],{"__ignoreMap":140},[198,598,599,601,603,605,607,610],{"class":200,"line":201},[198,600,205],{"class":204},[198,602,155],{"class":208},[198,604,296],{"class":212},[198,606,216],{"class":204},[198,608,609],{"class":219},"\"loading_area\"",[198,611,278],{"class":204},[198,613,614,616,618,620,622,624],{"class":200,"line":226},[198,615,205],{"class":204},[198,617,155],{"class":208},[198,619,296],{"class":212},[198,621,216],{"class":204},[198,623,301],{"class":219},[198,625,278],{"class":204},[20,627,628,629,631],{},"第一种直接提供固定字符串；\n第二种读取 Blackboard 中键为 ",[111,630,464],{}," 的值。",[20,633,634],{},"花括号表达条目引用，不是 C++ 指针，也不会自动创建有效的业务数据。",[20,636,637],{},"读取前必须有人写入该值，例如前面的输出节点，或者由外部程序初始化 Blackboard。引用了一个从未写入的条目，输入读取就会失败。",[366,639,641],{"id":640},"接口由-c-声明连接由-xml-配置","接口由 C++ 声明，连接由 XML 配置",[20,643,644,645,648],{},"节点用静态方法 ",[111,646,647],{},"providedPorts()"," 声明端口的名称、方向和 C++ 类型。\nXML 则给端口配置固定值，或把它映射到 Blackboard。",[20,650,651,652,655,656,659],{},"输入使用 ",[111,653,654],{},"getInput\u003CT>()","，输出使用 ",[111,657,658],{},"setOutput()","。\n读取可能因为缺少数据、类型或转换问题而失败，节点需要检查结果；本篇选择在配置错误时抛出异常，由主程序报告。",[20,661,662],{},"应在执行回调中读取输入，让后续执行能取得当前值。不要只在构造函数中读取一次，再假定这个值永远不变。",[20,664,665],{},"Blackboard 可以保存多种 C++ 类型，并不限于字符串；从 XML 文本转换自定义类型、双向端口以及子树的数据映射，会在后面的数据传递实践中学习。",[56,667],{},[12,669,671],{"id":670},"认识-xml-中的几种补充写法","认识 XML 中的几种补充写法",[366,673,674],{"id":674},"简写与显式写法",[20,676,677],{},"下面两种写法指向同一种动作节点：",[134,679,681],{"className":192,"code":680,"language":194,"meta":140,"style":140},"\u003CPrintGoal name=\"print_goal\" goal=\"loading_area\"\u002F>\n\u003CAction ID=\"PrintGoal\" name=\"print_goal\" goal=\"loading_area\"\u002F>\n",[111,682,683,703],{"__ignoreMap":140},[198,684,685,687,689,691,693,695,697,699,701],{"class":200,"line":201},[198,686,205],{"class":204},[198,688,155],{"class":208},[198,690,252],{"class":212},[198,692,216],{"class":204},[198,694,317],{"class":219},[198,696,296],{"class":212},[198,698,216],{"class":204},[198,700,609],{"class":219},[198,702,278],{"class":204},[198,704,705,707,710,712,714,717,719,721,723,725,727,729],{"class":200,"line":226},[198,706,205],{"class":204},[198,708,709],{"class":208},"Action",[198,711,235],{"class":212},[198,713,216],{"class":204},[198,715,716],{"class":219},"\"PrintGoal\"",[198,718,252],{"class":212},[198,720,216],{"class":204},[198,722,317],{"class":219},[198,724,296],{"class":212},[198,726,216],{"class":204},[198,728,609],{"class":219},[198,730,278],{"class":204},[20,732,733,734,736,737,739],{},"前者直接把注册标识写作标签名；\n后者用 ",[111,735,709],{}," 指明分类，再用 ",[111,738,471],{}," 指定注册标识。\n本教程以简写为主。",[20,741,742,743,746],{},"XML 中还可能出现 ",[111,744,745],{},"\u003CTreeNodeModel>","，为 Groot 等工具提供自定义节点的分类和端口描述。\n它是模型信息，不是执行分支，也不会替代 C++ 节点注册。\n运行本篇示例时不需要额外填写它。",[366,748,750],{"id":749},"用-subtree-引用另一棵树","用 SubTree 引用另一棵树",[20,752,753],{},"同一个 XML 中可以定义并复用一段行为：",[134,755,757],{"className":192,"code":756,"language":194,"meta":140,"style":140},"\u003Croot main_tree_to_execute=\"MainTree\">\n    \u003CBehaviorTree ID=\"MainTree\">\n        \u003CSequence>\n            \u003CCheckBattery\u002F>\n            \u003CSubTree ID=\"ReportTask\"\u002F>\n        \u003C\u002FSequence>\n    \u003C\u002FBehaviorTree>\n\n    \u003CBehaviorTree ID=\"ReportTask\">\n        \u003CPrintGoal goal=\"loading_area\"\u002F>\n    \u003C\u002FBehaviorTree>\n\u003C\u002Froot>\n",[111,758,759,773,787,795,803,819,827,835,841,855,870,879],{"__ignoreMap":140},[198,760,761,763,765,767,769,771],{"class":200,"line":201},[198,762,205],{"class":204},[198,764,209],{"class":208},[198,766,213],{"class":212},[198,768,216],{"class":204},[198,770,220],{"class":219},[198,772,223],{"class":204},[198,774,775,777,779,781,783,785],{"class":200,"line":226},[198,776,229],{"class":204},[198,778,232],{"class":208},[198,780,235],{"class":212},[198,782,216],{"class":204},[198,784,220],{"class":219},[198,786,223],{"class":204},[198,788,789,791,793],{"class":200,"line":244},[198,790,247],{"class":204},[198,792,113],{"class":208},[198,794,223],{"class":204},[198,796,797,799,801],{"class":200,"line":262},[198,798,265],{"class":204},[198,800,268],{"class":208},[198,802,278],{"class":204},[198,804,805,807,810,812,814,817],{"class":200,"line":281},[198,806,265],{"class":204},[198,808,809],{"class":208},"SubTree",[198,811,235],{"class":212},[198,813,216],{"class":204},[198,815,816],{"class":219},"\"ReportTask\"",[198,818,278],{"class":204},[198,820,821,823,825],{"class":200,"line":306},[198,822,331],{"class":204},[198,824,113],{"class":208},[198,826,223],{"class":204},[198,828,829,831,833],{"class":200,"line":328},[198,830,341],{"class":204},[198,832,232],{"class":208},[198,834,223],{"class":204},[198,836,837],{"class":200,"line":338},[198,838,840],{"emptyLinePlaceholder":839},true,"\n",[198,842,843,845,847,849,851,853],{"class":200,"line":348},[198,844,229],{"class":204},[198,846,232],{"class":208},[198,848,235],{"class":212},[198,850,216],{"class":204},[198,852,816],{"class":219},[198,854,223],{"class":204},[198,856,858,860,862,864,866,868],{"class":200,"line":857},10,[198,859,247],{"class":204},[198,861,155],{"class":208},[198,863,296],{"class":212},[198,865,216],{"class":204},[198,867,609],{"class":219},[198,869,278],{"class":204},[198,871,873,875,877],{"class":200,"line":872},11,[198,874,341],{"class":204},[198,876,232],{"class":208},[198,878,223],{"class":204},[198,880,882,884,886],{"class":200,"line":881},12,[198,883,351],{"class":204},[198,885,209],{"class":208},[198,887,223],{"class":204},[20,889,890,891,894,895,898],{},"这里先检查电量，再执行 ",[111,892,893],{},"ReportTask","。\n",[111,896,897],{},"\u003CSubTree ID=\"ReportTask\"\u002F>"," 引用的是树定义，不是普通 C++ 动作类；工厂在创建树时展开并建立相应结构。",[20,900,901],{},"所以这个 XML 标签本身虽然没有嵌套内容，运行时仍连接到所引用的子树。子树如何传数据，需要明确考虑其 Blackboard 范围，后面再讲。",[366,903,905],{"id":904},"用-include-拆分文件","用 include 拆分文件",[20,907,908,909,911],{},"也可以在 ",[111,910,390],{}," 中写：",[134,913,915],{"className":192,"code":914,"language":194,"meta":140,"style":140},"\u003Cinclude path=\"report_task.xml\"\u002F>\n",[111,916,917],{"__ignoreMap":140},[198,918,919,921,924,927,929,932],{"class":200,"line":201},[198,920,205],{"class":204},[198,922,923],{"class":208},"include",[198,925,926],{"class":212}," path",[198,928,216],{"class":204},[198,930,931],{"class":219},"\"report_task.xml\"",[198,933,278],{"class":204},[20,935,936,937,939],{},"它把其他 XML 中的树定义引入当前文件，再通过 ",[111,938,809],{}," 使用。\n相对路径以包含它的 XML 文件位置为基准；引入定义不会自动执行一遍那棵树。",[20,941,942,943,946,947,950],{},"另一种方式是在 C++ 中用 ",[111,944,945],{},"registerBehaviorTreeFromFile()"," 加载各个文件的树定义，再用 ",[111,948,949],{},"createTree(\"MainTree\")"," 创建选定的树。\n当前先掌握单文件示例，多文件组织留到任务组合时实践。",[56,952],{},[12,954,955],{"id":955},"入门总结",[20,957,958],{},"读完以上概念部分，加上前面的入门、基本概念和控制规则，主线已经覆盖：",[66,960,961,971],{},[69,962,963],{},[72,964,965,968],{},[75,966,967],{},"官方部分",[75,969,970],{},"已建立的认识",[85,972,973,983,993],{},[72,974,975,980],{},[90,976,977],{},[111,978,979],{},"Introduction to BTs",[90,981,982],{},"树结构、tick、状态、节点分类和基本执行规则",[72,984,985,990],{},[90,986,987],{},[111,988,989],{},"Main Concepts",[90,991,992],{},"可复用节点、回调、运行时创建树，以及 Ports\u002FBlackboard",[72,994,995,999],{},[90,996,997],{},[111,998,35],{},[90,1000,1001],{},"树与节点标识、端口映射、简写与显式写法、子树及文件引用",[20,1003,1004,1008],{},[1005,1006,1007],"strong",{},"Learn the Basic Concepts 到此结束。"," 从下面的完整工程开始，进入基础编程实践。\n后面通过例子深化端口、异步和子树，不需要现在把所有接口一次记住。",[56,1010],{},[12,1012,1014],{"id":1013},"从最小-demo-开始搭建工程","从最小 demo 开始搭建工程",[20,1016,1017],{},"先把“注册一个动作 → 读取 XML → tick 执行”跑通，再逐步加功能。\n本篇只使用一个工程目录，按下面的顺序修改同一组文件：",[66,1019,1020,1033],{},[69,1021,1022],{},[72,1023,1024,1027,1030],{},[75,1025,1026],{},"实践内容",[75,1028,1029],{},"本次新增什么",[75,1031,1032],{},"能观察到什么",[85,1034,1035,1046,1059,1070],{},[72,1036,1037,1040,1043],{},[90,1038,1039],{},"最小 demo",[90,1041,1042],{},"一个打印信息的动作",[90,1044,1045],{},"C++ 动作被 XML 调用",[72,1047,1048,1051,1056],{},[90,1049,1050],{},"电量检查",[90,1052,1053,1054],{},"一个条件和 ",[111,1055,113],{},[90,1057,1058],{},"电量不足时，后面的动作被跳过",[72,1060,1061,1064,1067],{},[90,1062,1063],{},"固定目标",[90,1065,1066],{},"一个带输入端口的节点类",[90,1068,1069],{},"从 XML 读取目标字符串",[72,1071,1072,1075,1078],{},[90,1073,1074],{},"目标传递",[90,1076,1077],{},"一个输出节点和 Blackboard 映射",[90,1079,1080],{},"前面的节点产生目标，后面的节点读取",[20,1082,1083],{},"这些例子都使用模拟数据，不需要启动 ROS 2 或连接机器人。先运行最小版本，后续每完成一处修改，再编译或运行对应版本。",[366,1085,1087],{"id":1086},"沿用-c-教程中的-cmake-框架","沿用 C++ 教程中的 CMake 框架",[20,1089,1090,1091,1095,1096,1099,1100,1103],{},"本篇基于 ",[24,1092,1094],{"href":1093},"\u002Fwiki\u002F2023-10-05-cplusplus-jiao-xue\u002F2200-cmake-gong-cheng-mu-ban","CMake工程模板"," 建立 BT 练习版，\n沿用 ",[111,1097,1098],{},"CMakePresets.json","、公共编译选项和 ",[111,1101,1102],{},"src"," 目录。相关文件的作用可以对照阅读：",[66,1105,1106,1116],{},[69,1107,1108],{},[72,1109,1110,1113],{},[75,1111,1112],{},"本篇使用的部分",[75,1114,1115],{},"对应 C++ 教程",[85,1117,1118,1129,1140],{},[72,1119,1120,1123],{},[90,1121,1122],{},"Debug \u002F Release 预设、Ninja 和编辑器构建",[90,1124,1125],{},[24,1126,1128],{"href":1127},"\u002Fwiki\u002F2023-10-05-cplusplus-jiao-xue\u002F2200-0100-cmakepresets-yu-gou-jian-an-zhuang","CMakePresets与构建安装",[72,1130,1131,1134],{},[90,1132,1133],{},"顶层入口、C++ 标准和公共选项",[90,1135,1136],{},[24,1137,1139],{"href":1138},"\u002Fwiki\u002F2023-10-05-cplusplus-jiao-xue\u002F2200-0200-ding-ceng-cmake-yu-gong-gong-bian-yi-xuan-xiang","顶层CMake与公共编译选项",[72,1141,1142,1145],{},[90,1143,1144],{},"主程序与依赖的链接",[90,1146,1147],{},[24,1148,1150],{"href":1149},"\u002Fwiki\u002F2023-10-05-cplusplus-jiao-xue\u002F2200-0300-src-yu-lib-mo-kuai-cmake-xiang-jie","src与lib模块CMake详解",[20,1152,1153,1154,1158],{},"先准备 Git、支持 C++20 的编译器、CMake 3.22 或更新版本，以及 Ninja；\n工具安装见 ",[24,1155,1157],{"href":1156},"\u002Fwiki\u002F2023-10-05-cplusplus-jiao-xue\u002F0100-c-kai-fa-huan-jing-da-jian-yu-ce-shi","C++开发环境搭建与测试","。\n下载模板需要访问 GitHub；行为树依赖优先使用已安装的 3.8.7，只有未找到对应版本时才联网下载。",[20,1160,1161,1164],{},[1005,1162,1163],{},"Ubuntu 22.04 可以使用系统软件源提供的 CMake 3.22.1","，当前模板已采用对应的最低版本和预设格式，直接沿用即可。\n本篇使用的配置功能已受该版本支持，无需为这些练习升级 CMake。",[20,1166,1167],{},"在容器中开发时，CMake 和 Ninja 都应准备在容器内，VSCode 的 CMake Tools 也应使用容器内的工具。",[366,1169,1171],{"id":1170},"在-vscode-中打开工程并准备文件","在 VSCode 中打开工程并准备文件",[20,1173,1174,1175,48],{},"先准备一份新的模板副本，练习目录命名为 ",[111,1176,1177],{},"bt_first_tree",[134,1179,1183],{"className":1180,"code":1181,"language":1182,"meta":140,"style":140},"language-bash shiki shiki-themes github-light github-dark","git clone https:\u002F\u002Fgithub.com\u002Ftungchiahui\u002FCMake_Template bt_first_tree\ncd bt_first_tree\ncode .\n","bash",[111,1184,1185,1199,1207],{"__ignoreMap":140},[198,1186,1187,1190,1193,1196],{"class":200,"line":201},[198,1188,1189],{"class":212},"git",[198,1191,1192],{"class":219}," clone",[198,1194,1195],{"class":219}," https:\u002F\u002Fgithub.com\u002Ftungchiahui\u002FCMake_Template",[198,1197,1198],{"class":219}," bt_first_tree\n",[198,1200,1201,1205],{"class":200,"line":226},[198,1202,1204],{"class":1203},"sj4cs","cd",[198,1206,1198],{"class":219},[198,1208,1209,1211],{"class":200,"line":244},[198,1210,111],{"class":212},[198,1212,1213],{"class":219}," .\n",[20,1215,1216],{},[1217,1218],"img",{"alt":140,"src":1219},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792235968712-ba0bed41.webp",[20,1221,1222,1223,42,1226,1228],{},"左侧资源管理器中应直接看到顶层 ",[111,1224,1225],{},"CMakeLists.txt",[111,1227,1098],{},"，说明打开的是工程根目录。",[20,1230,1231],{},[1217,1232],{"alt":140,"src":1233},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792236012853-ecc9e983.webp",[20,1235,1236,1237,42,1240,1243],{},"在扩展面板确认已安装微软的 ",[1005,1238,1239],{},"CMake Tools",[1005,1241,1242],{},"C\u002FC++","。\n本篇通过 CMake Tools 的 Configure、Build、Launch 和 Debug 入口操作工程；断点调试还需要前面 C++ 环境章节准备好的 GDB。",[20,1245,1246,1247,1250,1251,1254,1255,1258,1259,1262],{},"在左侧资源管理器中，右键工程根目录，新建 ",[111,1248,1249],{},"trees"," 文件夹；在其中新建 ",[111,1252,1253],{},"mission.xml","。\n再在已有的 ",[111,1256,1257],{},"cmake"," 文件夹中新建 ",[111,1260,1261],{},"BehaviorTree.cmake","。\n后面直接在编辑器中填写这些文件，先完成配置和代码，再执行 Configure。",[20,1264,1265],{},[1217,1266],{"alt":140,"src":1267},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792236328493-a52ae285.webp",[20,1269,1270],{},"本篇参与构建和运行的文件如下，模板中的其他演示文件没有列出：",[134,1272,1275],{"className":1273,"code":1274,"language":139,"meta":140},[137],"bt_first_tree\u002F\n├── CMakeLists.txt\n├── CMakePresets.json          沿用 linux-debug \u002F linux-release 预设\n├── cmake\u002F\n│   ├── ProjectOptions.cmake   公共编译选项\n│   └── BehaviorTree.cmake     新增：查找 3.8.7，未安装时获取源码\n├── src\u002F\n│   ├── CMakeLists.txt         创建主程序、链接依赖、设置 XML 路径\n│   └── main.cpp              本篇逐步扩展的 C++ 程序\n└── trees\u002F\n    └── mission.xml           本篇逐步扩展的树\n",[111,1276,1274],{"__ignoreMap":140},[20,1278,1279],{},"下面先在编辑器中准备构建配置，再保存最小程序与 XML。\n完成后统一通过 CMake Tools 配置、构建和运行。",[366,1281,1282],{"id":1282},"调整工程名称并沿用模板配置",[20,1284,1285,1286,1288,1289,1292,1293,1296,1297,48],{},"将顶层 ",[111,1287,1225],{}," 里的",[111,1290,1291],{},"project(cmake_template VERSION 1.0.0 LANGUAGES C CXX)","改成",[111,1294,1295],{},"project(bt_first_tree VERSION 1.0.0 LANGUAGES C CXX)","，主要是把项目名改成 ",[111,1298,1177],{},[20,1300,1301],{},"当前模板已经使用下列配置，保留原值即可：",[66,1303,1304,1314],{},[69,1305,1306],{},[72,1307,1308,1311],{},[75,1309,1310],{},"文件与位置",[75,1312,1313],{},"当前配置",[85,1315,1316,1329,1344],{},[72,1317,1318,1324],{},[90,1319,1320,1321,1323],{},"顶层 ",[111,1322,1225],{}," 的最低版本声明",[90,1325,1326],{},[111,1327,1328],{},"cmake_minimum_required(VERSION 3.22)",[72,1330,1331,1339],{},[90,1332,1333,1335,1336],{},[111,1334,1098],{}," 顶层的 ",[111,1337,1338],{},"version",[90,1340,1341],{},[111,1342,1343],{},"3",[72,1345,1346,1354],{},[90,1347,1348,1350,1351],{},[111,1349,1098],{}," 中的 ",[111,1352,1353],{},"cmakeMinimumRequired",[90,1355,1356,30,1359,30,1362],{},[111,1357,1358],{},"major: 3",[111,1360,1361],{},"minor: 22",[111,1363,1364],{},"patch: 0",[20,1366,1367,1368,1370,1371,1373,1374,1376,1377,1382,1383,1386,1387,1390],{},"其中，预设文件的 ",[111,1369,1338],{}," 表示 JSON 格式版本，",[111,1372,1353],{}," 表示工程要求的 CMake 版本。\n当前预设只使用格式 ",[111,1375,1343],{}," 已支持的功能，说明见 ",[24,1378,1381],{"href":1379,"rel":1380},"https:\u002F\u002Fcmake.org\u002Fcmake\u002Fhelp\u002Fv3.22\u002Fmanual\u002Fcmake-presets.7.html#format",[28],"CMake 3.22 预设文档","。\n继续保留原有的 ",[111,1384,1385],{},"linux-debug"," \u002F ",[111,1388,1389],{},"linux-release"," 配置、构建预设和 Ninja 生成器。",[20,1392,1393,1396,1397,30,1400,1403],{},[111,1394,1395],{},"cmake\u002FProjectOptions.cmake"," 已默认使用 C++20，下面两项同样沿用模板原值；",[111,1398,1399],{},"project_options",[111,1401,1402],{},"project_warnings"," 等其他配置继续保留：",[134,1405,1408],{"className":1406,"code":1407,"language":1257,"meta":140,"style":140},"language-cmake shiki shiki-themes github-light github-dark","set(CMAKE_CXX_STANDARD 20)\ntarget_compile_features(project_options INTERFACE cxx_std_20)\n",[111,1409,1410,1419],{"__ignoreMap":140},[198,1411,1412,1416],{"class":200,"line":201},[198,1413,1415],{"class":1414},"szBVR","set",[198,1417,1418],{"class":204},"(CMAKE_CXX_STANDARD 20)\n",[198,1420,1421,1424,1427,1430],{"class":200,"line":226},[198,1422,1423],{"class":1414},"target_compile_features",[198,1425,1426],{"class":204},"(project_options ",[198,1428,1429],{"class":212},"INTERFACE",[198,1431,1432],{"class":204}," cxx_std_20)\n",[20,1434,1435],{},"本篇练习主程序统一使用 C++20。链接现成的行为树库时不重新编译该库；走源码获取分支时，库按自己的构建规则使用 C++14。",[366,1437,1438],{"id":1438},"集中保存行为树依赖配置",[20,1440,1441,1442,1445,1446,1449],{},"将以下完整内容保存到新增的 ",[111,1443,1444],{},"cmake\u002FBehaviorTree.cmake","。这份配置只准备一次，后续几轮不用反复修改。它先查找已安装的 ",[1005,1447,1448],{},"3.8.7","；未找到对应版本时，再获取同一版本的源码：",[134,1451,1453],{"className":1406,"code":1452,"language":1257,"meta":140,"style":140},"include_guard(GLOBAL)\n\n# 优先查找当前构建环境中已安装的目标版本\nfind_package(behaviortree_cpp_v3 3.8.7 EXACT QUIET CONFIG)\n\nif(behaviortree_cpp_v3_FOUND)\n    # ROS 包和独立安装的源码包可能导出不同名称的 CMake target\n    if(TARGET behaviortree_cpp_v3::behaviortree_cpp_v3)\n        set(BTCPP_TARGET behaviortree_cpp_v3::behaviortree_cpp_v3)\n    elseif(TARGET BT::behaviortree_cpp_v3)\n        set(BTCPP_TARGET BT::behaviortree_cpp_v3)\n    else()\n        message(FATAL_ERROR \"BehaviorTree.CPP 3.8.7 was found without a supported target\")\n    endif()\n    message(STATUS \"Using installed BehaviorTree.CPP 3.8.7: ${behaviortree_cpp_v3_DIR}\")\nelse()\n    include(FetchContent)\n\n    # 未安装目标版本时，固定获取 3.8.7 源码\n    set(BUILD_EXAMPLES OFF CACHE BOOL \"\" FORCE)\n    set(BUILD_UNIT_TESTS OFF CACHE BOOL \"\" FORCE)\n    set(BUILD_TOOLS OFF CACHE BOOL \"\" FORCE)\n    set(BUILD_MANUAL_SELECTOR OFF CACHE BOOL \"\" FORCE)\n    set(ENABLE_COROUTINES OFF CACHE BOOL \"\" FORCE)\n\n    FetchContent_Declare(\n        behaviortree_cpp_v3\n        URL https:\u002F\u002Fcodeload.github.com\u002FBehaviorTree\u002FBehaviorTree.CPP\u002Ftar.gz\u002Frefs\u002Ftags\u002F3.8.7\n    )\n    FetchContent_MakeAvailable(behaviortree_cpp_v3)\n\n    # 3.8.7 会创建 sample_nodes，本篇将它排除在默认构建之外\n    set_property(DIRECTORY \"${behaviortree_cpp_v3_SOURCE_DIR}\u002Fsample_nodes\"\n        PROPERTY EXCLUDE_FROM_ALL TRUE)\n\n    set(BTCPP_TARGET behaviortree_cpp_v3)\n    message(STATUS \"Building BehaviorTree.CPP 3.8.7 from source\")\nendif()\n",[111,1454,1455,1460,1464,1470,1478,1482,1490,1495,1509,1517,1529,1536,1544,1559,1567,1581,1589,1598,1603,1609,1633,1651,1669,1687,1705,1710,1716,1722,1728,1734,1740,1745,1751,1763,1777,1782,1790,1802],{"__ignoreMap":140},[198,1456,1457],{"class":200,"line":201},[198,1458,1459],{"class":204},"include_guard(GLOBAL)\n",[198,1461,1462],{"class":200,"line":226},[198,1463,840],{"emptyLinePlaceholder":839},[198,1465,1466],{"class":200,"line":244},[198,1467,1469],{"class":1468},"sJ8bj","# 优先查找当前构建环境中已安装的目标版本\n",[198,1471,1472,1475],{"class":200,"line":262},[198,1473,1474],{"class":1414},"find_package",[198,1476,1477],{"class":204},"(behaviortree_cpp_v3 3.8.7 EXACT QUIET CONFIG)\n",[198,1479,1480],{"class":200,"line":281},[198,1481,840],{"emptyLinePlaceholder":839},[198,1483,1484,1487],{"class":200,"line":306},[198,1485,1486],{"class":1414},"if",[198,1488,1489],{"class":204},"(behaviortree_cpp_v3_FOUND)\n",[198,1491,1492],{"class":200,"line":328},[198,1493,1494],{"class":1468},"    # ROS 包和独立安装的源码包可能导出不同名称的 CMake target\n",[198,1496,1497,1500,1503,1506],{"class":200,"line":338},[198,1498,1499],{"class":1414},"    if",[198,1501,1502],{"class":204},"(",[198,1504,1505],{"class":1414},"TARGET",[198,1507,1508],{"class":204}," behaviortree_cpp_v3::behaviortree_cpp_v3)\n",[198,1510,1511,1514],{"class":200,"line":348},[198,1512,1513],{"class":1414},"        set",[198,1515,1516],{"class":204},"(BTCPP_TARGET behaviortree_cpp_v3::behaviortree_cpp_v3)\n",[198,1518,1519,1522,1524,1526],{"class":200,"line":857},[198,1520,1521],{"class":1414},"    elseif",[198,1523,1502],{"class":204},[198,1525,1505],{"class":1414},[198,1527,1528],{"class":204}," BT::behaviortree_cpp_v3)\n",[198,1530,1531,1533],{"class":200,"line":872},[198,1532,1513],{"class":1414},[198,1534,1535],{"class":204},"(BTCPP_TARGET BT::behaviortree_cpp_v3)\n",[198,1537,1538,1541],{"class":200,"line":881},[198,1539,1540],{"class":1414},"    else",[198,1542,1543],{"class":204},"()\n",[198,1545,1547,1550,1553,1556],{"class":200,"line":1546},13,[198,1548,1549],{"class":1414},"        message",[198,1551,1552],{"class":204},"(FATAL_ERROR ",[198,1554,1555],{"class":219},"\"BehaviorTree.CPP 3.8.7 was found without a supported target\"",[198,1557,1558],{"class":204},")\n",[198,1560,1562,1565],{"class":200,"line":1561},14,[198,1563,1564],{"class":1414},"    endif",[198,1566,1543],{"class":204},[198,1568,1570,1573,1576,1579],{"class":200,"line":1569},15,[198,1571,1572],{"class":1414},"    message",[198,1574,1575],{"class":204},"(STATUS ",[198,1577,1578],{"class":219},"\"Using installed BehaviorTree.CPP 3.8.7: ${behaviortree_cpp_v3_DIR}\"",[198,1580,1558],{"class":204},[198,1582,1584,1587],{"class":200,"line":1583},16,[198,1585,1586],{"class":1414},"else",[198,1588,1543],{"class":204},[198,1590,1592,1595],{"class":200,"line":1591},17,[198,1593,1594],{"class":1414},"    include",[198,1596,1597],{"class":204},"(FetchContent)\n",[198,1599,1601],{"class":200,"line":1600},18,[198,1602,840],{"emptyLinePlaceholder":839},[198,1604,1606],{"class":200,"line":1605},19,[198,1607,1608],{"class":1468},"    # 未安装目标版本时，固定获取 3.8.7 源码\n",[198,1610,1612,1615,1618,1621,1624,1627,1630],{"class":200,"line":1611},20,[198,1613,1614],{"class":1414},"    set",[198,1616,1617],{"class":204},"(BUILD_EXAMPLES ",[198,1619,1620],{"class":1414},"OFF",[198,1622,1623],{"class":1414}," CACHE",[198,1625,1626],{"class":1414}," BOOL",[198,1628,1629],{"class":219}," \"\"",[198,1631,1632],{"class":204}," FORCE)\n",[198,1634,1636,1638,1641,1643,1645,1647,1649],{"class":200,"line":1635},21,[198,1637,1614],{"class":1414},[198,1639,1640],{"class":204},"(BUILD_UNIT_TESTS ",[198,1642,1620],{"class":1414},[198,1644,1623],{"class":1414},[198,1646,1626],{"class":1414},[198,1648,1629],{"class":219},[198,1650,1632],{"class":204},[198,1652,1654,1656,1659,1661,1663,1665,1667],{"class":200,"line":1653},22,[198,1655,1614],{"class":1414},[198,1657,1658],{"class":204},"(BUILD_TOOLS ",[198,1660,1620],{"class":1414},[198,1662,1623],{"class":1414},[198,1664,1626],{"class":1414},[198,1666,1629],{"class":219},[198,1668,1632],{"class":204},[198,1670,1672,1674,1677,1679,1681,1683,1685],{"class":200,"line":1671},23,[198,1673,1614],{"class":1414},[198,1675,1676],{"class":204},"(BUILD_MANUAL_SELECTOR ",[198,1678,1620],{"class":1414},[198,1680,1623],{"class":1414},[198,1682,1626],{"class":1414},[198,1684,1629],{"class":219},[198,1686,1632],{"class":204},[198,1688,1690,1692,1695,1697,1699,1701,1703],{"class":200,"line":1689},24,[198,1691,1614],{"class":1414},[198,1693,1694],{"class":204},"(ENABLE_COROUTINES ",[198,1696,1620],{"class":1414},[198,1698,1623],{"class":1414},[198,1700,1626],{"class":1414},[198,1702,1629],{"class":219},[198,1704,1632],{"class":204},[198,1706,1708],{"class":200,"line":1707},25,[198,1709,840],{"emptyLinePlaceholder":839},[198,1711,1713],{"class":200,"line":1712},26,[198,1714,1715],{"class":204},"    FetchContent_Declare(\n",[198,1717,1719],{"class":200,"line":1718},27,[198,1720,1721],{"class":204},"        behaviortree_cpp_v3\n",[198,1723,1725],{"class":200,"line":1724},28,[198,1726,1727],{"class":204},"        URL https:\u002F\u002Fcodeload.github.com\u002FBehaviorTree\u002FBehaviorTree.CPP\u002Ftar.gz\u002Frefs\u002Ftags\u002F3.8.7\n",[198,1729,1731],{"class":200,"line":1730},29,[198,1732,1733],{"class":204},"    )\n",[198,1735,1737],{"class":200,"line":1736},30,[198,1738,1739],{"class":204},"    FetchContent_MakeAvailable(behaviortree_cpp_v3)\n",[198,1741,1743],{"class":200,"line":1742},31,[198,1744,840],{"emptyLinePlaceholder":839},[198,1746,1748],{"class":200,"line":1747},32,[198,1749,1750],{"class":1468},"    # 3.8.7 会创建 sample_nodes，本篇将它排除在默认构建之外\n",[198,1752,1754,1757,1760],{"class":200,"line":1753},33,[198,1755,1756],{"class":1414},"    set_property",[198,1758,1759],{"class":204},"(DIRECTORY ",[198,1761,1762],{"class":219},"\"${behaviortree_cpp_v3_SOURCE_DIR}\u002Fsample_nodes\"\n",[198,1764,1766,1769,1772,1775],{"class":200,"line":1765},34,[198,1767,1768],{"class":204},"        PROPERTY ",[198,1770,1771],{"class":212},"EXCLUDE_FROM_ALL",[198,1773,1774],{"class":1414}," TRUE",[198,1776,1558],{"class":204},[198,1778,1780],{"class":200,"line":1779},35,[198,1781,840],{"emptyLinePlaceholder":839},[198,1783,1785,1787],{"class":200,"line":1784},36,[198,1786,1614],{"class":1414},[198,1788,1789],{"class":204},"(BTCPP_TARGET behaviortree_cpp_v3)\n",[198,1791,1793,1795,1797,1800],{"class":200,"line":1792},37,[198,1794,1572],{"class":1414},[198,1796,1575],{"class":204},[198,1798,1799],{"class":219},"\"Building BehaviorTree.CPP 3.8.7 from source\"",[198,1801,1558],{"class":204},[198,1803,1805,1808],{"class":200,"line":1804},38,[198,1806,1807],{"class":1414},"endif",[198,1809,1543],{"class":204},[20,1811,1812,1813,1816,1817,48],{},"在已加载 ROS 2、能找到安装包的 Humble 环境中，Configure 输出会包含 ",[111,1814,1815],{},"Using installed BehaviorTree.CPP 3.8.7","，并显示包配置所在路径。此时直接链接容器里的库，不下载或重新编译它。当前构建环境找不到该版本时，则显示 ",[111,1818,1819],{},"Building BehaviorTree.CPP 3.8.7 from source",[20,1821,1822,1823,1826],{},"复用 Humble 安装包时，配置和运行程序都需要 ROS 2 环境。本篇测试在加载 ",[111,1824,1825],{},"\u002Fopt\u002Fros\u002Fhumble\u002Fsetup.bash"," 后完成；VSCode 的 CMake Tools 与运行、调试进程也应继承这份环境。\n只在已打开窗口的集成终端中加载环境，不会自动更新扩展进程。\n未准备 ROS 2 环境的独立工程可以使用同版本源码分支，程序本身不需要启动 ROS 节点。",[20,1828,1829,1832],{},[111,1830,1831],{},"BTCPP_TARGET"," 保存本次应链接的 CMake target，主程序统一使用这个变量。这样两种依赖来源使用同一份 C++ 和 XML，框架也不需要手动切换。",[20,1834,1835,1836,1841,1842,1845,1846,1849,1850,48],{},"源码分支的构建选项可对照官方 ",[24,1837,1840],{"href":1838,"rel":1839},"https:\u002F\u002Fgithub.com\u002FBehaviorTree\u002FBehaviorTree.CPP\u002Fblob\u002F3.8.7\u002FCMakeLists.txt",[28],"3.8.7 CMakeLists.txt","。3.8.7 不再提供 ",[111,1843,1844],{},"BUILD_SAMPLES"," 开关，所以本篇直接将 ",[111,1847,1848],{},"sample_nodes"," 排除在默认构建之外。关闭协程不影响本篇同步节点，也不影响后面使用 ",[111,1851,1852],{},"StatefulActionNode",[20,1854,1855],{},"3.8.7 的源码分支已在 GCC 16 环境中构建运行通过，配置无需额外添加模板兼容选项。",[366,1857,1858],{"id":1858},"让主程序链接行为树库",[20,1860,1861,1862,1865],{},"将 ",[111,1863,1864],{},"src\u002FCMakeLists.txt"," 整个替换为：",[134,1867,1869],{"className":1406,"code":1868,"language":1257,"meta":140,"style":140},"include(\"${PROJECT_SOURCE_DIR}\u002Fcmake\u002FBehaviorTree.cmake\")\n\nadd_executable(${PROJECT_NAME}\n    ${CMAKE_CURRENT_SOURCE_DIR}\u002Fmain.cpp\n)\n\ntarget_link_libraries(${PROJECT_NAME}\n    PRIVATE\n        project_options\n        project_warnings\n        ${BTCPP_TARGET}\n)\n\n# 将源码目录中的 XML 完整路径作为字符串交给程序\ntarget_compile_definitions(${PROJECT_NAME}\n    PRIVATE BT_XML_PATH=\"${PROJECT_SOURCE_DIR}\u002Ftrees\u002Fmission.xml\"\n)\n",[111,1870,1871,1882,1886,1896,1904,1908,1912,1921,1926,1931,1936,1941,1945,1949,1954,1963,1974],{"__ignoreMap":140},[198,1872,1873,1875,1877,1880],{"class":200,"line":201},[198,1874,923],{"class":1414},[198,1876,1502],{"class":204},[198,1878,1879],{"class":219},"\"${PROJECT_SOURCE_DIR}\u002Fcmake\u002FBehaviorTree.cmake\"",[198,1881,1558],{"class":204},[198,1883,1884],{"class":200,"line":226},[198,1885,840],{"emptyLinePlaceholder":839},[198,1887,1888,1891,1893],{"class":200,"line":244},[198,1889,1890],{"class":1414},"add_executable",[198,1892,1502],{"class":204},[198,1894,1895],{"class":1414},"${PROJECT_NAME}\n",[198,1897,1898,1901],{"class":200,"line":262},[198,1899,1900],{"class":1414},"    ${CMAKE_CURRENT_SOURCE_DIR}",[198,1902,1903],{"class":204},"\u002Fmain.cpp\n",[198,1905,1906],{"class":200,"line":281},[198,1907,1558],{"class":204},[198,1909,1910],{"class":200,"line":306},[198,1911,840],{"emptyLinePlaceholder":839},[198,1913,1914,1917,1919],{"class":200,"line":328},[198,1915,1916],{"class":1414},"target_link_libraries",[198,1918,1502],{"class":204},[198,1920,1895],{"class":1414},[198,1922,1923],{"class":200,"line":338},[198,1924,1925],{"class":212},"    PRIVATE\n",[198,1927,1928],{"class":200,"line":348},[198,1929,1930],{"class":204},"        project_options\n",[198,1932,1933],{"class":200,"line":857},[198,1934,1935],{"class":204},"        project_warnings\n",[198,1937,1938],{"class":200,"line":872},[198,1939,1940],{"class":1414},"        ${BTCPP_TARGET}\n",[198,1942,1943],{"class":200,"line":881},[198,1944,1558],{"class":204},[198,1946,1947],{"class":200,"line":1546},[198,1948,840],{"emptyLinePlaceholder":839},[198,1950,1951],{"class":200,"line":1561},[198,1952,1953],{"class":1468},"# 将源码目录中的 XML 完整路径作为字符串交给程序\n",[198,1955,1956,1959,1961],{"class":200,"line":1569},[198,1957,1958],{"class":1414},"target_compile_definitions",[198,1960,1502],{"class":204},[198,1962,1895],{"class":1414},[198,1964,1965,1968,1971],{"class":200,"line":1583},[198,1966,1967],{"class":212},"    PRIVATE",[198,1969,1970],{"class":204}," BT_XML_PATH=",[198,1972,1973],{"class":219},"\"${PROJECT_SOURCE_DIR}\u002Ftrees\u002Fmission.xml\"\n",[198,1975,1976],{"class":200,"line":1591},[198,1977,1558],{"class":204},[20,1979,1980],{},[1217,1981],{"alt":140,"src":1982},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792242363354-1942b6de.webp",[20,1984,1985,1986,30,1989,1992],{},"模板中的 ",[111,1987,1988],{},"lib1",[111,1990,1991],{},"lib2"," 目录可以保留，但这份文件已经不再构建或链接它们；替换主程序后，本篇也不需要 Eigen 示例依赖。",[20,1994,1995,1998,1999,2002],{},[111,1996,1997],{},"BT_XML_PATH"," 是这份配置提供的字符串，自动指向当前工程的 ",[111,2000,2001],{},"trees\u002Fmission.xml","。后面的 C++ 直接使用它，不需要手写本机绝对路径，也不必随终端或编辑器的运行位置调整相对路径。",[366,2004,2006],{"id":2005},"保存最小-c-程序","保存最小 C++ 程序",[20,2008,2009,2010,2013],{},"将以下完整内容保存到 ",[111,2011,2012],{},"src\u002Fmain.cpp","，替换模板原来的演示主程序：",[134,2015,2019],{"className":2016,"code":2017,"language":2018,"meta":140,"style":140},"language-cpp shiki shiki-themes github-light github-dark","#include \"behaviortree_cpp_v3\u002Fbt_factory.h\"\n\n#include \u003Ciostream>\n\n\u002F\u002F 一个立即完成的动作：打印机器人已就绪\nBT::NodeStatus ReportReady(BT::TreeNode&)\n{\n    std::cout \u003C\u003C \"[ReportReady] robot is ready\\n\";\n    return BT::NodeStatus::SUCCESS;\n}\n\nint main()\n{\n    BT::BehaviorTreeFactory factory;\n    factory.registerSimpleAction(\"ReportReady\", ReportReady);\n\n    \u002F\u002F 根据 XML 创建树，再从根节点执行一次\n    auto tree = factory.createTreeFromFile(BT_XML_PATH);\n    const BT::NodeStatus result = tree.tickRoot();\n    std::cout \u003C\u003C \"Tree result: \" \u003C\u003C BT::toStr(result) \u003C\u003C '\\n';\n\n    return result == BT::NodeStatus::SUCCESS ? 0 : 1;\n}\n","cpp",[111,2020,2021,2029,2033,2040,2044,2049,2077,2082,2105,2120,2125,2129,2139,2143,2151,2167,2171,2176,2195,2216,2252,2256,2289],{"__ignoreMap":140},[198,2022,2023,2026],{"class":200,"line":201},[198,2024,2025],{"class":1414},"#include",[198,2027,2028],{"class":219}," \"behaviortree_cpp_v3\u002Fbt_factory.h\"\n",[198,2030,2031],{"class":200,"line":226},[198,2032,840],{"emptyLinePlaceholder":839},[198,2034,2035,2037],{"class":200,"line":244},[198,2036,2025],{"class":1414},[198,2038,2039],{"class":219}," \u003Ciostream>\n",[198,2041,2042],{"class":200,"line":262},[198,2043,840],{"emptyLinePlaceholder":839},[198,2045,2046],{"class":200,"line":281},[198,2047,2048],{"class":1468},"\u002F\u002F 一个立即完成的动作：打印机器人已就绪\n",[198,2050,2051,2054,2057,2060,2063,2065,2067,2069,2072,2075],{"class":200,"line":306},[198,2052,2053],{"class":212},"BT",[198,2055,2056],{"class":204},"::",[198,2058,2059],{"class":212},"NodeStatus",[198,2061,2062],{"class":212}," ReportReady",[198,2064,1502],{"class":204},[198,2066,2053],{"class":212},[198,2068,2056],{"class":204},[198,2070,2071],{"class":212},"TreeNode",[198,2073,2074],{"class":1414},"&",[198,2076,1558],{"class":204},[198,2078,2079],{"class":200,"line":328},[198,2080,2081],{"class":204},"{\n",[198,2083,2084,2087,2090,2093,2096,2099,2102],{"class":200,"line":338},[198,2085,2086],{"class":212},"    std",[198,2088,2089],{"class":204},"::cout ",[198,2091,2092],{"class":1414},"\u003C\u003C",[198,2094,2095],{"class":219}," \"[ReportReady] robot is ready",[198,2097,2098],{"class":1203},"\\n",[198,2100,2101],{"class":219},"\"",[198,2103,2104],{"class":204},";\n",[198,2106,2107,2110,2113,2115,2117],{"class":200,"line":348},[198,2108,2109],{"class":1414},"    return",[198,2111,2112],{"class":212}," BT",[198,2114,2056],{"class":204},[198,2116,2059],{"class":212},[198,2118,2119],{"class":204},"::SUCCESS;\n",[198,2121,2122],{"class":200,"line":857},[198,2123,2124],{"class":204},"}\n",[198,2126,2127],{"class":200,"line":872},[198,2128,840],{"emptyLinePlaceholder":839},[198,2130,2131,2134,2137],{"class":200,"line":881},[198,2132,2133],{"class":1414},"int",[198,2135,2136],{"class":212}," main",[198,2138,1543],{"class":204},[198,2140,2141],{"class":200,"line":1546},[198,2142,2081],{"class":204},[198,2144,2145,2148],{"class":200,"line":1561},[198,2146,2147],{"class":212},"    BT",[198,2149,2150],{"class":204},"::BehaviorTreeFactory factory;\n",[198,2152,2153,2156,2159,2161,2164],{"class":200,"line":1569},[198,2154,2155],{"class":204},"    factory.",[198,2157,2158],{"class":212},"registerSimpleAction",[198,2160,1502],{"class":204},[198,2162,2163],{"class":219},"\"ReportReady\"",[198,2165,2166],{"class":204},", ReportReady);\n",[198,2168,2169],{"class":200,"line":1583},[198,2170,840],{"emptyLinePlaceholder":839},[198,2172,2173],{"class":200,"line":1591},[198,2174,2175],{"class":1468},"    \u002F\u002F 根据 XML 创建树，再从根节点执行一次\n",[198,2177,2178,2181,2184,2186,2189,2192],{"class":200,"line":1600},[198,2179,2180],{"class":1414},"    auto",[198,2182,2183],{"class":204}," tree ",[198,2185,216],{"class":1414},[198,2187,2188],{"class":204}," factory.",[198,2190,2191],{"class":212},"createTreeFromFile",[198,2193,2194],{"class":204},"(BT_XML_PATH);\n",[198,2196,2197,2200,2202,2205,2207,2210,2213],{"class":200,"line":1605},[198,2198,2199],{"class":1414},"    const",[198,2201,2112],{"class":212},[198,2203,2204],{"class":204},"::NodeStatus result ",[198,2206,216],{"class":1414},[198,2208,2209],{"class":204}," tree.",[198,2211,2212],{"class":212},"tickRoot",[198,2214,2215],{"class":204},"();\n",[198,2217,2218,2220,2222,2224,2227,2230,2232,2234,2237,2240,2242,2245,2247,2250],{"class":200,"line":1611},[198,2219,2086],{"class":212},[198,2221,2089],{"class":204},[198,2223,2092],{"class":1414},[198,2225,2226],{"class":219}," \"Tree result: \"",[198,2228,2229],{"class":1414}," \u003C\u003C",[198,2231,2112],{"class":212},[198,2233,2056],{"class":204},[198,2235,2236],{"class":212},"toStr",[198,2238,2239],{"class":204},"(result) ",[198,2241,2092],{"class":1414},[198,2243,2244],{"class":219}," '",[198,2246,2098],{"class":1203},[198,2248,2249],{"class":219},"'",[198,2251,2104],{"class":204},[198,2253,2254],{"class":200,"line":1635},[198,2255,840],{"emptyLinePlaceholder":839},[198,2257,2258,2260,2263,2266,2268,2270,2272,2275,2278,2281,2284,2287],{"class":200,"line":1653},[198,2259,2109],{"class":1414},[198,2261,2262],{"class":204}," result ",[198,2264,2265],{"class":1414},"==",[198,2267,2112],{"class":212},[198,2269,2056],{"class":204},[198,2271,2059],{"class":212},[198,2273,2274],{"class":204},"::SUCCESS ",[198,2276,2277],{"class":1414},"?",[198,2279,2280],{"class":1203}," 0",[198,2282,2283],{"class":1414}," :",[198,2285,2286],{"class":1203}," 1",[198,2288,2104],{"class":204},[198,2290,2291],{"class":200,"line":1671},[198,2292,2124],{"class":204},[20,2294,2295,2298],{},[111,2296,2297],{},"ReportReady()"," 是普通 C++ 函数。",[20,2300,2301,2302,2304,2305,2308],{},"通过 ",[111,2303,177],{}," 注册后，框架可以将它作为同步动作调用。\n参数 ",[111,2306,2307],{},"BT::TreeNode&"," 由框架传入，本例暂时不使用，所以没有为它命名。",[20,2310,2311],{},"这里先保留最小正常路径；等到读取端口输入时，再加入输入检查和异常报告。",[366,2313,2315],{"id":2314},"注册简单动作与创建树的-api","注册简单动作与创建树的 API",[20,2317,2318,2321],{},[111,2319,2320],{},"BT::BehaviorTreeFactory factory;"," 创建一个节点工厂对象，本例不需要给构造函数传参。\n后面的注册操作告诉工厂怎样创建自定义节点；加载 XML 时，工厂再按注册名创建节点实例。",[20,2323,2324,2325,2330],{},"下面的函数声明用于理解接口，不用再次粘贴到工程中。\n原型按官方 ",[24,2326,2329],{"href":2327,"rel":2328},"https:\u002F\u002Fgithub.com\u002FBehaviorTree\u002FBehaviorTree.CPP\u002Fblob\u002F3.8.7\u002Finclude\u002Fbehaviortree_cpp_v3\u002Fbt_factory.h",[28],"3.8.7 bt_factory.h"," 核对。",[2332,2333,2335],"h4",{"id":2334},"registersimpleaction",[111,2336,177],{},[20,2338,2339],{},"函数原型：",[134,2341,2343],{"className":2016,"code":2342,"language":2018,"meta":140,"style":140},"void registerSimpleAction(\n    const std::string& ID,\n    const BT::SimpleActionNode::TickFunctor& tick_functor,\n    BT::PortsList ports = {});\n",[111,2344,2345,2356,2376,2399],{"__ignoreMap":140},[198,2346,2347,2350,2353],{"class":200,"line":201},[198,2348,2349],{"class":1414},"void",[198,2351,2352],{"class":212}," registerSimpleAction",[198,2354,2355],{"class":204},"(\n",[198,2357,2358,2360,2363,2365,2368,2370,2373],{"class":200,"line":226},[198,2359,2199],{"class":1414},[198,2361,2362],{"class":212}," std",[198,2364,2056],{"class":204},[198,2366,2367],{"class":212},"string",[198,2369,2074],{"class":1414},[198,2371,235],{"class":2372},"s4XuR",[198,2374,2375],{"class":204},",\n",[198,2377,2378,2380,2382,2384,2387,2389,2392,2394,2397],{"class":200,"line":244},[198,2379,2199],{"class":1414},[198,2381,2112],{"class":212},[198,2383,2056],{"class":204},[198,2385,2386],{"class":212},"SimpleActionNode",[198,2388,2056],{"class":204},[198,2390,2391],{"class":212},"TickFunctor",[198,2393,2074],{"class":1414},[198,2395,2396],{"class":2372}," tick_functor",[198,2398,2375],{"class":204},[198,2400,2401,2403,2405,2408,2411,2414],{"class":200,"line":262},[198,2402,2147],{"class":212},[198,2404,2056],{"class":204},[198,2406,2407],{"class":212},"PortsList",[198,2409,2410],{"class":2372}," ports",[198,2412,2413],{"class":1414}," =",[198,2415,2416],{"class":204}," {});\n",[20,2418,2419],{},"作用：把一个回调函数注册成简单同步动作，之后可以在 XML 中使用对应的节点名。",[66,2421,2422,2435],{},[69,2423,2424],{},[72,2425,2426,2429,2432],{},[75,2427,2428],{},"参数",[75,2430,2431],{},"类型与含义",[75,2433,2434],{},"本例传入",[85,2436,2437,2455,2475],{},[72,2438,2439,2444,2447],{},[90,2440,2441,2442],{},"第一个 ",[111,2443,471],{},[90,2445,2446],{},"字符串，注册到工厂的节点类型名称，对应 XML 标签",[90,2448,2449,2451,2452],{},[111,2450,2163],{},"，对应 ",[111,2453,2454],{},"\u003CReportReady\u002F>",[72,2456,2457,2463,2469],{},[90,2458,2459,2460],{},"第二个 ",[111,2461,2462],{},"tick_functor",[90,2464,2465,2466],{},"执行该节点时调用的函数，签名为 ",[111,2467,2468],{},"BT::NodeStatus(BT::TreeNode&)",[90,2470,2471,2472],{},"普通函数 ",[111,2473,2474],{},"ReportReady",[72,2476,2477,2483,2490],{},[90,2478,2479,2480],{},"第三个 ",[111,2481,2482],{},"ports",[90,2484,2485,2486,2489],{},"可选的端口声明，类型为 ",[111,2487,2488],{},"BT::PortsList","，默认空列表",[90,2491,2492],{},"省略，因为这个最小动作不读写端口",[20,2494,2495,2496,2501,2502,2504],{},"第二个参数传的是 ",[1005,2497,2498,2500],{},[111,2499,2474],{}," 函数本身","，没有调用括号。注册时先保存回调；树执行到这个动作时，框架才调用它，并传入代表当前节点实例的 ",[111,2503,2307],{},"。本例不使用这个引用，所以函数定义中只保留类型，没有写形参名。",[20,2506,2507,2508,2510,2511,2513,2514,2517,2518,2521,2522,2525,2526,2529],{},"返回值：",[111,2509,2349],{},"，注册函数本身不返回任务状态；",[111,2512,2297],{}," 回调返回的 ",[111,2515,2516],{},"BT::NodeStatus"," 才是节点执行结果。这个简单动作属于同步动作，回调应返回 ",[111,2519,2520],{},"SUCCESS"," 或 ",[111,2523,2524],{},"FAILURE","，不能返回 ",[111,2527,2528],{},"RUNNING","。注册名重复等注册错误会抛出异常。",[2332,2531,2533],{"id":2532},"createtreefromfile",[111,2534,2535],{},"createTreeFromFile()",[20,2537,2339],{},[134,2539,2541],{"className":2016,"code":2540,"language":2018,"meta":140,"style":140},"BT::Tree createTreeFromFile(\n    const std::string& file_path,\n    BT::Blackboard::Ptr blackboard = BT::Blackboard::create());\n",[111,2542,2543,2557,2574],{"__ignoreMap":140},[198,2544,2545,2547,2549,2552,2555],{"class":200,"line":201},[198,2546,2053],{"class":212},[198,2548,2056],{"class":204},[198,2550,2551],{"class":212},"Tree",[198,2553,2554],{"class":212}," createTreeFromFile",[198,2556,2355],{"class":204},[198,2558,2559,2561,2563,2565,2567,2569,2572],{"class":200,"line":226},[198,2560,2199],{"class":1414},[198,2562,2362],{"class":212},[198,2564,2056],{"class":204},[198,2566,2367],{"class":212},[198,2568,2074],{"class":1414},[198,2570,2571],{"class":2372}," file_path",[198,2573,2375],{"class":204},[198,2575,2576,2578,2580,2583,2585,2588,2591,2593,2595,2597,2599,2601,2604],{"class":200,"line":244},[198,2577,2147],{"class":212},[198,2579,2056],{"class":204},[198,2581,2582],{"class":212},"Blackboard",[198,2584,2056],{"class":204},[198,2586,2587],{"class":212},"Ptr",[198,2589,2590],{"class":2372}," blackboard",[198,2592,2413],{"class":1414},[198,2594,2112],{"class":212},[198,2596,2056],{"class":204},[198,2598,2582],{"class":212},[198,2600,2056],{"class":204},[198,2602,2603],{"class":212},"create",[198,2605,2606],{"class":204},"());\n",[20,2608,2609],{},"作用：读取 XML 文件，并按照工厂已有的节点类型和 XML 结构创建一棵树。",[66,2611,2612,2623],{},[69,2613,2614],{},[72,2615,2616,2618,2620],{},[75,2617,2428],{},[75,2619,381],{},[75,2621,2622],{},"本例用法",[85,2624,2625,2640],{},[72,2626,2627,2632,2635],{},[90,2628,2441,2629],{},[111,2630,2631],{},"file_path",[90,2633,2634],{},"入口 XML 的路径字符串",[90,2636,2637,2639],{},[111,2638,1997],{},"，由 CMake 提供的完整文件路径",[72,2641,2642,2647,2650],{},[90,2643,2459,2644],{},[111,2645,2646],{},"blackboard",[90,2648,2649],{},"可选，指定根树使用的 Blackboard",[90,2651,2652],{},"本例省略，由默认参数创建；下一篇会从外部传入",[20,2654,2507,2655,2658,2659,2662,2663,2666],{},[111,2656,2657],{},"BT::Tree","，本例用 ",[111,2660,2661],{},"auto tree"," 接收。创建时会构造节点并检查 XML，但还没有开始执行动作的 ",[111,2664,2665],{},"tick()","。文件不存在、XML 错误或使用未注册节点等情况会抛出异常。",[2332,2668,2670,2672,2673],{"id":2669},"tickroot-与-bttostr",[111,2671,129],{}," 与 ",[111,2674,2675],{},"BT::toStr()",[20,2677,2678,2681,2682,2684,2685,2687],{},[111,2679,2680],{},"tree.tickRoot()"," 不接收参数，返回 ",[111,2683,2516],{},"。调用一次，就从根节点驱动一次；一次根 tick 可能继续执行多个立即完成的节点，也可能在某个节点返回 ",[111,2686,2528],{}," 时先返回主程序。",[20,2689,2690,2693,2694,2697,2698,2700],{},[111,2691,2692],{},"BT::toStr(result)"," 的参数是需要显示的节点状态，返回对应的 ",[111,2695,2696],{},"std::string","，例如 ",[111,2699,2520],{},"。它只负责把状态变成文本；真正决定程序退出码的仍是后面的状态比较。",[366,2702,2704],{"id":2703},"保存只有一个动作的-xml","保存只有一个动作的 XML",[20,2706,2707,2708,2710],{},"将以下内容保存到 ",[111,2709,2001],{},"：",[134,2712,2714],{"className":192,"code":2713,"language":194,"meta":140,"style":140},"\u003Croot main_tree_to_execute=\"MainTree\">\n    \u003CBehaviorTree ID=\"MainTree\">\n        \u003CReportReady\u002F>\n    \u003C\u002FBehaviorTree>\n\u003C\u002Froot>\n",[111,2715,2716,2730,2744,2752,2760],{"__ignoreMap":140},[198,2717,2718,2720,2722,2724,2726,2728],{"class":200,"line":201},[198,2719,205],{"class":204},[198,2721,209],{"class":208},[198,2723,213],{"class":212},[198,2725,216],{"class":204},[198,2727,220],{"class":219},[198,2729,223],{"class":204},[198,2731,2732,2734,2736,2738,2740,2742],{"class":200,"line":226},[198,2733,229],{"class":204},[198,2735,232],{"class":208},[198,2737,235],{"class":212},[198,2739,216],{"class":204},[198,2741,220],{"class":219},[198,2743,223],{"class":204},[198,2745,2746,2748,2750],{"class":200,"line":244},[198,2747,247],{"class":204},[198,2749,2474],{"class":208},[198,2751,278],{"class":204},[198,2753,2754,2756,2758],{"class":200,"line":262},[198,2755,341],{"class":204},[198,2757,232],{"class":208},[198,2759,223],{"class":204},[198,2761,2762,2764,2766],{"class":200,"line":281},[198,2763,351],{"class":204},[198,2765,209],{"class":208},[198,2767,223],{"class":204},[20,2769,2770,2771,2773,2774,48],{},"这棵树只有一个动作，",[111,2772,2474],{}," 就是实际根节点，不需要为了凑结构而添加 ",[111,2775,113],{},[366,2777,2779],{"id":2778},"在-cmake-tools-中配置工程","在 CMake Tools 中配置工程",[20,2781,2782,2783,2786,2787,2451,2790,1350,2792,48],{},"保存前面填写的所有文件，再打开左侧 ",[1005,2784,2785],{},"CMake"," 面板。选择 Configure 预设 ",[1005,2788,2789],{},"Linux Debug",[111,2791,1098],{},[111,2793,1385],{},[20,2795,2796,2797,2800,2801,48],{},"也可以在命令面板中执行 ",[111,2798,2799],{},"CMake: Select Configure Preset","，选择同一个预设。这里使用模板已有的预设，不需要重新创建 ",[111,2802,1098],{},[20,2804,2805,2806,2809],{},"点击 CMake Tools 的 ",[1005,2807,2808],{},"Configure","。\n这一步读取工程配置并准备 BehaviorTree.CPP 依赖；已安装 3.8.7 时直接使用，否则第一次会下载源码。等待输出中出现配置和生成成功的信息。",[20,2811,2812],{},[1217,2813],{"alt":140,"src":2814},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792242509690-1cfa0965.webp",[20,2816,2817],{},[1217,2818],{"alt":140,"src":2819},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792242570652-9e98ccd0.webp",[366,2821,2822],{"id":2822},"构建并选择运行目标",[20,2824,2825,2826,2828,2829,2832],{},"在 CMake 面板中确认 Build 预设为 ",[111,2827,1385],{},"，点击 ",[1005,2830,2831],{},"Build","。等待构建完成，确认没有编译或链接错误。",[20,2834,2835],{},[1217,2836],{"alt":140,"src":2837},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792242539632-bd3cfef9.webp",[20,2839,2840,2841,2844,2845,2847],{},"在 CMake Tools 的 ",[1005,2842,2843],{},"Launch"," 项中选择 ",[111,2846,1177],{},"，然后点击旁边的运行按钮。\n也可以使用 CMake Tools 提供的状态栏 Launch 入口。\n这里启动的是整个 CMake 工程生成的可执行文件。",[20,2849,2850],{},[1217,2851],{"alt":140,"src":2852},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792282254881-173cb5a9.webp",[20,2854,2855,2856,2858,2859,48],{},"程序输出会显示在 VSCode 的集成终端中。配置、构建和运行的详细界面说明可对照 ",[24,2857,1128],{"href":1127},"，运行入口也可参考 ",[24,2860,2863],{"href":2861,"rel":2862},"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fvscode-cmake-tools\u002Fblob\u002Fmain\u002Fdocs\u002Fdebug-launch.md",[28],"CMake Tools 官方说明",[20,2865,2866],{},"预期输出：",[134,2868,2871],{"className":2869,"code":2870,"language":139,"meta":140},[137],"[ReportReady] robot is ready\nTree result: SUCCESS\n",[111,2872,2870],{"__ignoreMap":140},[20,2874,2875],{},[1217,2876],{"alt":140,"src":2877},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792242633602-859f0db6.webp",[20,2879,2880],{},"执行过程是：",[134,2882,2885],{"className":2883,"code":2884,"language":139,"meta":140},[137],"注册 ReportReady 对应的函数\n    ↓\n工厂读取 XML，创建 ReportReady 节点\n    ↓\ntree.tickRoot()\n    ↓\n调用 ReportReady()，打印信息并返回 SUCCESS\n",[111,2886,2884],{"__ignoreMap":140},[20,2888,2889,2892],{},[1005,2890,2891],{},"先看到这两行输出，再往下加功能。"," 到这里已经完成了一个可以独立运行的行为树程序。",[366,2894,2895],{"id":2895},"在动作回调中设置断点",[20,2897,2898,2899,2901,2902,2905,2906,42,2908,2910,2911,48],{},"想观察函数什么时候被调用，可以在 ",[111,2900,2297],{}," 内的 ",[111,2903,2904],{},"std::cout"," 语句左侧单击，设置一个断点。\n保持 ",[111,2907,1385],{},[111,2909,1177],{}," 目标不变，点击 CMake Tools 的 ",[1005,2912,2913],{},"Debug",[20,2915,2916,2917,2920,2921,2924,2925,48],{},"程序会在执行打印语句前暂停。此时可以查看调用堆栈，按 ",[111,2918,2919],{},"F10"," 执行当前语句，再按 ",[111,2922,2923],{},"F5"," 继续到程序结束。后面的普通运行继续使用 Launch；这个最小示例由 CMake Tools 直接启动调试，不需要另外编写 ",[111,2926,2927],{},"launch.json",[20,2929,2930],{},[1217,2931],{"alt":140,"src":2932},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792243006275-c20bd79c.webp",[56,2934],{},[12,2936,2938],{"id":2937},"加入电量检查观察-sequence-怎样执行","加入电量检查，观察 Sequence 怎样执行",[20,2940,2941,2942,2944,2945,2947,2948,2951],{},"保留刚才的工程和 ",[111,2943,2297],{},"。在 ",[111,2946,2012],{}," 中、",[111,2949,2950],{},"main()"," 函数前面，增加下面的条件函数：",[134,2953,2955],{"className":2016,"code":2954,"language":2018,"meta":140,"style":140},"BT::NodeStatus CheckBattery(BT::TreeNode&)\n{\n    \u002F\u002F 修改这里的数值，可以模拟电量变化\n    const double battery_level = 80.0;\n    std::cout \u003C\u003C \"[CheckBattery] battery=\" \u003C\u003C battery_level \u003C\u003C '\\n';\n\n    if (battery_level >= 20.0)\n    {\n        return BT::NodeStatus::SUCCESS;\n    }\n    return BT::NodeStatus::FAILURE;\n}\n",[111,2956,2957,2980,2984,2989,3006,3031,3035,3050,3055,3068,3073,3086],{"__ignoreMap":140},[198,2958,2959,2961,2963,2965,2968,2970,2972,2974,2976,2978],{"class":200,"line":201},[198,2960,2053],{"class":212},[198,2962,2056],{"class":204},[198,2964,2059],{"class":212},[198,2966,2967],{"class":212}," CheckBattery",[198,2969,1502],{"class":204},[198,2971,2053],{"class":212},[198,2973,2056],{"class":204},[198,2975,2071],{"class":212},[198,2977,2074],{"class":1414},[198,2979,1558],{"class":204},[198,2981,2982],{"class":200,"line":226},[198,2983,2081],{"class":204},[198,2985,2986],{"class":200,"line":244},[198,2987,2988],{"class":1468},"    \u002F\u002F 修改这里的数值，可以模拟电量变化\n",[198,2990,2991,2993,2996,2999,3001,3004],{"class":200,"line":262},[198,2992,2199],{"class":1414},[198,2994,2995],{"class":1414}," double",[198,2997,2998],{"class":204}," battery_level ",[198,3000,216],{"class":1414},[198,3002,3003],{"class":1203}," 80.0",[198,3005,2104],{"class":204},[198,3007,3008,3010,3012,3014,3017,3019,3021,3023,3025,3027,3029],{"class":200,"line":281},[198,3009,2086],{"class":212},[198,3011,2089],{"class":204},[198,3013,2092],{"class":1414},[198,3015,3016],{"class":219}," \"[CheckBattery] battery=\"",[198,3018,2229],{"class":1414},[198,3020,2998],{"class":204},[198,3022,2092],{"class":1414},[198,3024,2244],{"class":219},[198,3026,2098],{"class":1203},[198,3028,2249],{"class":219},[198,3030,2104],{"class":204},[198,3032,3033],{"class":200,"line":306},[198,3034,840],{"emptyLinePlaceholder":839},[198,3036,3037,3039,3042,3045,3048],{"class":200,"line":328},[198,3038,1499],{"class":1414},[198,3040,3041],{"class":204}," (battery_level ",[198,3043,3044],{"class":1414},">=",[198,3046,3047],{"class":1203}," 20.0",[198,3049,1558],{"class":204},[198,3051,3052],{"class":200,"line":338},[198,3053,3054],{"class":204},"    {\n",[198,3056,3057,3060,3062,3064,3066],{"class":200,"line":348},[198,3058,3059],{"class":1414},"        return",[198,3061,2112],{"class":212},[198,3063,2056],{"class":204},[198,3065,2059],{"class":212},[198,3067,2119],{"class":204},[198,3069,3070],{"class":200,"line":857},[198,3071,3072],{"class":204},"    }\n",[198,3074,3075,3077,3079,3081,3083],{"class":200,"line":872},[198,3076,2109],{"class":1414},[198,3078,2112],{"class":212},[198,3080,2056],{"class":204},[198,3082,2059],{"class":212},[198,3084,3085],{"class":204},"::FAILURE;\n",[198,3087,3088],{"class":200,"line":881},[198,3089,2124],{"class":204},[20,3091,3092,3093,3095,3096,3098,3099,3101],{},"然后在 ",[111,3094,2950],{}," 中，已有的 ",[111,3097,177],{}," 后面、",[111,3100,2535],{}," 前面，增加这一行注册：",[134,3103,3105],{"className":2016,"code":3104,"language":2018,"meta":140,"style":140},"factory.registerSimpleCondition(\"CheckBattery\", CheckBattery);\n",[111,3106,3107],{"__ignoreMap":140},[198,3108,3109,3112,3115,3117,3120],{"class":200,"line":201},[198,3110,3111],{"class":204},"factory.",[198,3113,3114],{"class":212},"registerSimpleCondition",[198,3116,1502],{"class":204},[198,3118,3119],{"class":219},"\"CheckBattery\"",[198,3121,3122],{"class":204},", CheckBattery);\n",[2332,3124,3126,3128],{"id":3125},"registersimplecondition-的参数",[111,3127,173],{}," 的参数",[20,3130,2339],{},[134,3132,3134],{"className":2016,"code":3133,"language":2018,"meta":140,"style":140},"void registerSimpleCondition(\n    const std::string& ID,\n    const BT::SimpleConditionNode::TickFunctor& tick_functor,\n    BT::PortsList ports = {});\n",[111,3135,3136,3145,3161,3182],{"__ignoreMap":140},[198,3137,3138,3140,3143],{"class":200,"line":201},[198,3139,2349],{"class":1414},[198,3141,3142],{"class":212}," registerSimpleCondition",[198,3144,2355],{"class":204},[198,3146,3147,3149,3151,3153,3155,3157,3159],{"class":200,"line":226},[198,3148,2199],{"class":1414},[198,3150,2362],{"class":212},[198,3152,2056],{"class":204},[198,3154,2367],{"class":212},[198,3156,2074],{"class":1414},[198,3158,235],{"class":2372},[198,3160,2375],{"class":204},[198,3162,3163,3165,3167,3169,3172,3174,3176,3178,3180],{"class":200,"line":244},[198,3164,2199],{"class":1414},[198,3166,2112],{"class":212},[198,3168,2056],{"class":204},[198,3170,3171],{"class":212},"SimpleConditionNode",[198,3173,2056],{"class":204},[198,3175,2391],{"class":212},[198,3177,2074],{"class":1414},[198,3179,2396],{"class":2372},[198,3181,2375],{"class":204},[198,3183,3184,3186,3188,3190,3192,3194],{"class":200,"line":262},[198,3185,2147],{"class":212},[198,3187,2056],{"class":204},[198,3189,2407],{"class":212},[198,3191,2410],{"class":2372},[198,3193,2413],{"class":1414},[198,3195,2416],{"class":204},[20,3197,3198],{},"作用：把回调注册成简单条件节点。",[3200,3201,3202,3214,3232],"ul",{},[3203,3204,3205,3206,3208,3209,2451,3211,48],"li",{},"第一个参数 ",[111,3207,471],{}," 是 XML 中使用的注册名，本例为 ",[111,3210,3119],{},[111,3212,3213],{},"\u003CCheckBattery\u002F>",[3203,3215,3216,3217,3219,3220,3222,3223,3225,3226,3228,3229,3231],{},"第二个参数 ",[111,3218,2462],{}," 是条件检查函数，本例传 ",[111,3221,268],{},"，签名同样为 ",[111,3224,2468],{},"。收到 tick 时才执行检查，本例用 ",[111,3227,2520],{}," 表示电量足够，用 ",[111,3230,2524],{}," 表示不足。",[3203,3233,3234,3235,3237],{},"第三个参数 ",[111,3236,2482],{}," 是可选端口列表。本例直接使用函数中的模拟电量，所以省略它。",[20,3239,2507,3240,3242,3243,3246],{},[111,3241,2349],{},"。条件是否满足由 ",[111,3244,3245],{},"CheckBattery()"," 的返回状态表达；注册调用本身不执行这次电量检查。",[20,3248,1861,3249,1865],{},[111,3250,2001],{},[134,3252,3254],{"className":192,"code":3253,"language":194,"meta":140,"style":140},"\u003Croot main_tree_to_execute=\"MainTree\">\n    \u003CBehaviorTree ID=\"MainTree\">\n        \u003CSequence name=\"mission_sequence\">\n            \u003CCheckBattery name=\"check_battery\"\u002F>\n            \u003CReportReady name=\"report_ready\"\u002F>\n        \u003C\u002FSequence>\n    \u003C\u002FBehaviorTree>\n\u003C\u002Froot>\n",[111,3255,3256,3270,3284,3298,3312,3327,3335,3343],{"__ignoreMap":140},[198,3257,3258,3260,3262,3264,3266,3268],{"class":200,"line":201},[198,3259,205],{"class":204},[198,3261,209],{"class":208},[198,3263,213],{"class":212},[198,3265,216],{"class":204},[198,3267,220],{"class":219},[198,3269,223],{"class":204},[198,3271,3272,3274,3276,3278,3280,3282],{"class":200,"line":226},[198,3273,229],{"class":204},[198,3275,232],{"class":208},[198,3277,235],{"class":212},[198,3279,216],{"class":204},[198,3281,220],{"class":219},[198,3283,223],{"class":204},[198,3285,3286,3288,3290,3292,3294,3296],{"class":200,"line":244},[198,3287,247],{"class":204},[198,3289,113],{"class":208},[198,3291,252],{"class":212},[198,3293,216],{"class":204},[198,3295,257],{"class":219},[198,3297,223],{"class":204},[198,3299,3300,3302,3304,3306,3308,3310],{"class":200,"line":262},[198,3301,265],{"class":204},[198,3303,268],{"class":208},[198,3305,252],{"class":212},[198,3307,216],{"class":204},[198,3309,275],{"class":219},[198,3311,278],{"class":204},[198,3313,3314,3316,3318,3320,3322,3325],{"class":200,"line":281},[198,3315,265],{"class":204},[198,3317,2474],{"class":208},[198,3319,252],{"class":212},[198,3321,216],{"class":204},[198,3323,3324],{"class":219},"\"report_ready\"",[198,3326,278],{"class":204},[198,3328,3329,3331,3333],{"class":200,"line":306},[198,3330,331],{"class":204},[198,3332,113],{"class":208},[198,3334,223],{"class":204},[198,3336,3337,3339,3341],{"class":200,"line":328},[198,3338,341],{"class":204},[198,3340,232],{"class":208},[198,3342,223],{"class":204},[198,3344,3345,3347,3349],{"class":200,"line":338},[198,3346,351],{"class":204},[198,3348,209],{"class":208},[198,3350,223],{"class":204},[20,3352,3353],{},"树变为：",[134,3355,3358],{"className":3356,"code":3357,"language":139,"meta":140},[137],"Sequence\n├── CheckBattery\n└── ReportReady\n",[111,3359,3357],{"__ignoreMap":140},[20,3361,3362,3363,42,3365,3367,3368,3370,3371,3373],{},"保存 ",[111,3364,2012],{},[111,3366,2001],{},"，在 CMake Tools 中先点击 ",[1005,3369,2831],{},"，成功后点击 ",[1005,3372,2843],{},"。这次只改了 C++ 和 XML，构建配置没有变化，沿用当前预设和运行目标即可。",[134,3375,3377],{"className":2016,"code":3376,"language":2018,"meta":140,"style":140},"#include \"behaviortree_cpp_v3\u002Fbt_factory.h\"\n\n#include \u003Ciostream>\n\n\u002F\u002F 一个立即完成的动作：打印机器人已就绪\nBT::NodeStatus ReportReady(BT::TreeNode&)\n{\n    std::cout \u003C\u003C \"[ReportReady] robot is ready\\n\";\n    return BT::NodeStatus::SUCCESS;\n}\n\n\u002F\u002F 电量检查\nBT::NodeStatus CheckBattery(BT::TreeNode&)\n{\n    \u002F\u002F 修改这里的数值，可以模拟电量变化\n    const double battery_level = 80.0;\n    std::cout \u003C\u003C \"[CheckBattery] battery=\" \u003C\u003C battery_level \u003C\u003C '\\n';\n\n    if (battery_level >= 20.0)\n    {\n        return BT::NodeStatus::SUCCESS;\n    }\n    return BT::NodeStatus::FAILURE;\n}\n\n\nint main()\n{\n    BT::BehaviorTreeFactory factory;\n    factory.registerSimpleAction(\"ReportReady\", ReportReady);\n    factory.registerSimpleCondition(\"CheckBattery\", CheckBattery);\n\n    \u002F\u002F 根据 XML 创建树，再从根节点执行一次\n    auto tree = factory.createTreeFromFile(BT_XML_PATH);\n    const BT::NodeStatus result = tree.tickRoot();\n    std::cout \u003C\u003C \"Tree result: \" \u003C\u003C BT::toStr(result) \u003C\u003C '\\n';\n\n    return result == BT::NodeStatus::SUCCESS ? 0 : 1;\n}\n",[111,3378,3379,3385,3389,3395,3399,3403,3425,3429,3445,3457,3461,3465,3470,3492,3496,3500,3514,3538,3542,3554,3558,3570,3574,3586,3590,3594,3598,3606,3610,3616,3628,3640,3644,3648,3662,3678,3708,3712,3738],{"__ignoreMap":140},[198,3380,3381,3383],{"class":200,"line":201},[198,3382,2025],{"class":1414},[198,3384,2028],{"class":219},[198,3386,3387],{"class":200,"line":226},[198,3388,840],{"emptyLinePlaceholder":839},[198,3390,3391,3393],{"class":200,"line":244},[198,3392,2025],{"class":1414},[198,3394,2039],{"class":219},[198,3396,3397],{"class":200,"line":262},[198,3398,840],{"emptyLinePlaceholder":839},[198,3400,3401],{"class":200,"line":281},[198,3402,2048],{"class":1468},[198,3404,3405,3407,3409,3411,3413,3415,3417,3419,3421,3423],{"class":200,"line":306},[198,3406,2053],{"class":212},[198,3408,2056],{"class":204},[198,3410,2059],{"class":212},[198,3412,2062],{"class":212},[198,3414,1502],{"class":204},[198,3416,2053],{"class":212},[198,3418,2056],{"class":204},[198,3420,2071],{"class":212},[198,3422,2074],{"class":1414},[198,3424,1558],{"class":204},[198,3426,3427],{"class":200,"line":328},[198,3428,2081],{"class":204},[198,3430,3431,3433,3435,3437,3439,3441,3443],{"class":200,"line":338},[198,3432,2086],{"class":212},[198,3434,2089],{"class":204},[198,3436,2092],{"class":1414},[198,3438,2095],{"class":219},[198,3440,2098],{"class":1203},[198,3442,2101],{"class":219},[198,3444,2104],{"class":204},[198,3446,3447,3449,3451,3453,3455],{"class":200,"line":348},[198,3448,2109],{"class":1414},[198,3450,2112],{"class":212},[198,3452,2056],{"class":204},[198,3454,2059],{"class":212},[198,3456,2119],{"class":204},[198,3458,3459],{"class":200,"line":857},[198,3460,2124],{"class":204},[198,3462,3463],{"class":200,"line":872},[198,3464,840],{"emptyLinePlaceholder":839},[198,3466,3467],{"class":200,"line":881},[198,3468,3469],{"class":1468},"\u002F\u002F 电量检查\n",[198,3471,3472,3474,3476,3478,3480,3482,3484,3486,3488,3490],{"class":200,"line":1546},[198,3473,2053],{"class":212},[198,3475,2056],{"class":204},[198,3477,2059],{"class":212},[198,3479,2967],{"class":212},[198,3481,1502],{"class":204},[198,3483,2053],{"class":212},[198,3485,2056],{"class":204},[198,3487,2071],{"class":212},[198,3489,2074],{"class":1414},[198,3491,1558],{"class":204},[198,3493,3494],{"class":200,"line":1561},[198,3495,2081],{"class":204},[198,3497,3498],{"class":200,"line":1569},[198,3499,2988],{"class":1468},[198,3501,3502,3504,3506,3508,3510,3512],{"class":200,"line":1583},[198,3503,2199],{"class":1414},[198,3505,2995],{"class":1414},[198,3507,2998],{"class":204},[198,3509,216],{"class":1414},[198,3511,3003],{"class":1203},[198,3513,2104],{"class":204},[198,3515,3516,3518,3520,3522,3524,3526,3528,3530,3532,3534,3536],{"class":200,"line":1591},[198,3517,2086],{"class":212},[198,3519,2089],{"class":204},[198,3521,2092],{"class":1414},[198,3523,3016],{"class":219},[198,3525,2229],{"class":1414},[198,3527,2998],{"class":204},[198,3529,2092],{"class":1414},[198,3531,2244],{"class":219},[198,3533,2098],{"class":1203},[198,3535,2249],{"class":219},[198,3537,2104],{"class":204},[198,3539,3540],{"class":200,"line":1600},[198,3541,840],{"emptyLinePlaceholder":839},[198,3543,3544,3546,3548,3550,3552],{"class":200,"line":1605},[198,3545,1499],{"class":1414},[198,3547,3041],{"class":204},[198,3549,3044],{"class":1414},[198,3551,3047],{"class":1203},[198,3553,1558],{"class":204},[198,3555,3556],{"class":200,"line":1611},[198,3557,3054],{"class":204},[198,3559,3560,3562,3564,3566,3568],{"class":200,"line":1635},[198,3561,3059],{"class":1414},[198,3563,2112],{"class":212},[198,3565,2056],{"class":204},[198,3567,2059],{"class":212},[198,3569,2119],{"class":204},[198,3571,3572],{"class":200,"line":1653},[198,3573,3072],{"class":204},[198,3575,3576,3578,3580,3582,3584],{"class":200,"line":1671},[198,3577,2109],{"class":1414},[198,3579,2112],{"class":212},[198,3581,2056],{"class":204},[198,3583,2059],{"class":212},[198,3585,3085],{"class":204},[198,3587,3588],{"class":200,"line":1689},[198,3589,2124],{"class":204},[198,3591,3592],{"class":200,"line":1707},[198,3593,840],{"emptyLinePlaceholder":839},[198,3595,3596],{"class":200,"line":1712},[198,3597,840],{"emptyLinePlaceholder":839},[198,3599,3600,3602,3604],{"class":200,"line":1718},[198,3601,2133],{"class":1414},[198,3603,2136],{"class":212},[198,3605,1543],{"class":204},[198,3607,3608],{"class":200,"line":1724},[198,3609,2081],{"class":204},[198,3611,3612,3614],{"class":200,"line":1730},[198,3613,2147],{"class":212},[198,3615,2150],{"class":204},[198,3617,3618,3620,3622,3624,3626],{"class":200,"line":1736},[198,3619,2155],{"class":204},[198,3621,2158],{"class":212},[198,3623,1502],{"class":204},[198,3625,2163],{"class":219},[198,3627,2166],{"class":204},[198,3629,3630,3632,3634,3636,3638],{"class":200,"line":1742},[198,3631,2155],{"class":204},[198,3633,3114],{"class":212},[198,3635,1502],{"class":204},[198,3637,3119],{"class":219},[198,3639,3122],{"class":204},[198,3641,3642],{"class":200,"line":1747},[198,3643,840],{"emptyLinePlaceholder":839},[198,3645,3646],{"class":200,"line":1753},[198,3647,2175],{"class":1468},[198,3649,3650,3652,3654,3656,3658,3660],{"class":200,"line":1765},[198,3651,2180],{"class":1414},[198,3653,2183],{"class":204},[198,3655,216],{"class":1414},[198,3657,2188],{"class":204},[198,3659,2191],{"class":212},[198,3661,2194],{"class":204},[198,3663,3664,3666,3668,3670,3672,3674,3676],{"class":200,"line":1779},[198,3665,2199],{"class":1414},[198,3667,2112],{"class":212},[198,3669,2204],{"class":204},[198,3671,216],{"class":1414},[198,3673,2209],{"class":204},[198,3675,2212],{"class":212},[198,3677,2215],{"class":204},[198,3679,3680,3682,3684,3686,3688,3690,3692,3694,3696,3698,3700,3702,3704,3706],{"class":200,"line":1784},[198,3681,2086],{"class":212},[198,3683,2089],{"class":204},[198,3685,2092],{"class":1414},[198,3687,2226],{"class":219},[198,3689,2229],{"class":1414},[198,3691,2112],{"class":212},[198,3693,2056],{"class":204},[198,3695,2236],{"class":212},[198,3697,2239],{"class":204},[198,3699,2092],{"class":1414},[198,3701,2244],{"class":219},[198,3703,2098],{"class":1203},[198,3705,2249],{"class":219},[198,3707,2104],{"class":204},[198,3709,3710],{"class":200,"line":1792},[198,3711,840],{"emptyLinePlaceholder":839},[198,3713,3714,3716,3718,3720,3722,3724,3726,3728,3730,3732,3734,3736],{"class":200,"line":1804},[198,3715,2109],{"class":1414},[198,3717,2262],{"class":204},[198,3719,2265],{"class":1414},[198,3721,2112],{"class":212},[198,3723,2056],{"class":204},[198,3725,2059],{"class":212},[198,3727,2274],{"class":204},[198,3729,2277],{"class":1414},[198,3731,2280],{"class":1203},[198,3733,2283],{"class":1414},[198,3735,2286],{"class":1203},[198,3737,2104],{"class":204},[198,3739,3741],{"class":200,"line":3740},39,[198,3742,2124],{"class":204},[20,3744,3745],{},"电量为 80 时，预期输出：",[134,3747,3750],{"className":3748,"code":3749,"language":139,"meta":140},[137],"[CheckBattery] battery=80\n[ReportReady] robot is ready\nTree result: SUCCESS\n",[111,3751,3749],{"__ignoreMap":140},[20,3753,3754],{},[1217,3755],{"alt":140,"src":3756},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792283421070-bd4fabd0.webp",[20,3758,3759,3760,3762,3763,3766,3767,3770,3771,2710],{},"接着在编辑器中将 ",[111,3761,3245],{}," 的 ",[111,3764,3765],{},"battery_level"," 改为 ",[111,3768,3769],{},"10.0","，保存后再次点击 ",[1005,3772,3773],{},"Build → Launch",[134,3775,3778],{"className":3776,"code":3777,"language":139,"meta":140},[137],"[CheckBattery] battery=10\nTree result: FAILURE\n",[111,3779,3777],{"__ignoreMap":140},[20,3781,3782],{},[1217,3783],{"alt":140,"src":3784},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792283468293-d1ed0d3f.webp",[20,3786,3787,3789,3790,3792,3793,3795,3796,3799],{},[111,3788,268],{}," 失败后，",[111,3791,113],{}," 不再执行 ",[111,3794,2474],{},"。程序退出码为 ",[111,3797,3798],{},"1","，表示树任务失败，不是编译错误。",[20,3801,3802,3811],{},[1005,3803,3804,3805,3807,3808,48],{},"继续下一步前，把 ",[111,3806,3765],{}," 改回 ",[111,3809,3810],{},"80.0"," 否则后面新增的动作仍然会因为电量条件失败而被跳过。",[56,3813],{},[12,3815,3816],{"id":3816},"将打印动作换成带输入端口的节点类",[20,3818,3819,3820,3762,3822,48],{},"现在希望目标可以从 XML 配置。把原来固定打印“机器人已就绪”的动作，换成读取并打印 ",[111,3821,460],{},[111,3823,155],{},[20,3825,3826],{},"这一轮只增加输入端口，先使用 XML 固定字符串；下一轮再连接 Blackboard。",[366,3828,3829],{"id":3829},"添加头文件并替换动作实现",[20,3831,3832,3833,42,3836,3839],{},"保留 ",[111,3834,3835],{},"bt_factory.h",[111,3837,3838],{},"\u003Ciostream>","，再在文件顶部增加两个头文件：",[134,3841,3843],{"className":2016,"code":3842,"language":2018,"meta":140,"style":140},"#include \u003Cexception>\n#include \u003Cstring>\n",[111,3844,3845,3852],{"__ignoreMap":140},[198,3846,3847,3849],{"class":200,"line":201},[198,3848,2025],{"class":1414},[198,3850,3851],{"class":219}," \u003Cexception>\n",[198,3853,3854,3856],{"class":200,"line":226},[198,3855,2025],{"class":1414},[198,3857,3858],{"class":219}," \u003Cstring>\n",[20,3860,3861,3862,3864,3865,3867,3868,3870,3871,2710],{},"删除原来的 ",[111,3863,2297],{}," 函数，换成下面的完整 ",[111,3866,155],{}," 类;\n",[111,3869,3245],{}," 函数继续保留，电量恢复为 ",[111,3872,3810],{},[134,3874,3876],{"className":2016,"code":3875,"language":2018,"meta":140,"style":140},"class PrintGoal : public BT::SyncActionNode\n{\npublic:\n    PrintGoal(const std::string& name, const BT::NodeConfiguration& config)\n        : BT::SyncActionNode(name, config)\n    {}\n\n    static BT::PortsList providedPorts()\n    {\n        \u002F\u002F 节点需要一个名为 goal 的字符串输入\n        return {BT::InputPort\u003Cstd::string>(\"goal\")};\n    }\n\n    BT::NodeStatus tick() override\n    {\n        \u002F\u002F 每次执行时读取输入，读取成功后再使用\n        auto goal = getInput\u003Cstd::string>(\"goal\");\n        if (!goal)\n        {\n            throw BT::RuntimeError(\"Cannot read input [goal]: \", goal.error());\n        }\n\n        std::cout \u003C\u003C \"[PrintGoal] target=\" \u003C\u003C goal.value() \u003C\u003C '\\n';\n        return BT::NodeStatus::SUCCESS;\n    }\n};\n",[111,3877,3878,3899,3903,3908,3947,3957,3962,3966,3982,3986,3991,4022,4026,4030,4047,4051,4056,4084,4098,4103,4128,4133,4137,4169,4181,4185],{"__ignoreMap":140},[198,3879,3880,3883,3886,3889,3892,3894,3896],{"class":200,"line":201},[198,3881,3882],{"class":1414},"class",[198,3884,3885],{"class":212}," PrintGoal",[198,3887,3888],{"class":204}," : ",[198,3890,3891],{"class":1414},"public",[198,3893,2112],{"class":212},[198,3895,2056],{"class":204},[198,3897,3898],{"class":212},"SyncActionNode\n",[198,3900,3901],{"class":200,"line":226},[198,3902,2081],{"class":204},[198,3904,3905],{"class":200,"line":244},[198,3906,3907],{"class":1414},"public:\n",[198,3909,3910,3913,3915,3918,3920,3922,3924,3926,3928,3931,3933,3935,3937,3940,3942,3945],{"class":200,"line":262},[198,3911,3912],{"class":212},"    PrintGoal",[198,3914,1502],{"class":204},[198,3916,3917],{"class":1414},"const",[198,3919,2362],{"class":212},[198,3921,2056],{"class":204},[198,3923,2367],{"class":212},[198,3925,2074],{"class":1414},[198,3927,252],{"class":2372},[198,3929,3930],{"class":204},", ",[198,3932,3917],{"class":1414},[198,3934,2112],{"class":212},[198,3936,2056],{"class":204},[198,3938,3939],{"class":212},"NodeConfiguration",[198,3941,2074],{"class":1414},[198,3943,3944],{"class":2372}," config",[198,3946,1558],{"class":204},[198,3948,3949,3952,3954],{"class":200,"line":281},[198,3950,3951],{"class":204},"        : BT::",[198,3953,169],{"class":212},[198,3955,3956],{"class":204},"(name, config)\n",[198,3958,3959],{"class":200,"line":306},[198,3960,3961],{"class":204},"    {}\n",[198,3963,3964],{"class":200,"line":328},[198,3965,840],{"emptyLinePlaceholder":839},[198,3967,3968,3971,3973,3975,3977,3980],{"class":200,"line":338},[198,3969,3970],{"class":1414},"    static",[198,3972,2112],{"class":212},[198,3974,2056],{"class":204},[198,3976,2407],{"class":212},[198,3978,3979],{"class":212}," providedPorts",[198,3981,1543],{"class":204},[198,3983,3984],{"class":200,"line":348},[198,3985,3054],{"class":204},[198,3987,3988],{"class":200,"line":857},[198,3989,3990],{"class":1468},"        \u002F\u002F 节点需要一个名为 goal 的字符串输入\n",[198,3992,3993,3995,3998,4000,4002,4004,4006,4009,4011,4013,4016,4019],{"class":200,"line":872},[198,3994,3059],{"class":1414},[198,3996,3997],{"class":204}," {",[198,3999,2053],{"class":212},[198,4001,2056],{"class":204},[198,4003,557],{"class":212},[198,4005,205],{"class":204},[198,4007,4008],{"class":212},"std",[198,4010,2056],{"class":204},[198,4012,2367],{"class":212},[198,4014,4015],{"class":204},">(",[198,4017,4018],{"class":219},"\"goal\"",[198,4020,4021],{"class":204},")};\n",[198,4023,4024],{"class":200,"line":881},[198,4025,3072],{"class":204},[198,4027,4028],{"class":200,"line":1546},[198,4029,840],{"emptyLinePlaceholder":839},[198,4031,4032,4034,4036,4038,4041,4044],{"class":200,"line":1561},[198,4033,2147],{"class":212},[198,4035,2056],{"class":204},[198,4037,2059],{"class":212},[198,4039,4040],{"class":212}," tick",[198,4042,4043],{"class":204},"() ",[198,4045,4046],{"class":1414},"override\n",[198,4048,4049],{"class":200,"line":1569},[198,4050,3054],{"class":204},[198,4052,4053],{"class":200,"line":1583},[198,4054,4055],{"class":1468},"        \u002F\u002F 每次执行时读取输入，读取成功后再使用\n",[198,4057,4058,4061,4064,4066,4069,4071,4073,4075,4077,4079,4081],{"class":200,"line":1591},[198,4059,4060],{"class":1414},"        auto",[198,4062,4063],{"class":204}," goal ",[198,4065,216],{"class":1414},[198,4067,4068],{"class":212}," getInput",[198,4070,205],{"class":204},[198,4072,4008],{"class":212},[198,4074,2056],{"class":204},[198,4076,2367],{"class":212},[198,4078,4015],{"class":204},[198,4080,4018],{"class":219},[198,4082,4083],{"class":204},");\n",[198,4085,4086,4089,4092,4095],{"class":200,"line":1600},[198,4087,4088],{"class":1414},"        if",[198,4090,4091],{"class":204}," (",[198,4093,4094],{"class":1414},"!",[198,4096,4097],{"class":204},"goal)\n",[198,4099,4100],{"class":200,"line":1605},[198,4101,4102],{"class":204},"        {\n",[198,4104,4105,4108,4110,4112,4115,4117,4120,4123,4126],{"class":200,"line":1611},[198,4106,4107],{"class":1414},"            throw",[198,4109,2112],{"class":212},[198,4111,2056],{"class":204},[198,4113,4114],{"class":212},"RuntimeError",[198,4116,1502],{"class":204},[198,4118,4119],{"class":219},"\"Cannot read input [goal]: \"",[198,4121,4122],{"class":204},", goal.",[198,4124,4125],{"class":212},"error",[198,4127,2606],{"class":204},[198,4129,4130],{"class":200,"line":1635},[198,4131,4132],{"class":204},"        }\n",[198,4134,4135],{"class":200,"line":1653},[198,4136,840],{"emptyLinePlaceholder":839},[198,4138,4139,4142,4144,4146,4149,4151,4154,4157,4159,4161,4163,4165,4167],{"class":200,"line":1671},[198,4140,4141],{"class":212},"        std",[198,4143,2089],{"class":204},[198,4145,2092],{"class":1414},[198,4147,4148],{"class":219}," \"[PrintGoal] target=\"",[198,4150,2229],{"class":1414},[198,4152,4153],{"class":204}," goal.",[198,4155,4156],{"class":212},"value",[198,4158,4043],{"class":204},[198,4160,2092],{"class":1414},[198,4162,2244],{"class":219},[198,4164,2098],{"class":1203},[198,4166,2249],{"class":219},[198,4168,2104],{"class":204},[198,4170,4171,4173,4175,4177,4179],{"class":200,"line":1689},[198,4172,3059],{"class":1414},[198,4174,2112],{"class":212},[198,4176,2056],{"class":204},[198,4178,2059],{"class":212},[198,4180,2119],{"class":204},[198,4182,4183],{"class":200,"line":1707},[198,4184,3072],{"class":204},[198,4186,4187],{"class":200,"line":1712},[198,4188,4189],{"class":204},"};\n",[20,4191,4192],{},"类里的几个部分各有职责：",[66,4194,4195,4204],{},[69,4196,4197],{},[72,4198,4199,4201],{},[75,4200,77],{},[75,4202,4203],{},"作用",[85,4205,4206,4217,4225,4236],{},[72,4207,4208,4214],{},[90,4209,4210,4211],{},"继承 ",[111,4212,4213],{},"BT::SyncActionNode",[90,4215,4216],{},"表示这是立即完成的同步动作",[72,4218,4219,4222],{},[90,4220,4221],{},"构造函数",[90,4223,4224],{},"将实例名称和配置交给基类保存",[72,4226,4227,4231],{},[90,4228,4229],{},[111,4230,647],{},[90,4232,4233,4234],{},"声明字符串输入端口 ",[111,4235,460],{},[72,4237,4238,4242],{},[90,4239,4240],{},[111,4241,2665],{},[90,4243,4244],{},"读取目标，打印后返回成功",[2332,4246,4248,4249,2672,4251],{"id":4247},"构造函数的-name-与-config","构造函数的 ",[111,4250,475],{},[111,4252,4253],{},"config",[20,4255,4256],{},"本例构造函数的两个参数由工厂在创建节点实例时提供：",[66,4258,4259,4270],{},[69,4260,4261],{},[72,4262,4263,4265,4268],{},[75,4264,2428],{},[75,4266,4267],{},"类型",[75,4269,381],{},[85,4271,4272,4289],{},[72,4273,4274,4278,4283],{},[90,4275,2441,4276],{},[111,4277,475],{},[90,4279,4280],{},[111,4281,4282],{},"const std::string&",[90,4284,4285,4286,4288],{},"当前节点的实例名称；XML 可以用 ",[111,4287,475],{}," 属性设置，省略时通常使用节点的注册名",[72,4290,4291,4295,4300],{},[90,4292,2459,4293],{},[111,4294,4253],{},[90,4296,4297],{},[111,4298,4299],{},"const BT::NodeConfiguration&",[90,4301,4302],{},"当前实例的配置，包含 Blackboard，以及输入、输出端口的映射",[20,4304,4305,4306,4309,4310,4313,4314,4316,4317,4319],{},"初始化列表中的 ",[111,4307,4308],{},"BT::SyncActionNode(name, config)"," 将这两个参数交给基类保存，后面的 ",[111,4311,4312],{},"getInput()"," 才能按本实例的 XML 配置读取数据。\n这里不需要在 ",[111,4315,2950],{}," 中手动调用 ",[111,4318,155],{}," 的构造函数。",[2332,4321,4323,2672,4325],{"id":4322},"providedports-与-inputportt",[111,4324,647],{},[111,4326,4327],{},"InputPort\u003CT>()",[20,4329,4330,4333],{},[111,4331,4332],{},"static BT::PortsList providedPorts()"," 不接收参数，返回本节点的端口声明。它是自定义节点按约定提供的静态函数，工厂注册节点类时会读取它；声明里保存的是接口信息，实际目标值由 XML 或 Blackboard 提供。",[20,4335,4336,4337,2710],{},"本例使用 ",[111,4338,4339],{},"BT::InputPort\u003Cstd::string>(\"goal\")",[66,4341,4342,4351],{},[69,4343,4344],{},[72,4345,4346,4349],{},[75,4347,4348],{},"位置",[75,4350,381],{},[85,4352,4353,4364,4377],{},[72,4354,4355,4361],{},[90,4356,4357,4358],{},"模板参数 ",[111,4359,4360],{},"\u003Cstd::string>",[90,4362,4363],{},"输入值的 C++ 类型",[72,4365,4366,4371],{},[90,4367,4368,4369],{},"第一个函数参数 ",[111,4370,4018],{},[90,4372,4373,4374,4376],{},"节点自己的输入端口名，对应 XML 的 ",[111,4375,460],{}," 属性",[72,4378,4379,4382],{},[90,4380,4381],{},"可选的第二个函数参数",[90,4383,4384],{},"端口说明文字，本例省略；带默认值的三参数形式在下一篇说明",[20,4386,4387,4388,4391,4392,4394],{},"返回值：一个端口名称和 ",[111,4389,4390],{},"BT::PortInfo"," 组成的键值对，加入 ",[111,4393,2488],{}," 后供工厂使用。这次调用声明端口，不会立即读取目标。",[2332,4396,4398,4400],{"id":4397},"getinputt-的参数与返回值",[111,4399,654],{}," 的参数与返回值",[20,4402,4403],{},"本例使用的原型为：",[134,4405,4407],{"className":2016,"code":4406,"language":2018,"meta":140,"style":140},"template \u003Ctypename T>\nBT::Optional\u003CT> getInput(const std::string& key) const;\n",[111,4408,4409,4425],{"__ignoreMap":140},[198,4410,4411,4414,4417,4420,4423],{"class":200,"line":201},[198,4412,4413],{"class":1414},"template",[198,4415,4416],{"class":204}," \u003C",[198,4418,4419],{"class":1414},"typename",[198,4421,4422],{"class":212}," T",[198,4424,223],{"class":204},[198,4426,4427,4429,4431,4434,4436,4439,4442,4445,4447,4449,4451,4453,4455,4457,4460,4463,4465],{"class":200,"line":226},[198,4428,2053],{"class":212},[198,4430,2056],{"class":204},[198,4432,4433],{"class":212},"Optional",[198,4435,205],{"class":204},[198,4437,4438],{"class":212},"T",[198,4440,4441],{"class":204},"> ",[198,4443,4444],{"class":212},"getInput",[198,4446,1502],{"class":204},[198,4448,3917],{"class":1414},[198,4450,2362],{"class":212},[198,4452,2056],{"class":204},[198,4454,2367],{"class":212},[198,4456,2074],{"class":1414},[198,4458,4459],{"class":2372}," key",[198,4461,4462],{"class":204},") ",[198,4464,3917],{"class":1414},[198,4466,2104],{"class":204},[20,4468,4469,4470,4472],{},"作用：根据当前节点的端口映射读取输入，并按 ",[111,4471,4438],{}," 获取或转换数据。",[3200,4474,4475,4482],{},[3203,4476,4357,4477,4479,4480,48],{},[111,4478,4438],{}," 是期望读取的类型，本例为 ",[111,4481,2696],{},[3203,4483,4484,4485,4488,4489,4492,4493,4495],{},"唯一的函数参数 ",[111,4486,4487],{},"key"," 是",[1005,4490,4491],{},"节点自己的端口名","，本例为 ",[111,4494,4018],{},"。XML 决定它来自固定文本还是某个 Blackboard 条目。",[20,4497,2507,4498,4501,4502,4505,4506,4509,4510,4513],{},[111,4499,4500],{},"BT::Optional\u003CT>","，这是 BehaviorTree.CPP 使用的带错误信息的结果类型。先用 ",[111,4503,4504],{},"if (!goal)"," 判断读取是否失败；有效时用 ",[111,4507,4508],{},"goal.value()"," 取得值，失败时用 ",[111,4511,4512],{},"goal.error()"," 取得错误字符串。",[20,4515,4516,4519,4520,3762,4522,4525,4526,4529,4530,4533,4534,48],{},[111,4517,4518],{},"throw BT::RuntimeError(\"Cannot read input [goal]: \", goal.error());"," 将固定提示和库返回的错误信息组成异常，再交给 ",[111,4521,2950],{},[111,4523,4524],{},"catch"," 显示。",[111,4527,4528],{},"BT::RuntimeError"," 是库提供的异常类型，可以被 ",[111,4531,4532],{},"const std::exception&"," 捕获。接口细节见 ",[24,4535,4538],{"href":4536,"rel":4537},"https:\u002F\u002Fgithub.com\u002FBehaviorTree\u002FBehaviorTree.CPP\u002Fblob\u002F3.8.7\u002Finclude\u002Fbehaviortree_cpp_v3\u002Ftree_node.h",[28],"3.8.7 tree_node.h",[366,4540,4542],{"id":4541},"替换-main-函数","替换 main 函数",[20,4544,1861,4545,4547,4548,4551],{},[111,4546,2950],{}," 整个替换为以下版本。这里保留电量条件的注册，将动作注册改成 ",[111,4549,4550],{},"registerNodeType\u003CPrintGoal>()","，并添加错误报告：",[134,4553,4555],{"className":2016,"code":4554,"language":2018,"meta":140,"style":140},"int main()\n{\n    try\n    {\n        BT::BehaviorTreeFactory factory;\n        factory.registerSimpleCondition(\"CheckBattery\", CheckBattery);\n        factory.registerNodeType\u003CPrintGoal>(\"PrintGoal\");\n\n        auto tree = factory.createTreeFromFile(BT_XML_PATH);\n        const BT::NodeStatus result = tree.tickRoot();\n        std::cout \u003C\u003C \"Tree result: \" \u003C\u003C BT::toStr(result) \u003C\u003C '\\n';\n\n        return result == BT::NodeStatus::SUCCESS ? 0 : 1;\n    }\n    catch (const std::exception& error)\n    {\n        \u002F\u002F 创建树或读取输入出错时，显示原因\n        std::cerr \u003C\u003C \"Error: \" \u003C\u003C error.what() \u003C\u003C '\\n';\n        return 2;\n    }\n}\n",[111,4556,4557,4565,4569,4574,4578,4585,4598,4616,4620,4634,4651,4681,4685,4711,4715,4734,4738,4743,4775,4784,4788],{"__ignoreMap":140},[198,4558,4559,4561,4563],{"class":200,"line":201},[198,4560,2133],{"class":1414},[198,4562,2136],{"class":212},[198,4564,1543],{"class":204},[198,4566,4567],{"class":200,"line":226},[198,4568,2081],{"class":204},[198,4570,4571],{"class":200,"line":244},[198,4572,4573],{"class":1414},"    try\n",[198,4575,4576],{"class":200,"line":262},[198,4577,3054],{"class":204},[198,4579,4580,4583],{"class":200,"line":281},[198,4581,4582],{"class":212},"        BT",[198,4584,2150],{"class":204},[198,4586,4587,4590,4592,4594,4596],{"class":200,"line":306},[198,4588,4589],{"class":204},"        factory.",[198,4591,3114],{"class":212},[198,4593,1502],{"class":204},[198,4595,3119],{"class":219},[198,4597,3122],{"class":204},[198,4599,4600,4603,4605,4607,4610,4612,4614],{"class":200,"line":328},[198,4601,4602],{"class":204},"        factory.registerNodeType",[198,4604,205],{"class":1414},[198,4606,155],{"class":204},[198,4608,4609],{"class":1414},">",[198,4611,1502],{"class":204},[198,4613,716],{"class":219},[198,4615,4083],{"class":204},[198,4617,4618],{"class":200,"line":338},[198,4619,840],{"emptyLinePlaceholder":839},[198,4621,4622,4624,4626,4628,4630,4632],{"class":200,"line":348},[198,4623,4060],{"class":1414},[198,4625,2183],{"class":204},[198,4627,216],{"class":1414},[198,4629,2188],{"class":204},[198,4631,2191],{"class":212},[198,4633,2194],{"class":204},[198,4635,4636,4639,4641,4643,4645,4647,4649],{"class":200,"line":857},[198,4637,4638],{"class":1414},"        const",[198,4640,2112],{"class":212},[198,4642,2204],{"class":204},[198,4644,216],{"class":1414},[198,4646,2209],{"class":204},[198,4648,2212],{"class":212},[198,4650,2215],{"class":204},[198,4652,4653,4655,4657,4659,4661,4663,4665,4667,4669,4671,4673,4675,4677,4679],{"class":200,"line":872},[198,4654,4141],{"class":212},[198,4656,2089],{"class":204},[198,4658,2092],{"class":1414},[198,4660,2226],{"class":219},[198,4662,2229],{"class":1414},[198,4664,2112],{"class":212},[198,4666,2056],{"class":204},[198,4668,2236],{"class":212},[198,4670,2239],{"class":204},[198,4672,2092],{"class":1414},[198,4674,2244],{"class":219},[198,4676,2098],{"class":1203},[198,4678,2249],{"class":219},[198,4680,2104],{"class":204},[198,4682,4683],{"class":200,"line":881},[198,4684,840],{"emptyLinePlaceholder":839},[198,4686,4687,4689,4691,4693,4695,4697,4699,4701,4703,4705,4707,4709],{"class":200,"line":1546},[198,4688,3059],{"class":1414},[198,4690,2262],{"class":204},[198,4692,2265],{"class":1414},[198,4694,2112],{"class":212},[198,4696,2056],{"class":204},[198,4698,2059],{"class":212},[198,4700,2274],{"class":204},[198,4702,2277],{"class":1414},[198,4704,2280],{"class":1203},[198,4706,2283],{"class":1414},[198,4708,2286],{"class":1203},[198,4710,2104],{"class":204},[198,4712,4713],{"class":200,"line":1561},[198,4714,3072],{"class":204},[198,4716,4717,4720,4722,4724,4726,4729,4731],{"class":200,"line":1569},[198,4718,4719],{"class":1414},"    catch",[198,4721,4091],{"class":204},[198,4723,3917],{"class":1414},[198,4725,2362],{"class":212},[198,4727,4728],{"class":204},"::exception",[198,4730,2074],{"class":1414},[198,4732,4733],{"class":204}," error)\n",[198,4735,4736],{"class":200,"line":1583},[198,4737,3054],{"class":204},[198,4739,4740],{"class":200,"line":1591},[198,4741,4742],{"class":1468},"        \u002F\u002F 创建树或读取输入出错时，显示原因\n",[198,4744,4745,4747,4750,4752,4755,4757,4760,4763,4765,4767,4769,4771,4773],{"class":200,"line":1600},[198,4746,4141],{"class":212},[198,4748,4749],{"class":204},"::cerr ",[198,4751,2092],{"class":1414},[198,4753,4754],{"class":219}," \"Error: \"",[198,4756,2229],{"class":1414},[198,4758,4759],{"class":204}," error.",[198,4761,4762],{"class":212},"what",[198,4764,4043],{"class":204},[198,4766,2092],{"class":1414},[198,4768,2244],{"class":219},[198,4770,2098],{"class":1203},[198,4772,2249],{"class":219},[198,4774,2104],{"class":204},[198,4776,4777,4779,4782],{"class":200,"line":1605},[198,4778,3059],{"class":1414},[198,4780,4781],{"class":1203}," 2",[198,4783,2104],{"class":204},[198,4785,4786],{"class":200,"line":1611},[198,4787,3072],{"class":204},[198,4789,4790],{"class":200,"line":1635},[198,4791,2124],{"class":204},[20,4793,4794,4796,4797,4799,4800,4802],{},[111,4795,155],{}," 类和 ",[111,4798,3245],{}," 都应放在 ",[111,4801,2950],{}," 前面，保证注册时已经有对应定义。",[2332,4804,4806,3128],{"id":4805},"registernodetypet-的参数",[111,4807,4808],{},"registerNodeType\u003CT>()",[20,4810,4403],{},[134,4812,4814],{"className":2016,"code":4813,"language":2018,"meta":140,"style":140},"template \u003Ctypename T>\nvoid registerNodeType(const std::string& ID);\n",[111,4815,4816,4828],{"__ignoreMap":140},[198,4817,4818,4820,4822,4824,4826],{"class":200,"line":201},[198,4819,4413],{"class":1414},[198,4821,4416],{"class":204},[198,4823,4419],{"class":1414},[198,4825,4422],{"class":212},[198,4827,223],{"class":204},[198,4829,4830,4832,4835,4837,4839,4841,4843,4845,4847,4849],{"class":200,"line":226},[198,4831,2349],{"class":1414},[198,4833,4834],{"class":212}," registerNodeType",[198,4836,1502],{"class":204},[198,4838,3917],{"class":1414},[198,4840,2362],{"class":212},[198,4842,2056],{"class":204},[198,4844,2367],{"class":212},[198,4846,2074],{"class":1414},[198,4848,235],{"class":2372},[198,4850,4083],{"class":204},[20,4852,4853],{},"作用：将一个自定义节点类注册到工厂，让工厂能够按 XML 创建该类的实例。",[66,4855,4856,4867],{},[69,4857,4858],{},[72,4859,4860,4862,4864],{},[75,4861,4348],{},[75,4863,381],{},[75,4865,4866],{},"本例",[85,4868,4869,4882],{},[72,4870,4871,4875,4878],{},[90,4872,4357,4873],{},[111,4874,4438],{},[90,4876,4877],{},"C++ 中已经定义的节点类",[90,4879,4880],{},[111,4881,155],{},[72,4883,4884,4888,4891],{},[90,4885,4368,4886],{},[111,4887,471],{},[90,4889,4890],{},"XML 使用的注册名",[90,4892,4893,2451,4895],{},[111,4894,716],{},[111,4896,159],{},[20,4898,4899,4901,4902,4905],{},[111,4900,434],{}," 是模板参数，",[111,4903,4904],{},"(\"PrintGoal\")"," 中的字符串才是函数实参。这两个名字在本例中保持一致以便阅读；C++ 类名和 XML 注册名分别负责不同的一层。",[20,4907,2507,4908,4910,4911,42,4913,4915],{},[111,4909,2349],{},"。注册时读取这个类的端口声明并保存创建方式；加载 XML 时，工厂再为每个节点实例提供 ",[111,4912,475],{},[111,4914,4253],{},"，调用前面说明的构造函数。",[366,4917,4919],{"id":4918},"从-xml-传入固定目标","从 XML 传入固定目标",[20,4921,1861,4922,1865],{},[111,4923,2001],{},[134,4925,4927],{"className":192,"code":4926,"language":194,"meta":140,"style":140},"\u003Croot main_tree_to_execute=\"MainTree\">\n    \u003CBehaviorTree ID=\"MainTree\">\n        \u003CSequence name=\"mission_sequence\">\n            \u003CCheckBattery name=\"check_battery\"\u002F>\n            \u003CPrintGoal name=\"print_goal\" goal=\"loading_area\"\u002F>\n        \u003C\u002FSequence>\n    \u003C\u002FBehaviorTree>\n\u003C\u002Froot>\n",[111,4928,4929,4943,4957,4971,4985,5005,5013,5021],{"__ignoreMap":140},[198,4930,4931,4933,4935,4937,4939,4941],{"class":200,"line":201},[198,4932,205],{"class":204},[198,4934,209],{"class":208},[198,4936,213],{"class":212},[198,4938,216],{"class":204},[198,4940,220],{"class":219},[198,4942,223],{"class":204},[198,4944,4945,4947,4949,4951,4953,4955],{"class":200,"line":226},[198,4946,229],{"class":204},[198,4948,232],{"class":208},[198,4950,235],{"class":212},[198,4952,216],{"class":204},[198,4954,220],{"class":219},[198,4956,223],{"class":204},[198,4958,4959,4961,4963,4965,4967,4969],{"class":200,"line":244},[198,4960,247],{"class":204},[198,4962,113],{"class":208},[198,4964,252],{"class":212},[198,4966,216],{"class":204},[198,4968,257],{"class":219},[198,4970,223],{"class":204},[198,4972,4973,4975,4977,4979,4981,4983],{"class":200,"line":262},[198,4974,265],{"class":204},[198,4976,268],{"class":208},[198,4978,252],{"class":212},[198,4980,216],{"class":204},[198,4982,275],{"class":219},[198,4984,278],{"class":204},[198,4986,4987,4989,4991,4993,4995,4997,4999,5001,5003],{"class":200,"line":281},[198,4988,265],{"class":204},[198,4990,155],{"class":208},[198,4992,252],{"class":212},[198,4994,216],{"class":204},[198,4996,317],{"class":219},[198,4998,296],{"class":212},[198,5000,216],{"class":204},[198,5002,609],{"class":219},[198,5004,278],{"class":204},[198,5006,5007,5009,5011],{"class":200,"line":306},[198,5008,331],{"class":204},[198,5010,113],{"class":208},[198,5012,223],{"class":204},[198,5014,5015,5017,5019],{"class":200,"line":328},[198,5016,341],{"class":204},[198,5018,232],{"class":208},[198,5020,223],{"class":204},[198,5022,5023,5025,5027],{"class":200,"line":338},[198,5024,351],{"class":204},[198,5026,209],{"class":208},[198,5028,223],{"class":204},[134,5030,5032],{"className":2016,"code":5031,"language":2018,"meta":140,"style":140},"#include \"behaviortree_cpp_v3\u002Fbt_factory.h\"\n#include \u003Ciostream>\n#include \u003Cexception>\n#include \u003Cstring>\n\nclass PrintGoal : public BT::SyncActionNode\n{\npublic:\n    PrintGoal(const std::string& name, const BT::NodeConfiguration& config)\n        : BT::SyncActionNode(name, config)\n    {}\n\n    static BT::PortsList providedPorts()\n    {\n        \u002F\u002F 节点需要一个名为 goal 的字符串输入\n        return {BT::InputPort\u003Cstd::string>(\"goal\")};\n    }\n\n    BT::NodeStatus tick() override\n    {\n        \u002F\u002F 每次执行时读取输入，读取成功后再使用\n        auto goal = getInput\u003Cstd::string>(\"goal\");\n        if (!goal)\n        {\n            throw BT::RuntimeError(\"Cannot read input [goal]: \", goal.error());\n        }\n\n        std::cout \u003C\u003C \"[PrintGoal] target=\" \u003C\u003C goal.value() \u003C\u003C '\\n';\n        return BT::NodeStatus::SUCCESS;\n    }\n};\n\n\u002F\u002F 电量检查\nBT::NodeStatus CheckBattery(BT::TreeNode&)\n{\n    \u002F\u002F 修改这里的数值，可以模拟电量变化\n    const double battery_level = 80.0;\n    std::cout \u003C\u003C \"[CheckBattery] battery=\" \u003C\u003C battery_level \u003C\u003C '\\n';\n\n    if (battery_level >= 20.0)\n    {\n        return BT::NodeStatus::SUCCESS;\n    }\n    return BT::NodeStatus::FAILURE;\n}\n\n\nint main()\n{\n    try\n    {\n        BT::BehaviorTreeFactory factory;\n        factory.registerSimpleCondition(\"CheckBattery\", CheckBattery);\n        factory.registerNodeType\u003CPrintGoal>(\"PrintGoal\");\n\n        auto tree = factory.createTreeFromFile(BT_XML_PATH);\n        const BT::NodeStatus result = tree.tickRoot();\n        std::cout \u003C\u003C \"Tree result: \" \u003C\u003C BT::toStr(result) \u003C\u003C '\\n';\n\n        return result == BT::NodeStatus::SUCCESS ? 0 : 1;\n    }\n    catch (const std::exception& error)\n    {\n        \u002F\u002F 创建树或读取输入出错时，显示原因\n        std::cerr \u003C\u003C \"Error: \" \u003C\u003C error.what() \u003C\u003C '\\n';\n        return 2;\n    }\n}\n",[111,5033,5034,5040,5046,5052,5058,5062,5078,5082,5086,5120,5128,5132,5136,5150,5154,5158,5184,5188,5192,5206,5210,5214,5238,5248,5252,5272,5276,5280,5308,5320,5324,5328,5332,5336,5358,5362,5366,5380,5404,5408,5421,5426,5439,5444,5457,5462,5467,5472,5481,5486,5491,5496,5503,5516,5533,5538,5553,5570,5601,5606,5633,5638,5655,5660,5665,5694,5703,5708],{"__ignoreMap":140},[198,5035,5036,5038],{"class":200,"line":201},[198,5037,2025],{"class":1414},[198,5039,2028],{"class":219},[198,5041,5042,5044],{"class":200,"line":226},[198,5043,2025],{"class":1414},[198,5045,2039],{"class":219},[198,5047,5048,5050],{"class":200,"line":244},[198,5049,2025],{"class":1414},[198,5051,3851],{"class":219},[198,5053,5054,5056],{"class":200,"line":262},[198,5055,2025],{"class":1414},[198,5057,3858],{"class":219},[198,5059,5060],{"class":200,"line":281},[198,5061,840],{"emptyLinePlaceholder":839},[198,5063,5064,5066,5068,5070,5072,5074,5076],{"class":200,"line":306},[198,5065,3882],{"class":1414},[198,5067,3885],{"class":212},[198,5069,3888],{"class":204},[198,5071,3891],{"class":1414},[198,5073,2112],{"class":212},[198,5075,2056],{"class":204},[198,5077,3898],{"class":212},[198,5079,5080],{"class":200,"line":328},[198,5081,2081],{"class":204},[198,5083,5084],{"class":200,"line":338},[198,5085,3907],{"class":1414},[198,5087,5088,5090,5092,5094,5096,5098,5100,5102,5104,5106,5108,5110,5112,5114,5116,5118],{"class":200,"line":348},[198,5089,3912],{"class":212},[198,5091,1502],{"class":204},[198,5093,3917],{"class":1414},[198,5095,2362],{"class":212},[198,5097,2056],{"class":204},[198,5099,2367],{"class":212},[198,5101,2074],{"class":1414},[198,5103,252],{"class":2372},[198,5105,3930],{"class":204},[198,5107,3917],{"class":1414},[198,5109,2112],{"class":212},[198,5111,2056],{"class":204},[198,5113,3939],{"class":212},[198,5115,2074],{"class":1414},[198,5117,3944],{"class":2372},[198,5119,1558],{"class":204},[198,5121,5122,5124,5126],{"class":200,"line":857},[198,5123,3951],{"class":204},[198,5125,169],{"class":212},[198,5127,3956],{"class":204},[198,5129,5130],{"class":200,"line":872},[198,5131,3961],{"class":204},[198,5133,5134],{"class":200,"line":881},[198,5135,840],{"emptyLinePlaceholder":839},[198,5137,5138,5140,5142,5144,5146,5148],{"class":200,"line":1546},[198,5139,3970],{"class":1414},[198,5141,2112],{"class":212},[198,5143,2056],{"class":204},[198,5145,2407],{"class":212},[198,5147,3979],{"class":212},[198,5149,1543],{"class":204},[198,5151,5152],{"class":200,"line":1561},[198,5153,3054],{"class":204},[198,5155,5156],{"class":200,"line":1569},[198,5157,3990],{"class":1468},[198,5159,5160,5162,5164,5166,5168,5170,5172,5174,5176,5178,5180,5182],{"class":200,"line":1583},[198,5161,3059],{"class":1414},[198,5163,3997],{"class":204},[198,5165,2053],{"class":212},[198,5167,2056],{"class":204},[198,5169,557],{"class":212},[198,5171,205],{"class":204},[198,5173,4008],{"class":212},[198,5175,2056],{"class":204},[198,5177,2367],{"class":212},[198,5179,4015],{"class":204},[198,5181,4018],{"class":219},[198,5183,4021],{"class":204},[198,5185,5186],{"class":200,"line":1591},[198,5187,3072],{"class":204},[198,5189,5190],{"class":200,"line":1600},[198,5191,840],{"emptyLinePlaceholder":839},[198,5193,5194,5196,5198,5200,5202,5204],{"class":200,"line":1605},[198,5195,2147],{"class":212},[198,5197,2056],{"class":204},[198,5199,2059],{"class":212},[198,5201,4040],{"class":212},[198,5203,4043],{"class":204},[198,5205,4046],{"class":1414},[198,5207,5208],{"class":200,"line":1611},[198,5209,3054],{"class":204},[198,5211,5212],{"class":200,"line":1635},[198,5213,4055],{"class":1468},[198,5215,5216,5218,5220,5222,5224,5226,5228,5230,5232,5234,5236],{"class":200,"line":1653},[198,5217,4060],{"class":1414},[198,5219,4063],{"class":204},[198,5221,216],{"class":1414},[198,5223,4068],{"class":212},[198,5225,205],{"class":204},[198,5227,4008],{"class":212},[198,5229,2056],{"class":204},[198,5231,2367],{"class":212},[198,5233,4015],{"class":204},[198,5235,4018],{"class":219},[198,5237,4083],{"class":204},[198,5239,5240,5242,5244,5246],{"class":200,"line":1671},[198,5241,4088],{"class":1414},[198,5243,4091],{"class":204},[198,5245,4094],{"class":1414},[198,5247,4097],{"class":204},[198,5249,5250],{"class":200,"line":1689},[198,5251,4102],{"class":204},[198,5253,5254,5256,5258,5260,5262,5264,5266,5268,5270],{"class":200,"line":1707},[198,5255,4107],{"class":1414},[198,5257,2112],{"class":212},[198,5259,2056],{"class":204},[198,5261,4114],{"class":212},[198,5263,1502],{"class":204},[198,5265,4119],{"class":219},[198,5267,4122],{"class":204},[198,5269,4125],{"class":212},[198,5271,2606],{"class":204},[198,5273,5274],{"class":200,"line":1712},[198,5275,4132],{"class":204},[198,5277,5278],{"class":200,"line":1718},[198,5279,840],{"emptyLinePlaceholder":839},[198,5281,5282,5284,5286,5288,5290,5292,5294,5296,5298,5300,5302,5304,5306],{"class":200,"line":1724},[198,5283,4141],{"class":212},[198,5285,2089],{"class":204},[198,5287,2092],{"class":1414},[198,5289,4148],{"class":219},[198,5291,2229],{"class":1414},[198,5293,4153],{"class":204},[198,5295,4156],{"class":212},[198,5297,4043],{"class":204},[198,5299,2092],{"class":1414},[198,5301,2244],{"class":219},[198,5303,2098],{"class":1203},[198,5305,2249],{"class":219},[198,5307,2104],{"class":204},[198,5309,5310,5312,5314,5316,5318],{"class":200,"line":1730},[198,5311,3059],{"class":1414},[198,5313,2112],{"class":212},[198,5315,2056],{"class":204},[198,5317,2059],{"class":212},[198,5319,2119],{"class":204},[198,5321,5322],{"class":200,"line":1736},[198,5323,3072],{"class":204},[198,5325,5326],{"class":200,"line":1742},[198,5327,4189],{"class":204},[198,5329,5330],{"class":200,"line":1747},[198,5331,840],{"emptyLinePlaceholder":839},[198,5333,5334],{"class":200,"line":1753},[198,5335,3469],{"class":1468},[198,5337,5338,5340,5342,5344,5346,5348,5350,5352,5354,5356],{"class":200,"line":1765},[198,5339,2053],{"class":212},[198,5341,2056],{"class":204},[198,5343,2059],{"class":212},[198,5345,2967],{"class":212},[198,5347,1502],{"class":204},[198,5349,2053],{"class":212},[198,5351,2056],{"class":204},[198,5353,2071],{"class":212},[198,5355,2074],{"class":1414},[198,5357,1558],{"class":204},[198,5359,5360],{"class":200,"line":1779},[198,5361,2081],{"class":204},[198,5363,5364],{"class":200,"line":1784},[198,5365,2988],{"class":1468},[198,5367,5368,5370,5372,5374,5376,5378],{"class":200,"line":1792},[198,5369,2199],{"class":1414},[198,5371,2995],{"class":1414},[198,5373,2998],{"class":204},[198,5375,216],{"class":1414},[198,5377,3003],{"class":1203},[198,5379,2104],{"class":204},[198,5381,5382,5384,5386,5388,5390,5392,5394,5396,5398,5400,5402],{"class":200,"line":1804},[198,5383,2086],{"class":212},[198,5385,2089],{"class":204},[198,5387,2092],{"class":1414},[198,5389,3016],{"class":219},[198,5391,2229],{"class":1414},[198,5393,2998],{"class":204},[198,5395,2092],{"class":1414},[198,5397,2244],{"class":219},[198,5399,2098],{"class":1203},[198,5401,2249],{"class":219},[198,5403,2104],{"class":204},[198,5405,5406],{"class":200,"line":3740},[198,5407,840],{"emptyLinePlaceholder":839},[198,5409,5411,5413,5415,5417,5419],{"class":200,"line":5410},40,[198,5412,1499],{"class":1414},[198,5414,3041],{"class":204},[198,5416,3044],{"class":1414},[198,5418,3047],{"class":1203},[198,5420,1558],{"class":204},[198,5422,5424],{"class":200,"line":5423},41,[198,5425,3054],{"class":204},[198,5427,5429,5431,5433,5435,5437],{"class":200,"line":5428},42,[198,5430,3059],{"class":1414},[198,5432,2112],{"class":212},[198,5434,2056],{"class":204},[198,5436,2059],{"class":212},[198,5438,2119],{"class":204},[198,5440,5442],{"class":200,"line":5441},43,[198,5443,3072],{"class":204},[198,5445,5447,5449,5451,5453,5455],{"class":200,"line":5446},44,[198,5448,2109],{"class":1414},[198,5450,2112],{"class":212},[198,5452,2056],{"class":204},[198,5454,2059],{"class":212},[198,5456,3085],{"class":204},[198,5458,5460],{"class":200,"line":5459},45,[198,5461,2124],{"class":204},[198,5463,5465],{"class":200,"line":5464},46,[198,5466,840],{"emptyLinePlaceholder":839},[198,5468,5470],{"class":200,"line":5469},47,[198,5471,840],{"emptyLinePlaceholder":839},[198,5473,5475,5477,5479],{"class":200,"line":5474},48,[198,5476,2133],{"class":1414},[198,5478,2136],{"class":212},[198,5480,1543],{"class":204},[198,5482,5484],{"class":200,"line":5483},49,[198,5485,2081],{"class":204},[198,5487,5489],{"class":200,"line":5488},50,[198,5490,4573],{"class":1414},[198,5492,5494],{"class":200,"line":5493},51,[198,5495,3054],{"class":204},[198,5497,5499,5501],{"class":200,"line":5498},52,[198,5500,4582],{"class":212},[198,5502,2150],{"class":204},[198,5504,5506,5508,5510,5512,5514],{"class":200,"line":5505},53,[198,5507,4589],{"class":204},[198,5509,3114],{"class":212},[198,5511,1502],{"class":204},[198,5513,3119],{"class":219},[198,5515,3122],{"class":204},[198,5517,5519,5521,5523,5525,5527,5529,5531],{"class":200,"line":5518},54,[198,5520,4602],{"class":204},[198,5522,205],{"class":1414},[198,5524,155],{"class":204},[198,5526,4609],{"class":1414},[198,5528,1502],{"class":204},[198,5530,716],{"class":219},[198,5532,4083],{"class":204},[198,5534,5536],{"class":200,"line":5535},55,[198,5537,840],{"emptyLinePlaceholder":839},[198,5539,5541,5543,5545,5547,5549,5551],{"class":200,"line":5540},56,[198,5542,4060],{"class":1414},[198,5544,2183],{"class":204},[198,5546,216],{"class":1414},[198,5548,2188],{"class":204},[198,5550,2191],{"class":212},[198,5552,2194],{"class":204},[198,5554,5556,5558,5560,5562,5564,5566,5568],{"class":200,"line":5555},57,[198,5557,4638],{"class":1414},[198,5559,2112],{"class":212},[198,5561,2204],{"class":204},[198,5563,216],{"class":1414},[198,5565,2209],{"class":204},[198,5567,2212],{"class":212},[198,5569,2215],{"class":204},[198,5571,5573,5575,5577,5579,5581,5583,5585,5587,5589,5591,5593,5595,5597,5599],{"class":200,"line":5572},58,[198,5574,4141],{"class":212},[198,5576,2089],{"class":204},[198,5578,2092],{"class":1414},[198,5580,2226],{"class":219},[198,5582,2229],{"class":1414},[198,5584,2112],{"class":212},[198,5586,2056],{"class":204},[198,5588,2236],{"class":212},[198,5590,2239],{"class":204},[198,5592,2092],{"class":1414},[198,5594,2244],{"class":219},[198,5596,2098],{"class":1203},[198,5598,2249],{"class":219},[198,5600,2104],{"class":204},[198,5602,5604],{"class":200,"line":5603},59,[198,5605,840],{"emptyLinePlaceholder":839},[198,5607,5609,5611,5613,5615,5617,5619,5621,5623,5625,5627,5629,5631],{"class":200,"line":5608},60,[198,5610,3059],{"class":1414},[198,5612,2262],{"class":204},[198,5614,2265],{"class":1414},[198,5616,2112],{"class":212},[198,5618,2056],{"class":204},[198,5620,2059],{"class":212},[198,5622,2274],{"class":204},[198,5624,2277],{"class":1414},[198,5626,2280],{"class":1203},[198,5628,2283],{"class":1414},[198,5630,2286],{"class":1203},[198,5632,2104],{"class":204},[198,5634,5636],{"class":200,"line":5635},61,[198,5637,3072],{"class":204},[198,5639,5641,5643,5645,5647,5649,5651,5653],{"class":200,"line":5640},62,[198,5642,4719],{"class":1414},[198,5644,4091],{"class":204},[198,5646,3917],{"class":1414},[198,5648,2362],{"class":212},[198,5650,4728],{"class":204},[198,5652,2074],{"class":1414},[198,5654,4733],{"class":204},[198,5656,5658],{"class":200,"line":5657},63,[198,5659,3054],{"class":204},[198,5661,5663],{"class":200,"line":5662},64,[198,5664,4742],{"class":1468},[198,5666,5668,5670,5672,5674,5676,5678,5680,5682,5684,5686,5688,5690,5692],{"class":200,"line":5667},65,[198,5669,4141],{"class":212},[198,5671,4749],{"class":204},[198,5673,2092],{"class":1414},[198,5675,4754],{"class":219},[198,5677,2229],{"class":1414},[198,5679,4759],{"class":204},[198,5681,4762],{"class":212},[198,5683,4043],{"class":204},[198,5685,2092],{"class":1414},[198,5687,2244],{"class":219},[198,5689,2098],{"class":1203},[198,5691,2249],{"class":219},[198,5693,2104],{"class":204},[198,5695,5697,5699,5701],{"class":200,"line":5696},66,[198,5698,3059],{"class":1414},[198,5700,4781],{"class":1203},[198,5702,2104],{"class":204},[198,5704,5706],{"class":200,"line":5705},67,[198,5707,3072],{"class":204},[198,5709,5711],{"class":200,"line":5710},68,[198,5712,2124],{"class":204},[20,5714,5715,5716,3370,5718,48],{},"保存修改，在 CMake Tools 中点击 ",[1005,5717,2831],{},[1005,5719,2843],{},[20,5721,2866],{},[134,5723,5726],{"className":5724,"code":5725,"language":139,"meta":140},[137],"[CheckBattery] battery=80\n[PrintGoal] target=loading_area\nTree result: SUCCESS\n",[111,5727,5725],{"__ignoreMap":140},[20,5729,5730,5731,3766,5734,5737,5738,5740,5741,5743],{},"现在只把 XML 中的 ",[111,5732,5733],{},"goal=\"loading_area\"",[111,5735,5736],{},"goal=\"charging_station\"","，保存 ",[111,5739,2001],{}," 后再次点击 ",[1005,5742,2843],{},"。这一步没有改 C++，不需要手动重新 Build。",[20,5745,5746,5747,5750],{},"打印目标会变为 ",[111,5748,5749],{},"charging_station","。这样就把节点的具体实现和本次配置的目标分开了。",[20,5752,5753],{},[1217,5754],{"alt":140,"src":5755},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792284077213-18c7dfb5.webp",[20,5757,5758],{},[1217,5759],{"alt":140,"src":5760},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792284103043-f84dc70f.webp",[56,5762],{},[12,5764,5766],{"id":5765},"再增加输出节点让两个动作传递目标","再增加输出节点，让两个动作传递目标",[20,5768,5769,5770,30,5772,42,5774,5776],{},"固定值已经能读到了，现在让前面的节点产生目标，后面的节点通过 Blackboard 读取。保留上一轮的 ",[111,5771,155],{},[111,5773,3245],{},[111,5775,2950],{},"，只增加一个类和一行注册。",[366,5778,5780],{"id":5779},"增加-selectgoal-类并注册","增加 SelectGoal 类并注册",[20,5782,5783,5784,5786],{},"在 ",[111,5785,155],{}," 类前面加入：",[134,5788,5790],{"className":2016,"code":5789,"language":2018,"meta":140,"style":140},"class SelectGoal : public BT::SyncActionNode\n{\npublic:\n    SelectGoal(const std::string& name, const BT::NodeConfiguration& config)\n        : BT::SyncActionNode(name, config)\n    {}\n\n    static BT::PortsList providedPorts()\n    {\n        \u002F\u002F 节点通过 goal 输出选定的目标\n        return {BT::OutputPort\u003Cstd::string>(\"goal\")};\n    }\n\n    BT::NodeStatus tick() override\n    {\n        const std::string goal = \"loading_area\";\n        setOutput\u003Cstd::string>(\"goal\", goal);\n        std::cout \u003C\u003C \"[SelectGoal] goal=\" \u003C\u003C goal \u003C\u003C '\\n';\n        return BT::NodeStatus::SUCCESS;\n    }\n};\n",[111,5791,5792,5809,5813,5817,5852,5860,5864,5868,5882,5886,5891,5917,5921,5925,5939,5943,5959,5979,6004,6016,6020],{"__ignoreMap":140},[198,5793,5794,5796,5799,5801,5803,5805,5807],{"class":200,"line":201},[198,5795,3882],{"class":1414},[198,5797,5798],{"class":212}," SelectGoal",[198,5800,3888],{"class":204},[198,5802,3891],{"class":1414},[198,5804,2112],{"class":212},[198,5806,2056],{"class":204},[198,5808,3898],{"class":212},[198,5810,5811],{"class":200,"line":226},[198,5812,2081],{"class":204},[198,5814,5815],{"class":200,"line":244},[198,5816,3907],{"class":1414},[198,5818,5819,5822,5824,5826,5828,5830,5832,5834,5836,5838,5840,5842,5844,5846,5848,5850],{"class":200,"line":262},[198,5820,5821],{"class":212},"    SelectGoal",[198,5823,1502],{"class":204},[198,5825,3917],{"class":1414},[198,5827,2362],{"class":212},[198,5829,2056],{"class":204},[198,5831,2367],{"class":212},[198,5833,2074],{"class":1414},[198,5835,252],{"class":2372},[198,5837,3930],{"class":204},[198,5839,3917],{"class":1414},[198,5841,2112],{"class":212},[198,5843,2056],{"class":204},[198,5845,3939],{"class":212},[198,5847,2074],{"class":1414},[198,5849,3944],{"class":2372},[198,5851,1558],{"class":204},[198,5853,5854,5856,5858],{"class":200,"line":281},[198,5855,3951],{"class":204},[198,5857,169],{"class":212},[198,5859,3956],{"class":204},[198,5861,5862],{"class":200,"line":306},[198,5863,3961],{"class":204},[198,5865,5866],{"class":200,"line":328},[198,5867,840],{"emptyLinePlaceholder":839},[198,5869,5870,5872,5874,5876,5878,5880],{"class":200,"line":338},[198,5871,3970],{"class":1414},[198,5873,2112],{"class":212},[198,5875,2056],{"class":204},[198,5877,2407],{"class":212},[198,5879,3979],{"class":212},[198,5881,1543],{"class":204},[198,5883,5884],{"class":200,"line":348},[198,5885,3054],{"class":204},[198,5887,5888],{"class":200,"line":857},[198,5889,5890],{"class":1468},"        \u002F\u002F 节点通过 goal 输出选定的目标\n",[198,5892,5893,5895,5897,5899,5901,5903,5905,5907,5909,5911,5913,5915],{"class":200,"line":872},[198,5894,3059],{"class":1414},[198,5896,3997],{"class":204},[198,5898,2053],{"class":212},[198,5900,2056],{"class":204},[198,5902,530],{"class":212},[198,5904,205],{"class":204},[198,5906,4008],{"class":212},[198,5908,2056],{"class":204},[198,5910,2367],{"class":212},[198,5912,4015],{"class":204},[198,5914,4018],{"class":219},[198,5916,4021],{"class":204},[198,5918,5919],{"class":200,"line":881},[198,5920,3072],{"class":204},[198,5922,5923],{"class":200,"line":1546},[198,5924,840],{"emptyLinePlaceholder":839},[198,5926,5927,5929,5931,5933,5935,5937],{"class":200,"line":1561},[198,5928,2147],{"class":212},[198,5930,2056],{"class":204},[198,5932,2059],{"class":212},[198,5934,4040],{"class":212},[198,5936,4043],{"class":204},[198,5938,4046],{"class":1414},[198,5940,5941],{"class":200,"line":1569},[198,5942,3054],{"class":204},[198,5944,5945,5947,5949,5952,5954,5957],{"class":200,"line":1583},[198,5946,4638],{"class":1414},[198,5948,2362],{"class":212},[198,5950,5951],{"class":204},"::string goal ",[198,5953,216],{"class":1414},[198,5955,5956],{"class":219}," \"loading_area\"",[198,5958,2104],{"class":204},[198,5960,5961,5964,5966,5968,5970,5972,5974,5976],{"class":200,"line":1591},[198,5962,5963],{"class":212},"        setOutput",[198,5965,205],{"class":204},[198,5967,4008],{"class":212},[198,5969,2056],{"class":204},[198,5971,2367],{"class":212},[198,5973,4015],{"class":204},[198,5975,4018],{"class":219},[198,5977,5978],{"class":204},", goal);\n",[198,5980,5981,5983,5985,5987,5990,5992,5994,5996,5998,6000,6002],{"class":200,"line":1600},[198,5982,4141],{"class":212},[198,5984,2089],{"class":204},[198,5986,2092],{"class":1414},[198,5988,5989],{"class":219}," \"[SelectGoal] goal=\"",[198,5991,2229],{"class":1414},[198,5993,4063],{"class":204},[198,5995,2092],{"class":1414},[198,5997,2244],{"class":219},[198,5999,2098],{"class":1203},[198,6001,2249],{"class":219},[198,6003,2104],{"class":204},[198,6005,6006,6008,6010,6012,6014],{"class":200,"line":1605},[198,6007,3059],{"class":1414},[198,6009,2112],{"class":212},[198,6011,2056],{"class":204},[198,6013,2059],{"class":212},[198,6015,2119],{"class":204},[198,6017,6018],{"class":200,"line":1611},[198,6019,3072],{"class":204},[198,6021,6022],{"class":200,"line":1635},[198,6023,4189],{"class":204},[20,6025,5783,6026,6028,6029,6031],{},[111,6027,2950],{}," 中、注册 ",[111,6030,155],{}," 的前面增加：",[134,6033,6035],{"className":2016,"code":6034,"language":2018,"meta":140,"style":140},"factory.registerNodeType\u003CSelectGoal>(\"SelectGoal\");\n",[111,6036,6037],{"__ignoreMap":140},[198,6038,6039,6042,6044,6046,6048,6050,6053],{"class":200,"line":201},[198,6040,6041],{"class":204},"factory.registerNodeType",[198,6043,205],{"class":1414},[198,6045,286],{"class":204},[198,6047,4609],{"class":1414},[198,6049,1502],{"class":204},[198,6051,6052],{"class":219},"\"SelectGoal\"",[198,6054,4083],{"class":204},[20,6056,6057,6058,6060],{},"工厂已有 ",[111,6059,113],{}," 等内置节点；本轮只是多注册一种自定义动作类型，不需要修改构建配置。",[2332,6062,6064,2672,6067],{"id":6063},"outputportt-与-setoutput",[111,6065,6066],{},"OutputPort\u003CT>()",[111,6068,658],{},[20,6070,6071,6074,6075,6077],{},[111,6072,6073],{},"BT::OutputPort\u003Cstd::string>(\"goal\")"," 声明字符串输出端口：模板参数指定值类型，第一个函数参数是端口名，可选的第二个参数是说明文字。返回值同样是用于组成 ",[111,6076,2488],{}," 的端口键值对。",[20,6079,6080],{},"写入使用下面的接口：",[134,6082,6084],{"className":2016,"code":6083,"language":2018,"meta":140,"style":140},"template \u003Ctypename T>\nBT::Result setOutput(const std::string& key, const T& value);\n",[111,6085,6086,6098],{"__ignoreMap":140},[198,6087,6088,6090,6092,6094,6096],{"class":200,"line":201},[198,6089,4413],{"class":1414},[198,6091,4416],{"class":204},[198,6093,4419],{"class":1414},[198,6095,4422],{"class":212},[198,6097,223],{"class":204},[198,6099,6100,6102,6104,6107,6110,6112,6114,6116,6118,6120,6122,6124,6126,6128,6130,6132,6135],{"class":200,"line":226},[198,6101,2053],{"class":212},[198,6103,2056],{"class":204},[198,6105,6106],{"class":212},"Result",[198,6108,6109],{"class":212}," setOutput",[198,6111,1502],{"class":204},[198,6113,3917],{"class":1414},[198,6115,2362],{"class":212},[198,6117,2056],{"class":204},[198,6119,2367],{"class":212},[198,6121,2074],{"class":1414},[198,6123,4459],{"class":2372},[198,6125,3930],{"class":204},[198,6127,3917],{"class":1414},[198,6129,4422],{"class":212},[198,6131,2074],{"class":1414},[198,6133,6134],{"class":2372}," value",[198,6136,4083],{"class":204},[66,6138,6139,6149],{},[69,6140,6141],{},[72,6142,6143,6145,6147],{},[75,6144,2428],{},[75,6146,381],{},[75,6148,4866],{},[85,6150,6151,6164],{},[72,6152,6153,6157,6160],{},[90,6154,2441,6155],{},[111,6156,4487],{},[90,6158,6159],{},"当前节点自己的输出端口名",[90,6161,6162],{},[111,6163,4018],{},[72,6165,6166,6170,6175],{},[90,6167,2459,6168],{},[111,6169,4156],{},[90,6171,6172,6173],{},"要写出的 C++ 值，类型为 ",[111,6174,4438],{},[90,6176,3362,6177,6179,6180],{},[111,6178,609],{}," 的字符串变量 ",[111,6181,460],{},[20,6183,6184,6185,6188,6189,6192,6193,6196],{},"XML 会把这个输出端口连接到 Blackboard 条目；节点实现仍然只使用自己的端口名。返回值为 ",[111,6186,6187],{},"BT::Result","，成功时可判真，失败时可判假并调用 ",[111,6190,6191],{},".error()"," 查看原因。本篇先展示正常写入路径，下一篇会用 ",[111,6194,6195],{},"written"," 变量显式检查这个结果。",[366,6198,6200],{"id":6199},"把固定值改成-blackboard-映射","把固定值改成 Blackboard 映射",[20,6202,1861,6203,6205],{},[111,6204,2001],{}," 整个替换为下面的最终版本：",[134,6207,6208],{"className":192,"code":193,"language":194,"meta":140,"style":140},[111,6209,6210,6224,6238,6252,6266,6286,6306,6314,6322],{"__ignoreMap":140},[198,6211,6212,6214,6216,6218,6220,6222],{"class":200,"line":201},[198,6213,205],{"class":204},[198,6215,209],{"class":208},[198,6217,213],{"class":212},[198,6219,216],{"class":204},[198,6221,220],{"class":219},[198,6223,223],{"class":204},[198,6225,6226,6228,6230,6232,6234,6236],{"class":200,"line":226},[198,6227,229],{"class":204},[198,6229,232],{"class":208},[198,6231,235],{"class":212},[198,6233,216],{"class":204},[198,6235,220],{"class":219},[198,6237,223],{"class":204},[198,6239,6240,6242,6244,6246,6248,6250],{"class":200,"line":244},[198,6241,247],{"class":204},[198,6243,113],{"class":208},[198,6245,252],{"class":212},[198,6247,216],{"class":204},[198,6249,257],{"class":219},[198,6251,223],{"class":204},[198,6253,6254,6256,6258,6260,6262,6264],{"class":200,"line":262},[198,6255,265],{"class":204},[198,6257,268],{"class":208},[198,6259,252],{"class":212},[198,6261,216],{"class":204},[198,6263,275],{"class":219},[198,6265,278],{"class":204},[198,6267,6268,6270,6272,6274,6276,6278,6280,6282,6284],{"class":200,"line":281},[198,6269,265],{"class":204},[198,6271,286],{"class":208},[198,6273,252],{"class":212},[198,6275,216],{"class":204},[198,6277,293],{"class":219},[198,6279,296],{"class":212},[198,6281,216],{"class":204},[198,6283,301],{"class":219},[198,6285,278],{"class":204},[198,6287,6288,6290,6292,6294,6296,6298,6300,6302,6304],{"class":200,"line":306},[198,6289,265],{"class":204},[198,6291,155],{"class":208},[198,6293,252],{"class":212},[198,6295,216],{"class":204},[198,6297,317],{"class":219},[198,6299,296],{"class":212},[198,6301,216],{"class":204},[198,6303,301],{"class":219},[198,6305,278],{"class":204},[198,6307,6308,6310,6312],{"class":200,"line":328},[198,6309,331],{"class":204},[198,6311,113],{"class":208},[198,6313,223],{"class":204},[198,6315,6316,6318,6320],{"class":200,"line":338},[198,6317,341],{"class":204},[198,6319,232],{"class":208},[198,6321,223],{"class":204},[198,6323,6324,6326,6328],{"class":200,"line":348},[198,6325,351],{"class":204},[198,6327,209],{"class":208},[198,6329,223],{"class":204},[134,6331,6333],{"className":2016,"code":6332,"language":2018,"meta":140,"style":140},"#include \"behaviortree_cpp_v3\u002Fbt_factory.h\"\n#include \u003Ciostream>\n#include \u003Cexception>\n#include \u003Cstring>\n\nclass SelectGoal : public BT::SyncActionNode\n{\npublic:\n    SelectGoal(const std::string& name, const BT::NodeConfiguration& config)\n        : BT::SyncActionNode(name, config)\n    {}\n\n    static BT::PortsList providedPorts()\n    {\n        \u002F\u002F 节点通过 goal 输出选定的目标\n        return {BT::OutputPort\u003Cstd::string>(\"goal\")};\n    }\n\n    BT::NodeStatus tick() override\n    {\n        const std::string goal = \"loading_area\";\n        setOutput\u003Cstd::string>(\"goal\", goal);\n        std::cout \u003C\u003C \"[SelectGoal] goal=\" \u003C\u003C goal \u003C\u003C '\\n';\n        return BT::NodeStatus::SUCCESS;\n    }\n};\n\n\nclass PrintGoal : public BT::SyncActionNode\n{\npublic:\n    PrintGoal(const std::string& name, const BT::NodeConfiguration& config)\n        : BT::SyncActionNode(name, config)\n    {}\n\n    static BT::PortsList providedPorts()\n    {\n        \u002F\u002F 节点需要一个名为 goal 的字符串输入\n        return {BT::InputPort\u003Cstd::string>(\"goal\")};\n    }\n\n    BT::NodeStatus tick() override\n    {\n        \u002F\u002F 每次执行时读取输入，读取成功后再使用\n        auto goal = getInput\u003Cstd::string>(\"goal\");\n        if (!goal)\n        {\n            throw BT::RuntimeError(\"Cannot read input [goal]: \", goal.error());\n        }\n\n        std::cout \u003C\u003C \"[PrintGoal] target=\" \u003C\u003C goal.value() \u003C\u003C '\\n';\n        return BT::NodeStatus::SUCCESS;\n    }\n};\n\n\u002F\u002F 电量检查\nBT::NodeStatus CheckBattery(BT::TreeNode&)\n{\n    \u002F\u002F 修改这里的数值，可以模拟电量变化\n    const double battery_level = 80.0;\n    std::cout \u003C\u003C \"[CheckBattery] battery=\" \u003C\u003C battery_level \u003C\u003C '\\n';\n\n    if (battery_level >= 20.0)\n    {\n        return BT::NodeStatus::SUCCESS;\n    }\n    return BT::NodeStatus::FAILURE;\n}\n\n\nint main()\n{\n    try\n    {\n        BT::BehaviorTreeFactory factory;\n        factory.registerSimpleCondition(\"CheckBattery\", CheckBattery);\n        factory.registerNodeType\u003CSelectGoal>(\"SelectGoal\");\n        factory.registerNodeType\u003CPrintGoal>(\"PrintGoal\");\n\n        auto tree = factory.createTreeFromFile(BT_XML_PATH);\n        const BT::NodeStatus result = tree.tickRoot();\n        std::cout \u003C\u003C \"Tree result: \" \u003C\u003C BT::toStr(result) \u003C\u003C '\\n';\n\n        return result == BT::NodeStatus::SUCCESS ? 0 : 1;\n    }\n    catch (const std::exception& error)\n    {\n        \u002F\u002F 创建树或读取输入出错时，显示原因\n        std::cerr \u003C\u003C \"Error: \" \u003C\u003C error.what() \u003C\u003C '\\n';\n        return 2;\n    }\n}\n",[111,6334,6335,6341,6347,6353,6359,6363,6379,6383,6387,6421,6429,6433,6437,6451,6455,6459,6485,6489,6493,6507,6511,6525,6543,6567,6579,6583,6587,6591,6595,6611,6615,6619,6653,6661,6665,6669,6683,6687,6691,6717,6721,6725,6739,6743,6747,6771,6781,6785,6805,6809,6813,6841,6853,6857,6861,6865,6869,6891,6895,6899,6913,6937,6941,6953,6957,6969,6973,6985,6989,6994,6999,7008,7013,7018,7023,7030,7043,7060,7077,7082,7097,7114,7145,7150,7177,7182,7199,7204,7209,7238,7247,7252],{"__ignoreMap":140},[198,6336,6337,6339],{"class":200,"line":201},[198,6338,2025],{"class":1414},[198,6340,2028],{"class":219},[198,6342,6343,6345],{"class":200,"line":226},[198,6344,2025],{"class":1414},[198,6346,2039],{"class":219},[198,6348,6349,6351],{"class":200,"line":244},[198,6350,2025],{"class":1414},[198,6352,3851],{"class":219},[198,6354,6355,6357],{"class":200,"line":262},[198,6356,2025],{"class":1414},[198,6358,3858],{"class":219},[198,6360,6361],{"class":200,"line":281},[198,6362,840],{"emptyLinePlaceholder":839},[198,6364,6365,6367,6369,6371,6373,6375,6377],{"class":200,"line":306},[198,6366,3882],{"class":1414},[198,6368,5798],{"class":212},[198,6370,3888],{"class":204},[198,6372,3891],{"class":1414},[198,6374,2112],{"class":212},[198,6376,2056],{"class":204},[198,6378,3898],{"class":212},[198,6380,6381],{"class":200,"line":328},[198,6382,2081],{"class":204},[198,6384,6385],{"class":200,"line":338},[198,6386,3907],{"class":1414},[198,6388,6389,6391,6393,6395,6397,6399,6401,6403,6405,6407,6409,6411,6413,6415,6417,6419],{"class":200,"line":348},[198,6390,5821],{"class":212},[198,6392,1502],{"class":204},[198,6394,3917],{"class":1414},[198,6396,2362],{"class":212},[198,6398,2056],{"class":204},[198,6400,2367],{"class":212},[198,6402,2074],{"class":1414},[198,6404,252],{"class":2372},[198,6406,3930],{"class":204},[198,6408,3917],{"class":1414},[198,6410,2112],{"class":212},[198,6412,2056],{"class":204},[198,6414,3939],{"class":212},[198,6416,2074],{"class":1414},[198,6418,3944],{"class":2372},[198,6420,1558],{"class":204},[198,6422,6423,6425,6427],{"class":200,"line":857},[198,6424,3951],{"class":204},[198,6426,169],{"class":212},[198,6428,3956],{"class":204},[198,6430,6431],{"class":200,"line":872},[198,6432,3961],{"class":204},[198,6434,6435],{"class":200,"line":881},[198,6436,840],{"emptyLinePlaceholder":839},[198,6438,6439,6441,6443,6445,6447,6449],{"class":200,"line":1546},[198,6440,3970],{"class":1414},[198,6442,2112],{"class":212},[198,6444,2056],{"class":204},[198,6446,2407],{"class":212},[198,6448,3979],{"class":212},[198,6450,1543],{"class":204},[198,6452,6453],{"class":200,"line":1561},[198,6454,3054],{"class":204},[198,6456,6457],{"class":200,"line":1569},[198,6458,5890],{"class":1468},[198,6460,6461,6463,6465,6467,6469,6471,6473,6475,6477,6479,6481,6483],{"class":200,"line":1583},[198,6462,3059],{"class":1414},[198,6464,3997],{"class":204},[198,6466,2053],{"class":212},[198,6468,2056],{"class":204},[198,6470,530],{"class":212},[198,6472,205],{"class":204},[198,6474,4008],{"class":212},[198,6476,2056],{"class":204},[198,6478,2367],{"class":212},[198,6480,4015],{"class":204},[198,6482,4018],{"class":219},[198,6484,4021],{"class":204},[198,6486,6487],{"class":200,"line":1591},[198,6488,3072],{"class":204},[198,6490,6491],{"class":200,"line":1600},[198,6492,840],{"emptyLinePlaceholder":839},[198,6494,6495,6497,6499,6501,6503,6505],{"class":200,"line":1605},[198,6496,2147],{"class":212},[198,6498,2056],{"class":204},[198,6500,2059],{"class":212},[198,6502,4040],{"class":212},[198,6504,4043],{"class":204},[198,6506,4046],{"class":1414},[198,6508,6509],{"class":200,"line":1611},[198,6510,3054],{"class":204},[198,6512,6513,6515,6517,6519,6521,6523],{"class":200,"line":1635},[198,6514,4638],{"class":1414},[198,6516,2362],{"class":212},[198,6518,5951],{"class":204},[198,6520,216],{"class":1414},[198,6522,5956],{"class":219},[198,6524,2104],{"class":204},[198,6526,6527,6529,6531,6533,6535,6537,6539,6541],{"class":200,"line":1653},[198,6528,5963],{"class":212},[198,6530,205],{"class":204},[198,6532,4008],{"class":212},[198,6534,2056],{"class":204},[198,6536,2367],{"class":212},[198,6538,4015],{"class":204},[198,6540,4018],{"class":219},[198,6542,5978],{"class":204},[198,6544,6545,6547,6549,6551,6553,6555,6557,6559,6561,6563,6565],{"class":200,"line":1671},[198,6546,4141],{"class":212},[198,6548,2089],{"class":204},[198,6550,2092],{"class":1414},[198,6552,5989],{"class":219},[198,6554,2229],{"class":1414},[198,6556,4063],{"class":204},[198,6558,2092],{"class":1414},[198,6560,2244],{"class":219},[198,6562,2098],{"class":1203},[198,6564,2249],{"class":219},[198,6566,2104],{"class":204},[198,6568,6569,6571,6573,6575,6577],{"class":200,"line":1689},[198,6570,3059],{"class":1414},[198,6572,2112],{"class":212},[198,6574,2056],{"class":204},[198,6576,2059],{"class":212},[198,6578,2119],{"class":204},[198,6580,6581],{"class":200,"line":1707},[198,6582,3072],{"class":204},[198,6584,6585],{"class":200,"line":1712},[198,6586,4189],{"class":204},[198,6588,6589],{"class":200,"line":1718},[198,6590,840],{"emptyLinePlaceholder":839},[198,6592,6593],{"class":200,"line":1724},[198,6594,840],{"emptyLinePlaceholder":839},[198,6596,6597,6599,6601,6603,6605,6607,6609],{"class":200,"line":1730},[198,6598,3882],{"class":1414},[198,6600,3885],{"class":212},[198,6602,3888],{"class":204},[198,6604,3891],{"class":1414},[198,6606,2112],{"class":212},[198,6608,2056],{"class":204},[198,6610,3898],{"class":212},[198,6612,6613],{"class":200,"line":1736},[198,6614,2081],{"class":204},[198,6616,6617],{"class":200,"line":1742},[198,6618,3907],{"class":1414},[198,6620,6621,6623,6625,6627,6629,6631,6633,6635,6637,6639,6641,6643,6645,6647,6649,6651],{"class":200,"line":1747},[198,6622,3912],{"class":212},[198,6624,1502],{"class":204},[198,6626,3917],{"class":1414},[198,6628,2362],{"class":212},[198,6630,2056],{"class":204},[198,6632,2367],{"class":212},[198,6634,2074],{"class":1414},[198,6636,252],{"class":2372},[198,6638,3930],{"class":204},[198,6640,3917],{"class":1414},[198,6642,2112],{"class":212},[198,6644,2056],{"class":204},[198,6646,3939],{"class":212},[198,6648,2074],{"class":1414},[198,6650,3944],{"class":2372},[198,6652,1558],{"class":204},[198,6654,6655,6657,6659],{"class":200,"line":1753},[198,6656,3951],{"class":204},[198,6658,169],{"class":212},[198,6660,3956],{"class":204},[198,6662,6663],{"class":200,"line":1765},[198,6664,3961],{"class":204},[198,6666,6667],{"class":200,"line":1779},[198,6668,840],{"emptyLinePlaceholder":839},[198,6670,6671,6673,6675,6677,6679,6681],{"class":200,"line":1784},[198,6672,3970],{"class":1414},[198,6674,2112],{"class":212},[198,6676,2056],{"class":204},[198,6678,2407],{"class":212},[198,6680,3979],{"class":212},[198,6682,1543],{"class":204},[198,6684,6685],{"class":200,"line":1792},[198,6686,3054],{"class":204},[198,6688,6689],{"class":200,"line":1804},[198,6690,3990],{"class":1468},[198,6692,6693,6695,6697,6699,6701,6703,6705,6707,6709,6711,6713,6715],{"class":200,"line":3740},[198,6694,3059],{"class":1414},[198,6696,3997],{"class":204},[198,6698,2053],{"class":212},[198,6700,2056],{"class":204},[198,6702,557],{"class":212},[198,6704,205],{"class":204},[198,6706,4008],{"class":212},[198,6708,2056],{"class":204},[198,6710,2367],{"class":212},[198,6712,4015],{"class":204},[198,6714,4018],{"class":219},[198,6716,4021],{"class":204},[198,6718,6719],{"class":200,"line":5410},[198,6720,3072],{"class":204},[198,6722,6723],{"class":200,"line":5423},[198,6724,840],{"emptyLinePlaceholder":839},[198,6726,6727,6729,6731,6733,6735,6737],{"class":200,"line":5428},[198,6728,2147],{"class":212},[198,6730,2056],{"class":204},[198,6732,2059],{"class":212},[198,6734,4040],{"class":212},[198,6736,4043],{"class":204},[198,6738,4046],{"class":1414},[198,6740,6741],{"class":200,"line":5441},[198,6742,3054],{"class":204},[198,6744,6745],{"class":200,"line":5446},[198,6746,4055],{"class":1468},[198,6748,6749,6751,6753,6755,6757,6759,6761,6763,6765,6767,6769],{"class":200,"line":5459},[198,6750,4060],{"class":1414},[198,6752,4063],{"class":204},[198,6754,216],{"class":1414},[198,6756,4068],{"class":212},[198,6758,205],{"class":204},[198,6760,4008],{"class":212},[198,6762,2056],{"class":204},[198,6764,2367],{"class":212},[198,6766,4015],{"class":204},[198,6768,4018],{"class":219},[198,6770,4083],{"class":204},[198,6772,6773,6775,6777,6779],{"class":200,"line":5464},[198,6774,4088],{"class":1414},[198,6776,4091],{"class":204},[198,6778,4094],{"class":1414},[198,6780,4097],{"class":204},[198,6782,6783],{"class":200,"line":5469},[198,6784,4102],{"class":204},[198,6786,6787,6789,6791,6793,6795,6797,6799,6801,6803],{"class":200,"line":5474},[198,6788,4107],{"class":1414},[198,6790,2112],{"class":212},[198,6792,2056],{"class":204},[198,6794,4114],{"class":212},[198,6796,1502],{"class":204},[198,6798,4119],{"class":219},[198,6800,4122],{"class":204},[198,6802,4125],{"class":212},[198,6804,2606],{"class":204},[198,6806,6807],{"class":200,"line":5483},[198,6808,4132],{"class":204},[198,6810,6811],{"class":200,"line":5488},[198,6812,840],{"emptyLinePlaceholder":839},[198,6814,6815,6817,6819,6821,6823,6825,6827,6829,6831,6833,6835,6837,6839],{"class":200,"line":5493},[198,6816,4141],{"class":212},[198,6818,2089],{"class":204},[198,6820,2092],{"class":1414},[198,6822,4148],{"class":219},[198,6824,2229],{"class":1414},[198,6826,4153],{"class":204},[198,6828,4156],{"class":212},[198,6830,4043],{"class":204},[198,6832,2092],{"class":1414},[198,6834,2244],{"class":219},[198,6836,2098],{"class":1203},[198,6838,2249],{"class":219},[198,6840,2104],{"class":204},[198,6842,6843,6845,6847,6849,6851],{"class":200,"line":5498},[198,6844,3059],{"class":1414},[198,6846,2112],{"class":212},[198,6848,2056],{"class":204},[198,6850,2059],{"class":212},[198,6852,2119],{"class":204},[198,6854,6855],{"class":200,"line":5505},[198,6856,3072],{"class":204},[198,6858,6859],{"class":200,"line":5518},[198,6860,4189],{"class":204},[198,6862,6863],{"class":200,"line":5535},[198,6864,840],{"emptyLinePlaceholder":839},[198,6866,6867],{"class":200,"line":5540},[198,6868,3469],{"class":1468},[198,6870,6871,6873,6875,6877,6879,6881,6883,6885,6887,6889],{"class":200,"line":5555},[198,6872,2053],{"class":212},[198,6874,2056],{"class":204},[198,6876,2059],{"class":212},[198,6878,2967],{"class":212},[198,6880,1502],{"class":204},[198,6882,2053],{"class":212},[198,6884,2056],{"class":204},[198,6886,2071],{"class":212},[198,6888,2074],{"class":1414},[198,6890,1558],{"class":204},[198,6892,6893],{"class":200,"line":5572},[198,6894,2081],{"class":204},[198,6896,6897],{"class":200,"line":5603},[198,6898,2988],{"class":1468},[198,6900,6901,6903,6905,6907,6909,6911],{"class":200,"line":5608},[198,6902,2199],{"class":1414},[198,6904,2995],{"class":1414},[198,6906,2998],{"class":204},[198,6908,216],{"class":1414},[198,6910,3003],{"class":1203},[198,6912,2104],{"class":204},[198,6914,6915,6917,6919,6921,6923,6925,6927,6929,6931,6933,6935],{"class":200,"line":5635},[198,6916,2086],{"class":212},[198,6918,2089],{"class":204},[198,6920,2092],{"class":1414},[198,6922,3016],{"class":219},[198,6924,2229],{"class":1414},[198,6926,2998],{"class":204},[198,6928,2092],{"class":1414},[198,6930,2244],{"class":219},[198,6932,2098],{"class":1203},[198,6934,2249],{"class":219},[198,6936,2104],{"class":204},[198,6938,6939],{"class":200,"line":5640},[198,6940,840],{"emptyLinePlaceholder":839},[198,6942,6943,6945,6947,6949,6951],{"class":200,"line":5657},[198,6944,1499],{"class":1414},[198,6946,3041],{"class":204},[198,6948,3044],{"class":1414},[198,6950,3047],{"class":1203},[198,6952,1558],{"class":204},[198,6954,6955],{"class":200,"line":5662},[198,6956,3054],{"class":204},[198,6958,6959,6961,6963,6965,6967],{"class":200,"line":5667},[198,6960,3059],{"class":1414},[198,6962,2112],{"class":212},[198,6964,2056],{"class":204},[198,6966,2059],{"class":212},[198,6968,2119],{"class":204},[198,6970,6971],{"class":200,"line":5696},[198,6972,3072],{"class":204},[198,6974,6975,6977,6979,6981,6983],{"class":200,"line":5705},[198,6976,2109],{"class":1414},[198,6978,2112],{"class":212},[198,6980,2056],{"class":204},[198,6982,2059],{"class":212},[198,6984,3085],{"class":204},[198,6986,6987],{"class":200,"line":5710},[198,6988,2124],{"class":204},[198,6990,6992],{"class":200,"line":6991},69,[198,6993,840],{"emptyLinePlaceholder":839},[198,6995,6997],{"class":200,"line":6996},70,[198,6998,840],{"emptyLinePlaceholder":839},[198,7000,7002,7004,7006],{"class":200,"line":7001},71,[198,7003,2133],{"class":1414},[198,7005,2136],{"class":212},[198,7007,1543],{"class":204},[198,7009,7011],{"class":200,"line":7010},72,[198,7012,2081],{"class":204},[198,7014,7016],{"class":200,"line":7015},73,[198,7017,4573],{"class":1414},[198,7019,7021],{"class":200,"line":7020},74,[198,7022,3054],{"class":204},[198,7024,7026,7028],{"class":200,"line":7025},75,[198,7027,4582],{"class":212},[198,7029,2150],{"class":204},[198,7031,7033,7035,7037,7039,7041],{"class":200,"line":7032},76,[198,7034,4589],{"class":204},[198,7036,3114],{"class":212},[198,7038,1502],{"class":204},[198,7040,3119],{"class":219},[198,7042,3122],{"class":204},[198,7044,7046,7048,7050,7052,7054,7056,7058],{"class":200,"line":7045},77,[198,7047,4602],{"class":204},[198,7049,205],{"class":1414},[198,7051,286],{"class":204},[198,7053,4609],{"class":1414},[198,7055,1502],{"class":204},[198,7057,6052],{"class":219},[198,7059,4083],{"class":204},[198,7061,7063,7065,7067,7069,7071,7073,7075],{"class":200,"line":7062},78,[198,7064,4602],{"class":204},[198,7066,205],{"class":1414},[198,7068,155],{"class":204},[198,7070,4609],{"class":1414},[198,7072,1502],{"class":204},[198,7074,716],{"class":219},[198,7076,4083],{"class":204},[198,7078,7080],{"class":200,"line":7079},79,[198,7081,840],{"emptyLinePlaceholder":839},[198,7083,7085,7087,7089,7091,7093,7095],{"class":200,"line":7084},80,[198,7086,4060],{"class":1414},[198,7088,2183],{"class":204},[198,7090,216],{"class":1414},[198,7092,2188],{"class":204},[198,7094,2191],{"class":212},[198,7096,2194],{"class":204},[198,7098,7100,7102,7104,7106,7108,7110,7112],{"class":200,"line":7099},81,[198,7101,4638],{"class":1414},[198,7103,2112],{"class":212},[198,7105,2204],{"class":204},[198,7107,216],{"class":1414},[198,7109,2209],{"class":204},[198,7111,2212],{"class":212},[198,7113,2215],{"class":204},[198,7115,7117,7119,7121,7123,7125,7127,7129,7131,7133,7135,7137,7139,7141,7143],{"class":200,"line":7116},82,[198,7118,4141],{"class":212},[198,7120,2089],{"class":204},[198,7122,2092],{"class":1414},[198,7124,2226],{"class":219},[198,7126,2229],{"class":1414},[198,7128,2112],{"class":212},[198,7130,2056],{"class":204},[198,7132,2236],{"class":212},[198,7134,2239],{"class":204},[198,7136,2092],{"class":1414},[198,7138,2244],{"class":219},[198,7140,2098],{"class":1203},[198,7142,2249],{"class":219},[198,7144,2104],{"class":204},[198,7146,7148],{"class":200,"line":7147},83,[198,7149,840],{"emptyLinePlaceholder":839},[198,7151,7153,7155,7157,7159,7161,7163,7165,7167,7169,7171,7173,7175],{"class":200,"line":7152},84,[198,7154,3059],{"class":1414},[198,7156,2262],{"class":204},[198,7158,2265],{"class":1414},[198,7160,2112],{"class":212},[198,7162,2056],{"class":204},[198,7164,2059],{"class":212},[198,7166,2274],{"class":204},[198,7168,2277],{"class":1414},[198,7170,2280],{"class":1203},[198,7172,2283],{"class":1414},[198,7174,2286],{"class":1203},[198,7176,2104],{"class":204},[198,7178,7180],{"class":200,"line":7179},85,[198,7181,3072],{"class":204},[198,7183,7185,7187,7189,7191,7193,7195,7197],{"class":200,"line":7184},86,[198,7186,4719],{"class":1414},[198,7188,4091],{"class":204},[198,7190,3917],{"class":1414},[198,7192,2362],{"class":212},[198,7194,4728],{"class":204},[198,7196,2074],{"class":1414},[198,7198,4733],{"class":204},[198,7200,7202],{"class":200,"line":7201},87,[198,7203,3054],{"class":204},[198,7205,7207],{"class":200,"line":7206},88,[198,7208,4742],{"class":1468},[198,7210,7212,7214,7216,7218,7220,7222,7224,7226,7228,7230,7232,7234,7236],{"class":200,"line":7211},89,[198,7213,4141],{"class":212},[198,7215,4749],{"class":204},[198,7217,2092],{"class":1414},[198,7219,4754],{"class":219},[198,7221,2229],{"class":1414},[198,7223,4759],{"class":204},[198,7225,4762],{"class":212},[198,7227,4043],{"class":204},[198,7229,2092],{"class":1414},[198,7231,2244],{"class":219},[198,7233,2098],{"class":1203},[198,7235,2249],{"class":219},[198,7237,2104],{"class":204},[198,7239,7241,7243,7245],{"class":200,"line":7240},90,[198,7242,3059],{"class":1414},[198,7244,4781],{"class":1203},[198,7246,2104],{"class":204},[198,7248,7250],{"class":200,"line":7249},91,[198,7251,3072],{"class":204},[198,7253,7255],{"class":200,"line":7254},92,[198,7256,2124],{"class":204},[20,7258,5715,7259,3370,7261,48],{},[1005,7260,2831],{},[1005,7262,2843],{},[20,7264,2866],{},[134,7266,7269],{"className":7267,"code":7268,"language":139,"meta":140},[137],"[CheckBattery] battery=80\n[SelectGoal] goal=loading_area\n[PrintGoal] target=loading_area\nTree result: SUCCESS\n",[111,7270,7268],{"__ignoreMap":140},[20,7272,7273],{},[1217,7274],{"alt":140,"src":7275},"https:\u002F\u002Fcdn.tungchiahui.cn\u002Ftungwebsite\u002Fassets\u002Fimages\u002F2023\u002F12\u002F30\u002F1792284251898-1d658830.webp",[20,7277,7278],{},"这时再对照数据流：",[134,7280,7283],{"className":7281,"code":7282,"language":139,"meta":140},[137],"SelectGoal::tick()\n    ↓ setOutput(\"goal\", \"loading_area\")\nBlackboard[\"mission_goal\"]\n    ↓ getInput\u003Cstd::string>(\"goal\")\nPrintGoal::tick()\n",[111,7284,7282],{"__ignoreMap":140},[20,7286,7287,7288,7290,7291,7293,7294,7296,7297,7299,7300,7302],{},"C++ 操作各自的 ",[111,7289,460],{}," 端口，XML 中的 ",[111,7292,583],{}," 决定它们连接哪个 Blackboard 条目。\n",[111,7295,286],{}," 先写入，",[111,7298,155],{}," 后读取，执行顺序由 ",[111,7301,113],{}," 保证。",[366,7304,7305],{"id":7305},"解释端口名和blackboard",[20,7307,7308],{},"Blackboard 存放数据;端口名更像“函数参数名”，数据类型是另外指定的。",[20,7310,7311],{},"拿“柜子取东西”来比喻：",[3200,7313,7314,7317,7320,7323],{},[3203,7315,7316],{},"Blackboard：一个储物柜。",[3203,7318,7319],{},"Blackboard 键名：抽屉上的标签，例如 pickup_goal。",[3203,7321,7322],{},"数据：抽屉里放的内容，例如 \"loading_area\"。",[3203,7324,7325],{},"端口名：节点接收数据的接口名称，例如 goal。",[20,7327,7328],{},"看这一行 C++：",[134,7330,7332],{"className":2016,"code":7331,"language":2018,"meta":140,"style":140},"BT::InputPort\u003Cstd::string>(\"goal\")\n",[111,7333,7334],{"__ignoreMap":140},[198,7335,7336,7338,7340,7342,7344,7346,7348,7350,7352,7354],{"class":200,"line":201},[198,7337,2053],{"class":212},[198,7339,2056],{"class":204},[198,7341,557],{"class":212},[198,7343,205],{"class":204},[198,7345,4008],{"class":212},[198,7347,2056],{"class":204},[198,7349,2367],{"class":212},[198,7351,4015],{"class":204},[198,7353,4018],{"class":219},[198,7355,1558],{"class":204},[20,7357,7358],{},"意思是：这个节点有一个叫 goal 的输入接口，接收字符串。\n其中 std::string 才是类型，goal 是端口名。",[20,7360,7361],{},"再看 XML：",[134,7363,7365],{"className":192,"code":7364,"language":194,"meta":140,"style":140},"\u003CNavigateToGoal goal=\"{pickup_goal}\"\u002F>\n",[111,7366,7367],{"__ignoreMap":140},[198,7368,7369,7371,7374,7376,7378,7381],{"class":200,"line":201},[198,7370,205],{"class":204},[198,7372,7373],{"class":208},"NavigateToGoal",[198,7375,296],{"class":212},[198,7377,216],{"class":204},[198,7379,7380],{"class":219},"\"{pickup_goal}\"",[198,7382,278],{"class":204},[20,7384,7385],{},"意思是：到 Blackboard 中找到标着 pickup_goal 的抽屉，把里面的数据交给这个导航节点的 goal 输入接口。",[20,7387,7388],{},"对应关系就是：",[134,7390,7393],{"className":7391,"code":7392,"language":139,"meta":140},[137],"Blackboard 的 pickup_goal 抽屉\n里面保存 \"loading_area\"\n          ↓ XML 指定从这里取\n导航节点的 goal 输入端口\n          ↓\n节点知道：这次去 loading_area\n",[111,7394,7392],{"__ignoreMap":140},[20,7396,7397,7400,7403],{},[1005,7398,7399],{},"端口负责让节点接收或写出数据;",[1005,7401,7402],{},"Blackboard 负责保存数据;",[1005,7404,7405],{},"XML 负责指定它们怎么连接。",[56,7407],{},[12,7409,7411],{"id":7410},"保持每一步的编译执行和路径一致","保持每一步的编译、执行和路径一致",[20,7413,7414,7415,2710],{},"本篇各轮实践都沿用同一套框架配置、同一个可执行文件和 ",[111,7416,2001],{},[66,7418,7419,7429],{},[69,7420,7421],{},[72,7422,7423,7426],{},[75,7424,7425],{},"改了什么",[75,7427,7428],{},"接下来执行什么",[85,7430,7431,7442,7450,7458],{},[72,7432,7433,7436],{},[90,7434,7435],{},"第一次建立工程",[90,7437,7438,7439,7441],{},"选择 ",[111,7440,1385],{},"，点击 Configure → Build → Launch",[72,7443,7444,7447],{},[90,7445,7446],{},"修改 C++ 函数、类或注册代码",[90,7448,7449],{},"保存后点击 Build → Launch",[72,7451,7452,7455],{},[90,7453,7454],{},"只改 XML 结构或端口配置",[90,7456,7457],{},"保存后再次点击 Launch",[72,7459,7460,7463],{},[90,7461,7462],{},"修改 CMake 配置或移动工程目录",[90,7464,7465],{},"点击 Configure → Build → Launch",[20,7467,7468,7469,7472,7473,7475,7476,7478],{},"C++ 中使用 ",[111,7470,7471],{},"createTreeFromFile(BT_XML_PATH)","，实际 XML 路径由 ",[111,7474,1864],{}," 根据源码目录提供。无论从工程根目录、构建目录，还是编辑器中启动，程序读取的都是源码中的 ",[111,7477,2001],{},"；只修改 XML 后重新运行即可。",[20,7480,7481],{},"这个完整路径也避免了向解析器传入不含目录部分的裸文件名。本篇的路径设置用于源码工程的开发和调试，安装发布时的资源布局后续按需处理；当前练习编译后直接运行，不要求执行安装。",[20,7483,7484,7485,48],{},"本篇以 VSCode 的 CMake Tools 为操作入口：Configure 准备构建配置，Build 编译程序，Launch 直接运行，Debug 启动断点调试。不同扩展版本的按钮位置可能略有不同，以 CMake 面板中的操作名称为准；界面细节可对照 ",[24,7486,1128],{"href":1127},[20,7488,7489],{},"CMake Tools 默认可能在 Launch 前自动检查并构建。只修改 XML 时，即使界面出现这一步，通常也没有 C++ 文件需要重新编译；本篇所说“直接 Launch”表示无需手动先点 Build。",[20,7491,7492,7493,7495,7496,7499,7500,7502],{},"本篇使用 ",[111,7494,129],{}," 执行一次。所有示例节点都立即结束，因此一次根节点 tick 就足够。3.8 还有 ",[111,7497,7498],{},"tickRootWhileRunning()","：先 tick 一次，如果得到 ",[111,7501,2528],{}," 就继续驱动，直到成功或失败。持续任务如何多次 tick，后面实现导航时再实践。",[20,7504,7505,7506,7508,7509,7512,7513,7516,7517,7519],{},"引入异常报告后的两个版本中，如果将 ",[111,7507,155],{}," 输入改为 ",[111,7510,7511],{},"{unknown_goal}","，又没有节点写入该条目，主程序会显示读取错误并返回 ",[111,7514,7515],{},"2","；未注册的节点名则会在创建树时报告错误。它们与电量不足时返回的树任务 ",[111,7518,2524],{}," 是不同情况。",[20,7521,7522,7523,7526,7527,7530,7531,7534],{},"以上四个版本已按文中的增量修改顺序，在 Ubuntu 22.04 的 ",[111,7524,7525],{},"ysu_ros_humble"," 容器中，使用 ",[1005,7528,7529],{},"C++20、CMake 3.22.1、GCC 11.4"," 和已安装的 ",[1005,7532,7533],{},"BehaviorTree.CPP 3.8.7"," 编译运行验证，输出与示例一致。测试使用临时 Ninja 和工程副本，容器系统软件及已有工程没有升级或改动。",[20,7536,7537,7538,7541],{},"本篇逐步串起了“最小动作 → 条件控制 → 固定输入 → 节点间传值”。\n",[111,7539,7540],{},"节点之间传数据","继续使用坐标数据，练习类型转换、双向端口和外部 Blackboard。",[7543,7544,7545],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":140,"searchDepth":226,"depth":226,"links":7547},[7548,7549,7550,7553,7557,7562,7563,7576,7577,7582,7587],{"id":14,"depth":226,"text":15},{"id":60,"depth":226,"text":61},{"id":185,"depth":226,"text":186,"children":7551},[7552],{"id":368,"depth":244,"text":369},{"id":495,"depth":226,"text":496,"children":7554},[7555,7556],{"id":587,"depth":244,"text":588},{"id":640,"depth":244,"text":641},{"id":670,"depth":226,"text":671,"children":7558},[7559,7560,7561],{"id":674,"depth":244,"text":674},{"id":749,"depth":244,"text":750},{"id":904,"depth":244,"text":905},{"id":955,"depth":226,"text":955},{"id":1013,"depth":226,"text":1014,"children":7564},[7565,7566,7567,7568,7569,7570,7571,7572,7573,7574,7575],{"id":1086,"depth":244,"text":1087},{"id":1170,"depth":244,"text":1171},{"id":1282,"depth":244,"text":1282},{"id":1438,"depth":244,"text":1438},{"id":1858,"depth":244,"text":1858},{"id":2005,"depth":244,"text":2006},{"id":2314,"depth":244,"text":2315},{"id":2703,"depth":244,"text":2704},{"id":2778,"depth":244,"text":2779},{"id":2822,"depth":244,"text":2822},{"id":2895,"depth":244,"text":2895},{"id":2937,"depth":226,"text":2938},{"id":3816,"depth":226,"text":3816,"children":7578},[7579,7580,7581],{"id":3829,"depth":244,"text":3829},{"id":4541,"depth":244,"text":4542},{"id":4918,"depth":244,"text":4919},{"id":5765,"depth":226,"text":5766,"children":7583},[7584,7585,7586],{"id":5779,"depth":244,"text":5780},{"id":6199,"depth":244,"text":6200},{"id":7305,"depth":244,"text":7305},{"id":7410,"depth":226,"text":7411},"1200-0400-0200-0300","2023-12-30","2023-12-30-ros2-tutorial","\u002Fwiki\u002F2023-12-30-ros2-tutorial","Ros2 Tutorial","md",false,{},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0300-di-yi-ge-wan-zheng-cheng-xu",{"title":5,"description":140},"wiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0300-第一个完整程序","SLU7jAjvPAI-Ga3X9E1rXwYTLoZbAY63lt8RKqfza4Q",[7601,7608,7614,7620,7626,7632,7637,7643,7649,7655,7661,7667,7673,7679,7685,7691,7697,7703,7709,7715,7721,7727,7729,7734,7740,7746,7752,7758,7764,7770,7776,7782,7788,7794,7800,7805,7811,7816,7822,7828,7834,7840,7846,7852,7858,7864,7870,7876,7882,7888,7894,7900,7906,7912,7918,7924,7930,7936,7942,7948,7954,7960,7966,7972,7978,7984,7990,7996,8002],{"path":7602,"stem":7603,"title":7604,"authors":7605,"date":7589,"chapterOrder":7606,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F0100-ros2-jie-shao","wiki\u002F2023-12-30-ros2-tutorial\u002F0100-ROS2介绍","ROS2介绍",[7],"0100",0,{"path":7609,"stem":7610,"title":7611,"authors":7612,"date":7589,"chapterOrder":7613,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F0200-ru-men-cao-zuo","wiki\u002F2023-12-30-ros2-tutorial\u002F0200-入门操作","入门操作",[7],"0200",{"path":7615,"stem":7616,"title":7617,"authors":7618,"date":7589,"chapterOrder":7619,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F0300-gong-zuo-kong-jian-yu-gong-neng-bao","wiki\u002F2023-12-30-ros2-tutorial\u002F0300-工作空间与功能包","工作空间与功能包",[7],"0300",{"path":7621,"stem":7622,"title":7623,"authors":7624,"date":7589,"chapterOrder":7625,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F0400-si-da-tong-xin","wiki\u002F2023-12-30-ros2-tutorial\u002F0400-四大通信","四大通信",[7],"0400",{"path":7627,"stem":7628,"title":7629,"authors":7630,"date":7589,"chapterOrder":7631,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F0500-ros2-qi-ta-tong-xin-ji-zhi","wiki\u002F2023-12-30-ros2-tutorial\u002F0500-ROS2其他通信机制","ROS2其他通信机制",[7],"0500",{"path":7633,"stem":7634,"title":2843,"authors":7635,"date":7589,"chapterOrder":7636,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F0600-launch","wiki\u002F2023-12-30-ros2-tutorial\u002F0600-Launch",[7],"0600",{"path":7638,"stem":7639,"title":7640,"authors":7641,"date":7589,"chapterOrder":7642,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F0700-hui-su-rosbag2","wiki\u002F2023-12-30-ros2-tutorial\u002F0700-回溯rosbag2","回溯rosbag2",[7],"0700",{"path":7644,"stem":7645,"title":7646,"authors":7647,"date":7589,"chapterOrder":7648,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F0800-zuo-biao-bian-huan-tf","wiki\u002F2023-12-30-ros2-tutorial\u002F0800-坐标变换TF","坐标变换TF",[7],"0800",{"path":7650,"stem":7651,"title":7652,"authors":7653,"date":7589,"chapterOrder":7654,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F0900-ke-shi-hua-ping-tai-rviz2-yu-urdf-jian-mo-yu-yan","wiki\u002F2023-12-30-ros2-tutorial\u002F0900-可视化平台RVIZ2与URDF建模语言","可视化平台RVIZ2与URDF建模语言",[7],"0900",{"path":7656,"stem":7657,"title":7658,"authors":7659,"date":7589,"chapterOrder":7660,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1000-stage-ros2-fang-zhen-ping-tai","wiki\u002F2023-12-30-ros2-tutorial\u002F1000-Stage_Ros2仿真平台","Stage_Ros2仿真平台",[7],"1000",{"path":7662,"stem":7663,"title":7664,"authors":7665,"date":7589,"chapterOrder":7666,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1100-0100-gazeboclassic","wiki\u002F2023-12-30-ros2-tutorial\u002F1100-0100-GazeboClassic","Gezebo Classic",[7],"1100-0100",{"path":7668,"stem":7669,"title":7670,"authors":7671,"date":7589,"chapterOrder":7672,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1100-0200-ignitiongazebo","wiki\u002F2023-12-30-ros2-tutorial\u002F1100-0200-IgnitionGazebo","Ignition Gazebo（Gazebo Fortress）",[7],"1100-0200",{"path":7674,"stem":7675,"title":7676,"authors":7677,"date":7589,"chapterOrder":7678,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1100-0300-gazebosim","wiki\u002F2023-12-30-ros2-tutorial\u002F1100-0300-GazeboSim","Gz Sim（Gazebo Harmonic）",[7],"1100-0300",{"path":7680,"stem":7681,"title":7682,"authors":7683,"date":7589,"chapterOrder":7684,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1100-0400-igngz2gzsim","wiki\u002F2023-12-30-ros2-tutorial\u002F1100-0400-Igngz2gzsim","将Ign Gazebo迁移至Gz Sim",[7],"1100-0400",{"path":7686,"stem":7687,"title":7688,"authors":7689,"date":7589,"chapterOrder":7690,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1100-gezebo-fang-zhen-ping-tai","wiki\u002F2023-12-30-ros2-tutorial\u002F1100-Gezebo仿真平台","Gezebo仿真平台",[7],"1100",{"path":7692,"stem":7693,"title":7694,"authors":7695,"date":7589,"chapterOrder":7696,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0100-humble-ban-ben","wiki\u002F2023-12-30-ros2-tutorial\u002F1200-0100-Humble版本","Humble导航仿真",[7],"1200-0100",{"path":7698,"stem":7699,"title":7700,"authors":7701,"date":7589,"chapterOrder":7702,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0200-jazzy-ban-ben","wiki\u002F2023-12-30-ros2-tutorial\u002F1200-0200-Jazzy版本","Jazzy导航仿真",[7],"1200-0200",{"path":7704,"stem":7705,"title":7706,"authors":7707,"date":7589,"chapterOrder":7708,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0300-humble2jazzy","wiki\u002F2023-12-30-ros2-tutorial\u002F1200-0300-Humble2Jazzy","Humble与Jazzy导航的差异",[7],"1200-0300",{"path":7710,"stem":7711,"title":7712,"authors":7713,"date":7589,"chapterOrder":7714,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0100-you-xian-zhuang-tai-ji-fsm","wiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0100-有限状态机FSM","有限状态机（Finite State Machine）",[7],"1200-0400-0100",{"path":7716,"stem":7717,"title":7718,"authors":7719,"date":7589,"chapterOrder":7720,"chapterDepth":244,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0100-ji-ben-gai-nian","wiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0100-基本概念","基本概念",[7],"1200-0400-0200-0100",{"path":7722,"stem":7723,"title":7724,"authors":7725,"date":7589,"chapterOrder":7726,"chapterDepth":244,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0200-kong-zhi-yu-zhuang-shi-gui-ze","wiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0200-控制与装饰规则","控制与装饰规则",[7],"1200-0400-0200-0200",{"path":7596,"stem":7598,"title":5,"authors":7728,"date":7589,"chapterOrder":7588,"chapterDepth":244,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},[7],{"path":7730,"stem":7731,"title":7540,"authors":7732,"date":7589,"chapterOrder":7733,"chapterDepth":244,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0400-jie-dian-zhi-jian-chuan-shu-ju","wiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0400-节点之间传数据",[7],"1200-0400-0200-0400",{"path":7735,"stem":7736,"title":7737,"authors":7738,"date":7589,"chapterOrder":7739,"chapterDepth":244,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0500-chi-xu-ren-wu-yu-zhong-zhi","wiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0500-持续任务与中止","持续任务与中止",[7],"1200-0400-0200-0500",{"path":7741,"stem":7742,"title":7743,"authors":7744,"date":7589,"chapterOrder":7745,"chapterDepth":244,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0600-zu-he-shi-ji-ren-wu","wiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-0600-组合实际任务","组合实际任务",[7],"1200-0400-0200-0600",{"path":7747,"stem":7748,"title":7749,"authors":7750,"date":7589,"chapterOrder":7751,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-xing-wei-shu-bt","wiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-0200-行为树BT","行为树（BehaviorTree.CPP）",[7],"1200-0400-0200",{"path":7753,"stem":7754,"title":7755,"authors":7756,"date":7589,"chapterOrder":7757,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-fsm-yu-bt","wiki\u002F2023-12-30-ros2-tutorial\u002F1200-0400-FSM与BT","有限状态机FSM与行为树BT",[7],"1200-0400",{"path":7759,"stem":7760,"title":7761,"authors":7762,"date":7589,"chapterOrder":7763,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1200-ji-qi-ren-dao-hang-navigation2-fang-zhen-pian","wiki\u002F2023-12-30-ros2-tutorial\u002F1200-机器人导航Navigation2(仿真篇)","机器人导航Navigation2(仿真篇)",[7],"1200",{"path":7765,"stem":7766,"title":7767,"authors":7768,"date":7589,"chapterOrder":7769,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1300-0100-0100-boost-aiso","wiki\u002F2023-12-30-ros2-tutorial\u002F1300-0100-0100-Boost.Aiso","Boost.Asio库",[7],"1300-0100-0100",{"path":7771,"stem":7772,"title":7773,"authors":7774,"date":7589,"chapterOrder":7775,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1300-0100-0200-ros2-serial-driver","wiki\u002F2023-12-30-ros2-tutorial\u002F1300-0100-0200-ros2_serial_driver","ROS2 Serial Driver库",[7],"1300-0100-0200",{"path":7777,"stem":7778,"title":7779,"authors":7780,"date":7589,"chapterOrder":7781,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1300-0100-chuan-kou-tong-xin","wiki\u002F2023-12-30-ros2-tutorial\u002F1300-0100-串口通信","UART串口通信",[7],"1300-0100",{"path":7783,"stem":7784,"title":7785,"authors":7786,"date":7589,"chapterOrder":7787,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1300-0200-0100-socketcan","wiki\u002F2023-12-30-ros2-tutorial\u002F1300-0200-0100-socketcan","SocketCAN",[7],"1300-0200-0100",{"path":7789,"stem":7790,"title":7791,"authors":7792,"date":7589,"chapterOrder":7793,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1300-0200-0200-ros2-socketcan","wiki\u002F2023-12-30-ros2-tutorial\u002F1300-0200-0200-ros2_socketcan","ROS2_SocketCAN",[7],"1300-0200-0200",{"path":7795,"stem":7796,"title":7797,"authors":7798,"date":7589,"chapterOrder":7799,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1300-0200-can-tong-xin","wiki\u002F2023-12-30-ros2-tutorial\u002F1300-0200-CAN通信","CAN通信",[7],"1300-0200",{"path":7801,"stem":7802,"title":7767,"authors":7803,"date":7589,"chapterOrder":7804,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1300-0300-0100-boost-aiso","wiki\u002F2023-12-30-ros2-tutorial\u002F1300-0300-0100-Boost.Aiso",[7],"1300-0300-0100",{"path":7806,"stem":7807,"title":7808,"authors":7809,"date":7589,"chapterOrder":7810,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1300-0300-tcp-tong-xin","wiki\u002F2023-12-30-ros2-tutorial\u002F1300-0300-TCP通信","TCP通信",[7],"1300-0300",{"path":7812,"stem":7813,"title":7767,"authors":7814,"date":7589,"chapterOrder":7815,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1300-0400-0100-boost-aiso","wiki\u002F2023-12-30-ros2-tutorial\u002F1300-0400-0100-Boost.Aiso",[7],"1300-0400-0100",{"path":7817,"stem":7818,"title":7819,"authors":7820,"date":7589,"chapterOrder":7821,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1300-0400-udp-tong-xin","wiki\u002F2023-12-30-ros2-tutorial\u002F1300-0400-UDP通信","UDP通信",[7],"1300-0400",{"path":7823,"stem":7824,"title":7825,"authors":7826,"date":7589,"chapterOrder":7827,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1300-linux-ying-jian-tong-xin","wiki\u002F2023-12-30-ros2-tutorial\u002F1300-Linux硬件通信","Linux硬件通信",[7],"1300",{"path":7829,"stem":7830,"title":7831,"authors":7832,"date":7589,"chapterOrder":7833,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0100-ji-qi-ren-zu-cheng","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0100-机器人组成","机器人组成",[7],"1400-0100",{"path":7835,"stem":7836,"title":7837,"authors":7838,"date":7589,"chapterOrder":7839,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0200-0100-yuan-cheng-kai-fa-ssh","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0200-0100-远程开发SSH","远程开发SSH",[7],"1400-0200-0100",{"path":7841,"stem":7842,"title":7843,"authors":7844,"date":7589,"chapterOrder":7845,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0200-0200-yuan-cheng-fang-wen-zhuo-mian","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0200-0200-远程访问桌面","远程访问桌面",[7],"1400-0200-0200",{"path":7847,"stem":7848,"title":7849,"authors":7850,"date":7589,"chapterOrder":7851,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0200-gong-kong-ji-zhi-yuan-cheng-kai-fa-huan-jing","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0200-工控机之远程开发环境","工控机之远程开发环境",[7],"1400-0200",{"path":7853,"stem":7854,"title":7855,"authors":7856,"date":7589,"chapterOrder":7857,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0300-gong-kong-ji-zhi-wai-jie-usb-she-bei","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0300-工控机之外接USB设备","工控机之外接USB设备",[7],"1400-0300",{"path":7859,"stem":7860,"title":7861,"authors":7862,"date":7589,"chapterOrder":7863,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0400-fen-bu-shi-da-jian","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0400-分布式搭建","分布式搭建",[7],"1400-0400",{"path":7865,"stem":7866,"title":7867,"authors":7868,"date":7589,"chapterOrder":7869,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0500-you-hua-ri-zhi","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0500-优化日志","优化日志",[7],"1400-0500",{"path":7871,"stem":7872,"title":7873,"authors":7874,"date":7589,"chapterOrder":7875,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0100-li-cheng-ji-odom","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0100-里程计Odom","里程计Odom",[7],"1400-0600-0100",{"path":7877,"stem":7878,"title":7879,"authors":7880,"date":7589,"chapterOrder":7881,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0200-guan-xing-ji-imu","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0200-惯性计IMU","惯性计IMU",[7],"1400-0600-0200",{"path":7883,"stem":7884,"title":7885,"authors":7886,"date":7589,"chapterOrder":7887,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0300-ji-guang-lei-da-lidar","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0300-激光雷达LiDAR","激光雷达LiDAR",[7],"1400-0600-0300",{"path":7889,"stem":7890,"title":7891,"authors":7892,"date":7589,"chapterOrder":7893,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0400-xiang-ji-camera","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0400-相机Camera","相机Camera",[7],"1400-0600-0400",{"path":7895,"stem":7896,"title":7897,"authors":7898,"date":7589,"chapterOrder":7899,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0500-quan-qiu-ding-wei-gnss","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0500-全球定位GNSS","全球定位GNSS",[7],"1400-0600-0500",{"path":7901,"stem":7902,"title":7903,"authors":7904,"date":7589,"chapterOrder":7905,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0600-shou-bing-joy","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0600-手柄joy","手柄JOY",[7],"1400-0600-0600",{"path":7907,"stem":7908,"title":7909,"authors":7910,"date":7589,"chapterOrder":7911,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0700-jian-pan-kong-zhi-jie-dian","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-0700-键盘控制节点","键盘控制节点",[7],"1400-0600-0700",{"path":7913,"stem":7914,"title":7915,"authors":7916,"date":7589,"chapterOrder":7917,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-ying-jian-ping-tai","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0600-硬件平台","硬件平台",[7],"1400-0600",{"path":7919,"stem":7920,"title":7921,"authors":7922,"date":7589,"chapterOrder":7923,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0700-zuo-biao-xi-yu-hua-ti-guan-xi","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0700-坐标系与话题关系","坐标系与话题关系",[7],"1400-0700",{"path":7925,"stem":7926,"title":7927,"authors":7928,"date":7589,"chapterOrder":7929,"chapterDepth":244,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0800-0100-0100-lun-shi-li-cheng-ji-biao-ding","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0800-0100-0100-轮式里程计标定","轮式里程计标定",[7],"1400-0800-0100-0100",{"path":7931,"stem":7932,"title":7933,"authors":7934,"date":7589,"chapterOrder":7935,"chapterDepth":244,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0800-0100-0200-lun-shi-li-cheng-ji-yu-imu-rong-he","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0800-0100-0200-轮式里程计与IMU融合","轮式里程计与IMU融合",[7],"1400-0800-0100-0200",{"path":7937,"stem":7938,"title":7939,"authors":7940,"date":7589,"chapterOrder":7941,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0800-0100-lun-shi-li-cheng-ji-biao-ding-yu-rong-he","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0800-0100-轮式里程计标定与融合","轮式里程计标定与融合",[7],"1400-0800-0100",{"path":7943,"stem":7944,"title":7945,"authors":7946,"date":7589,"chapterOrder":7947,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0800-0200-ji-guang-lei-da-gong-ju","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0800-0200-激光雷达工具","激光雷达工具",[7],"1400-0800-0200",{"path":7949,"stem":7950,"title":7951,"authors":7952,"date":7589,"chapterOrder":7953,"chapterDepth":226,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0800-0300-xiang-ji-shi-yong-jin-jie","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0800-0300-相机使用进阶","相机使用进阶",[7],"1400-0800-0300",{"path":7955,"stem":7956,"title":7957,"authors":7958,"date":7589,"chapterOrder":7959,"chapterDepth":201,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-0800-ying-jian-ping-tai-jin-jie","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-0800-硬件平台进阶","硬件平台进阶",[7],"1400-0800",{"path":7961,"stem":7962,"title":7963,"authors":7964,"date":7589,"chapterOrder":7965,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1400-ji-qi-ren-ying-jian","wiki\u002F2023-12-30-ros2-tutorial\u002F1400-机器人硬件","机器人硬件",[7],"1400",{"path":7967,"stem":7968,"title":7969,"authors":7970,"date":7589,"chapterOrder":7971,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1500-ros2-control","wiki\u002F2023-12-30-ros2-tutorial\u002F1500-ROS2_Control","ROS2_Control",[7],"1500",{"path":7973,"stem":7974,"title":7975,"authors":7976,"date":7589,"chapterOrder":7977,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1600-moveit2-gong-ye-ji-qi-ren-ji-xie-bi","wiki\u002F2023-12-30-ros2-tutorial\u002F1600-Moveit2工业机器人机械臂","Moveit2工业机器人机械臂",[7],"1600",{"path":7979,"stem":7980,"title":7981,"authors":7982,"date":7589,"chapterOrder":7983,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1700-ji-qi-ren-dao-hang-navigation2-shi-ti-pian","wiki\u002F2023-12-30-ros2-tutorial\u002F1700-机器人导航Navigation2(实体篇)","机器人导航Navigation2(实体篇)",[7],"1700",{"path":7985,"stem":7986,"title":7987,"authors":7988,"date":7589,"chapterOrder":7989,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1800-microros","wiki\u002F2023-12-30-ros2-tutorial\u002F1800-MicroROS","MicroROS",[7],"1800",{"path":7991,"stem":7992,"title":7993,"authors":7994,"date":7589,"chapterOrder":7995,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F1900-webots-fang-zhen-ping-tai","wiki\u002F2023-12-30-ros2-tutorial\u002F1900-Webots仿真平台","Webots仿真平台",[7],"1900",{"path":7997,"stem":7998,"title":7999,"authors":8000,"date":7589,"chapterOrder":8001,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":7594},"\u002Fwiki\u002F2023-12-30-ros2-tutorial\u002F2000-opencv","wiki\u002F2023-12-30-ros2-tutorial\u002F2000-OpenCV","OpenCV",[7],"2000",{"path":7591,"stem":8003,"title":8004,"authors":8005,"date":7589,"chapterOrder":8006,"chapterDepth":7607,"docKey":7590,"docRoot":7591,"docTitle":7592,"isWikiDoc":839,"isWikiIndex":839},"wiki\u002F2023-12-30-ros2-tutorial\u002Findex","ROS2机器人操作系统教程",[7],null,[8008,8010,8013,8016],{"name":7,"image":8009},"\u002Fimages\u002Fmember_photo\u002F董佳辉.jpg",{"name":8011,"image":8012},"宫金良","\u002Fimages\u002Fmember_photo\u002F宫金良.jpg",{"name":8014,"image":8015},"巩丽","\u002Fimages\u002Fmember_photo\u002F巩丽.png",{"name":8017,"image":8018},"张彦斐","\u002Fimages\u002Fmember_photo\u002F张彦斐.jpg",1792431903262]