mcpField
M
msw-behaviourtree

msw-behaviourtree

github.com/msw-git开发389.8k 次安装3 周前更新
代码仓库

关于 msw-behaviourtree

MSW BehaviourTree 是用于创建 MSW `.behaviourtree` 文件的技能。它从项目文件生成项目专属的节点与类型规范,并据此结合固定图规则指导行为树构建和验证。

主要功能

  1. 01生成项目专属的 BT 规范
  2. 02从项目文件发现自定义节点
  3. 03映射节点与属性类型
  4. 04规定图结构和子节点约束
  5. 05指导 Blackboard 与节点属性配置

适用场景

  1. 01为 MSW 项目创建行为树
  2. 02编写前核对自定义节点 ID 和属性
  3. 03组织复合节点、装饰器与动作节点

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/msw-git/msw-ai-coding-plugins-official --skill msw-behaviourtree

安装后在 Claude Code 中输入/msw-behaviourtree确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
node-catalog.md
skeleton-full.json
skeleton-minimal.json
scripts/
build-spec.cjs

详细介绍

MSW BehaviourTree

用于编写 MSW .behaviourtree 文件的端到端技能。涵盖项目专属编写规范(<ProjectRoot>/.behaviourDocs/bt-spec.md)和树的生成。固定图规则和骨架位于此技能的 references/ 中;项目专属规范由此技能的本地 scripts/build-spec.cjs(重新)构建。


🚦 执行顺序(请按此顺序操作)

0. 构建/刷新项目规范(bt-spec.md)

该规范是所有项目特定数据的真实来源:每个自定义动作/装饰器/复合节点的 definitionId、btNodeType、可见的 propertyKey 名称,以及标记为本项目 CoreVersion 的序列化 Type.type 字符串。

何时(重新)构建:

  • 首次在项目中处理 BT(尚无 .behaviourDocs/bt-spec.md)。
  • 任何会影响 BT 节点范围的更改之后:
    • 新增/重命名/移除 .codeblock,且其配对的 .mlua 扩展了 ActionNode/DecoratorNode/CompositeNode
    • 在此类 .mlua 中新增/移除/重命名 property 行
    • Environment/config 中的 CoreVersion 升级(序列化类型字符串带有版本标记)。
  • 用户表示最近新增/更改了 BT 代码块或 .mlua 属性——过期 UUID/缺失属性会悄无声息地导致行为树损坏。
  • 下游验证(步骤 7)指出 definitionId、propertyKey 或版本不匹配。

运行方式——调用此技能的本地脚本:

node "scripts/build-spec.cjs" --projectRoot "<MSW project root>"

如果当前工作目录已是 MSW 项目根目录,可以省略 --projectRoot。需要 PATH 中有 Node.js(无其他依赖——仅使用标准库 fs/path)。

可选覆盖项(长标志,不区分大小写):

标志默认值说明
--projectRoot当前工作目录要扫描的 MSW 项目根目录
--outputPath<ProjectRoot>/.behaviourDocs/bt-spec.md如果文件夹不存在,则创建
--coreVersion从 <ProjectRoot>/Environment/config 读取(CoreVersion 字段)如果配置文件缺失,则必需

覆盖项示例:

node "scripts/build-spec.cjs" --projectRoot "C:/path/to/project" --coreVersion <CoreVersion>

如果 Environment/config 不存在且未传入 --coreVersion,脚本会抛出异常——没有备用默认值。

规范包含的内容:

  1. 项目元数据——项目根目录、CoreVersion、生成时间、发现的节点数量。
  2. 复合节点——具有固定 definitionId/btNodeType 的内置名称,以及发现的自定义复合节点(.mlua 声明了 extends CompositeNode)。
  3. 自定义动作节点——Name、definitionId、btNodeType、可见属性名称。
  4. 自定义装饰器节点——结构与动作节点相同。
  5. 类型映射——mlua 类型到序列化 MODNativeType.type 的映射,以及 Blackboard ObjectValue 结构。

UUID 来自项目中实际的 .codeblock 文件——规范绝不会凭空生成 UUID。@HideFromInspector 属性会自动过滤。固定的创作规则、文件骨架和验证清单位于此技能的 references/ 中,而非生成的规范中。

(重新)构建后,读取刚写入的 <ProjectRoot>/.behaviourDocs/bt-spec.md,然后继续执行以下步骤。精简规范只列出属性名称;构造 nodeProperties 时,需从配对的 .mlua 文件中确定每个属性的 mlua 类型/默认值,然后使用 bt-spec.md §4 中的类型映射填写 propertyType.type。

还需阅读 references/skeleton-minimal.json,了解最精简的有效行为树;阅读 references/skeleton-full.json,了解所有可选字段均已填充的 Composite+Decorator+Action+Blackboard 示例;阅读 references/node-catalog.md,了解固定的图规则;并参考项目中任何现有的 .behaviourtree(**/*.behaviourtree)以遵循惯例。将骨架中的 {CORE_VERSION} 替换为 bt-spec.md 中的 CoreVersion——顶层以及 Blackboard 变量和 nodeProperties 中的每个 MOD.Core.* 类型字符串都要替换。

1. 收集用户输入

通过上下文确认;如果有任何不明确之处,请使用 AskUserQuestion 询问:

项目描述示例
name树的显示名称"PatrolAndChase"
保存路径.behaviourtree 的位置(相对于项目根目录)RootDesk/MyDesk/PatrolAndChase.behaviourtree
树结构计划使用的节点图(根复合节点 + 子节点)Sequence → [Chase, MoveTo]
自定义节点树引用的动作/装饰器/复合节点代码块Chase、MoveTo、Jump
黑板变量变量名称 + 类型 + 初始值TargetEntity: Entity、MoveSpeed: number = 10.0
节点属性对于每个自定义节点,哪个属性映射到哪个黑板变量Chase.TargetEntityKey = "TargetEntity"

**自定义节点存在性检查(必须):**用户提到的每个自定义动作/装饰器/复合节点名称都必须出现在 bt-spec.md §1 / §2 / §3 中。如果引用的节点不在规格说明中,请停止并询问用户——不要编造 UUID,不要假定同名节点存在,也不要跳过重新运行 Step 0。

2. 生成 UUID

你需要:

  • 文件 UUID 一个 → 写入 EntryKey 和 ContentProto.Json.id(两者相同,且都以 behaviourtree:// 为前缀)。
  • Nodes 中的每个节点各需要一个 UUID(nodeId)。
node -e "console.log(require('node:crypto').randomUUID())"

提前生成 UUID,写入临时表,然后再组装。不要将文件 UUID 用作 nodeId。

3. 解析每个 definitionId
节点类别definitionId 值btNodeType
内置复合节点(SequenceNode、SelectorNode、ParallelNode)与 nodeName 相同的字符串1
自定义复合节点(extends CompositeNode)来自 bt-spec.md §1 的值1
自定义动作节点来自 bt-spec.md §2 的值0
自定义装饰器节点来自 bt-spec.md §3 的值2

自定义节点 UUID 来自 bt-spec.md(该文件从真实的 .codeblock 文件中读取这些 UUID)——不得使用任何其他来源。

4. 构建黑板

对于每个变量,照抄 bt-spec.md §4 中的 Type.type 字符串和 ObjectValue 结构。带版本标记的子字符串(Version=<CoreVersion>)必须完全匹配——拼写错误会导致反序列化静默失败。

Variables 是一个有序数组;每个条目为 { Name, Type: { "$type": "MODNativeType", type: "<来自规格说明>" }, ObjectValue: <来自规格说明> }。ObjectValue 不包含 $type 区分符(与 .model 文件中的 Value 不同)。

对于 Component / ComponentRef,ComponentId 的格式为 <entity-uuid>:<ComponentName>(引擎组件)或 <entity-uuid>:<scriptCodeblockUuid>:<ScriptComponentName>(脚本组件)。请参照项目中已有的序列化示例。

数值型 ObjectValue 使用浮点数字面量形式(3.0,而不是 3)。

**运行时注意事项:**黑板存储在条目所关联的树上。如果任何脚本之后在该组件上调用 AIComponent:SetRootNode(...),条目树和此黑板都会被丢弃——BlackBoard 会读取到 nil,且 *Key 属性将停止解析。不要将运行时根节点切换与依赖黑板的树混用。

4.5 解析节点属性值

对于每个需要 nodeProperties 的自定义节点:

  1. 确认 propertyKey 在该节点对应的 bt-spec.md §1 / §2 / §3 中存在。
  2. 在项目中搜索 script <NodeName> extends ActionNode、extends DecoratorNode 或 extends CompositeNode,找到配对的 .mlua。如果有多个文件匹配,优先选择其同级 .codeblock 中包含与 bt-spec.md 完全一致的 definitionId UUID 的文件;如果仍有歧义,请询问用户。
  3. 阅读 .mlua 中可见的 property 声明,忽略带有 @HideFromInspector 的属性。由此可确定 mlua 类型和默认值。
  4. 仅当用户提供了值、行为需要非默认值,或 *Key 属性必须指向 Blackboard 变量时,才添加 nodeProperties 条目。可以安全使用 .mlua 默认值的可选属性应省略。
  5. 对于 *Key 字符串属性,将 propertyValue 设置为 Blackboard 变量名。如果变量一目了然,可根据名称和 getter 用法推断(MoveSpeedKey -> MoveSpeed、TargetEntityKey -> TargetEntity)。如果可能匹配多个 Blackboard 变量,请询问用户。
  6. 对于字面值属性,使用用户提供的值。如果用户未提供值,且 .mlua 默认值有实际意义,则省略该属性,而不要序列化猜测的值。
  7. 如果 OnBehave 会检查某个属性是否为 nil、空字符串或无效枚举值,且无法推断其值,请在编写树之前询问用户。

nodeProperties 条目格式:

{
  "propertyKey": "<property name>",
  "propertyType": { "$type": "MODNativeType", "type": "<type from bt-spec.md §4>" },
  "propertyValue": <value>
}
5. 组装节点

图结构硬性约束(编写前验证):

  • RootNode 不是父节点。 它不能有 childNodes。它只存储 startNodeId,且 startNodeId 必须指向 Nodes 中的一个节点。
  • 如果树需要多个顶层行为,请使用一个 Composite 作为唯一的 startNodeId,或使用一个 Decorator 作为唯一的 startNodeId,并让其 decoChildNodes 包装一个 Composite,或包装一条最终包含 Composite 的 Decorator 链。将多个行为放在该 Composite 的 childNodes 下。
  • Nodes 中恰好只能有一个节点的 nodeParentId: "":即 RootNode.startNodeId 所引用的节点。不要创建多个根级 Action/Composite/Decorator 节点。
  • Composite(btNodeType: 1)是唯一可以通过 childNodes 拥有多个子节点的节点类别。
  • Decorator(btNodeType: 2)仅用作包装/父节点,且只能包含一个 Action、Composite 或 Decorator 节点。它也可以是另一个 Decorator 的子节点,因此 Decorator 到 Decorator 的链是有效的。它必须使用单数形式的 decoChildNodes(一个 nodeId 字符串)指向该子节点,而不是使用 childNodes;被包装的子节点也必须在其 nodeParentId 中记录 Decorator 的 id。
  • 作用于同一个 Action 的 Decorator 必须串联,绝不能作为同级节点平铺。 每个 Decorator 只能拥有一个下游子树。如果两个或更多 Decorator 要对同一个 Action 进行门控/修改,则应构建一条链:Composite → ADeco → BDeco → CDeco → Action,其中每个 Decorator 的 decoChildNodes 都指向下一个 Decorator(最后指向 Action)。具体来说:在通向单个 Action 的同一条链中,任意两个 Decorator 都不能共用同一个 nodeParentId——每个 Decorator 的父节点都是前一个 Decorator,只有最顶层 Decorator 的父节点是 Composite。当每个 Decorator 包装不同的下游子树时,它们作为同一个 Composite 下的同级节点仍然有效。✅ Composite → ADeco → BDeco → CDeco → Action(链式结构——链中每个 Decorator 都有唯一的父节点)。❌ Composite → [ADeco→Action, BDeco→Action, CDeco→Action](通过重复 Action 来绕过串联)。❌ Composite → [ADeco, BDeco, CDeco, Action](Decorator 被平铺——它们没有包装 Action,实际上成了孤立节点)。
  • Action(btNodeType: 0)是叶节点——绝不能有子节点。

节点写入不变量:

  • 每个 nodeId 在文件中都是唯一的。
  • 每个非根节点的 nodeParentId 都指向一个真实的 nodeId,且该节点必须是 Composite 或 Decorator。绝不能指向 RootNode,因为 RootNode 不作为节点显示在 Nodes 中。
  • 如果节点的父节点是 Composite,该 Composite 必须在 childNodes 中包含该节点的 id。
  • 如果节点的父节点是 Decorator,该 Decorator 的 decoChildNodes 必须等于该节点的 nodeId。即使父节点和子节点都是 Decorator,这样设置也有效。
  • Composite 的 childNodes 与子节点的 nodeParentId 必须双向一致。
  • Action 节点省略 childNodes。Decorator 节点省略 childNodes,并使用且仅使用一个 decoChildNodes(单个字符串 nodeId)。
  • 绝不要写入 probability。 编辑器在往返保存时会移除此字段,且受支持的 Composite(SequenceNode、SelectorNode、ParallelNode)不会使用每个子节点的权重。项目中较早生成的树可能仍在每个节点上带有 "probability": 1.0;读取时将其视为旧版字段,但新建节点时不要写入。
  • Decorator 节点(btNodeType: 2)省略 nodePosition。 编辑器会根据 Decorator 包裹的子节点自动定位,并在保存时不写入 nodePosition 字段。只有 Composite 和 Action 节点带有 nodePosition。RootNode 块也带有自己的 nodePosition(与起始节点分开)。
  • 空集合字段应省略,而非序列化为 []。 尚无子节点的 Composite 应完全省略 childNodes;没有覆盖项的节点应完全省略 nodeProperties。空数组是编辑器草稿产生的内容,不要手动编写。
  • Decorator 的子节点字段是 decoChildNodes(规范字段——这是编辑器保存时保留的字段;ChildNodeId 在往返保存时会被静默移除)。它是一个保存被包裹子节点 nodeId 的单个字符串(不是数组)。读取旧版文件时,手动编写的 Decorator 中可能仍会出现 ChildNodeId;应将其视为同一字段。写入时,始终输出 decoChildNodes。
  • RootNode.startNodeId 引用一个 nodeId——该节点可以是 Action、Composite 或 Decorator——且该节点是唯一一个 nodeParentId: "" 的节点。

*Key 后缀的 String 属性保存 Blackboard 变量的名称(运行时通过 BlackBoard:GetXxx 解析)。不带 Key 的属性保存字面值。

6. nodePosition 格式

nodePosition 是一个包含数值 x / y 的 JSON 对象:

"nodePosition": { "x": 0.0, "y": 0.0 }

请使用浮点数字面量(0.0,而非 0)。旧版字符串格式 "(0.000, 0.000)" 仍可能出现在较早手动编写的树中——读取时将其视为等效格式,但始终写入对象格式(BT 编辑器会在保存时将其规范化为这种格式,因此首次打开文件时,字符串格式重新序列化会产生大量无关差异)。

编辑器坐标轴:BT 编辑器使用数学惯例的画布——+x 向右,+y 向上(右上象限为正)。因此,放置在比父节点 更高 y 坐标处的子节点,在屏幕上会显示在父节点上方。

布局规则——向下绘制树:深度沿 −y 方向增长(子节点位于父节点下方),同级节点围绕父节点的 x 坐标沿 ±x 方向展开。典型间距:深度层级之间相隔 200 个单位,同级节点之间相隔 200 个单位。

RootNode 块与起始节点——不要将它们放在同一位置。 RootNode.nodePosition 是画布锚点,保持为 { "x": 0.0, "y": 0.0 }。起始节点(由 startNodeId 引用的节点)必须位于该锚点下方一个层级——将其放在 (0, 0) 会导致它与编辑器画布上的 RootNode 标记视觉重叠。将 RootNode 锚点视为深度 0,起始节点视为深度 1。

  • RootNode.nodePosition:{ "x": 0.0, "y": 0.0 }(固定锚点——永不移动)
  • 起始节点(深度 1,由 startNodeId 引用):{ "x": 0.0, "y": -200.0 }
  • 起始节点的单个子节点(深度 2):{ "x": 0.0, "y": -400.0 }
  • 起始节点的两个子节点(深度 2):{ "x": -100.0, "y": -400.0 } 和 { "x": 100.0, "y": -400.0 }
  • 每增加一个层级:parent.y − 200

切勿将子节点放在 y 大于或等于父节点 y 的位置——这会使树向上绘制,并在视觉上与父节点重叠。RootNode 与起始节点之间也适用此规则:起始节点必须位于 y ≤ -200(严格低于锚点)。

装饰器节点不包含 nodePosition。 编辑器会根据被包装的子节点自动布局。请从所有 btNodeType: 2 节点中省略该字段;该字段仅出现在 RootNode、组合节点(btNodeType: 1)和动作节点(btNodeType: 0)上。

7. 编写并验证

写入 JSON 文件,然后运行此检查清单。请特别注意:

  • EntryKey 为 behaviourtree://{uuid},且与 ContentProto.Json.id 完全一致。
  • 顶层 Id、GameId、Content 均为 ""。Usage、UseService、DynamicLoading 均为 0。UsePublish 为 1。CoreVersion 与项目一致(Environment/config)。StudioVersion 为 0.1.0.0。ContentType 为 x-mod/behaviourtree。ContentProto.Use 为 Json。
  • RootNode 没有 childNodes;RootNode.startNodeId 与 Nodes 中恰好一个 nodeId 完全一致;该起始节点的 nodeParentId: "";且没有其他节点的 nodeParentId: ""。
  • 每个 nodeParentId 均为 "" 或现有的 nodeId。
  • 所有 nodeId 值均唯一。
  • 对于每个 Composite,childNodes ID 集合等于 nodeParentId 为该 Composite 的节点集合。
  • 每个 Action 都没有 childNodes。每个 Decorator 都没有 childNodes,且恰好有一个 decoChildNodes(单个 nodeId 字符串——ChildNodeId 是旧版变体;编辑器在往返转换时会将其移除),并且该 ID 指向恰好一个 Action、Composite 或 Decorator 子节点,且该子节点的 nodeParentId 指回此 Decorator。Decorator 到 Decorator 的父子链有效,必须使用相同的 decoChildNodes ↔ nodeParentId 规则进行检查。
  • Decorator 链规则: 多个 Decorator 作用于同一个 Action 时,必须形成一条链(Composite → ADeco → BDeco → … → Action)。沿每个 Action 向上遍历至其所属 Composite,确认该路径上遇到的 Decorator 的 nodeParentId 值均唯一(即每个 Decorator 的父节点都是前一个 Decorator,而不是链中已出现过的另一个 Decorator)。同一链中的两个 Decorator 共享一个 nodeParentId 属于无效情况。同一 Composite 下、包装不同下游子树的同级 Decorator 没有问题——唯一性按链检查,而非全局检查。
  • 没有节点序列化 "probability"。(读取时可能出现旧版 1.0 值,但绝不能在写入时生成。)
  • 每个 Composite 和 Action 都以对象形式携带 nodePosition:{ "x": <num>, "y": <num> },且使用浮点数字面量——写入时不能使用旧版 "(x.xxx, y.yyy)" 字符串。Decorator 节点完全不携带 nodePosition。
  • 起始节点不能与 RootNode 锚点重叠。 RootNode.nodePosition 为 { "x": 0.0, "y": 0.0 },且 startNodeId 引用的节点的 y ≤ -200.0(通常为 { "x": 0.0, "y": -200.0 })。如果起始节点是 Decorator(没有 nodePosition),则链中向下的第一个 Composite/Action 必须满足此偏移要求。
  • 没有节点序列化空数组——没有子节点的 Composite 应省略 childNodes;没有覆盖项的节点应省略 nodeProperties。不要写入 "childNodes": [] 或 "nodeProperties": []。
  • 每个自定义节点的 definitionId 均从 bt-spec.md 复制(绝不能自行编造)。
  • 每个 nodeProperties[].propertyKey 均与该节点在 bt-spec.md 中的属性匹配。
  • 每个 *Key 属性的 propertyValue 均与正确类型的 Blackboard.Variables[].Name 匹配。
  • 每个类型字符串均从 bt-spec.md §4 原样复制——带版本标签,且极易因拼写错误而失效。
  • 版本交叉检查: 每个 MOD.Core.* 类型字符串中的 Version=X.Y.Z.Z 子串(位于 Blackboard.Variables[].Type.type 和 Nodes[].nodeProperties[].propertyType.type 中)必须与文件顶层的 CoreVersion 一致。不匹配会导致反序列化静默失败——当 bt-spec.md 相对于项目当前的 CoreVersion 已过时,这种情况很常见。如果不一致,写入前请重新运行步骤 0。(System.* 类型使用不可变的 Version=4.0.0.0,不受此规则限制。)
  • JSON 可解析:
    node -e "JSON.parse(require('node:fs').readFileSync(process.argv[1],'utf8'))" "<path>"
    

如果任何检查未通过,请先修复,再报告完成。


📂 此技能中的文件/使用的文件

  • scripts/build-spec.cjs — 扫描项目并生成 <ProjectRoot>/.behaviourDocs/bt-spec.md 的 Node.js 脚本。在步骤 0 中调用。
  • <ProjectRoot>/.behaviourDocs/bt-spec.md — 生成的精简目录。它是节点名称、definitionId、btNodeType、属性名称和类型字符串的唯一真实来源。由上述脚本写入;供步骤 1–7 使用。
  • references/skeleton-minimal.json — 最小有效树(空 Blackboard,单个没有子节点的 Composite 根节点)。
  • references/skeleton-full.json — Composite → Decorator → Action,包含 nodeProperties(字面值 + Key 后缀)和已填充的 Blackboard。只要树并非简单结构,就将其用作结构参考。
  • references/node-catalog.md — 对 btNodeType 值、有效图结构、Key 后缀约定以及规范构建器如何发现节点的说明(仅供参考;运行时目录本身位于 bt-spec.md 中)。

🔁 编辑工作流(现有文件)

  1. 阅读整个文件——切勿盲目编辑。UUID 和父子图必须保持一致。
  2. 切勿更改文件的包装 UUID(EntryKey / ContentProto.Json.id)——否则外部引用会失效。
  3. 添加节点:生成一个全新的 nodeId,将其追加到 Nodes,更新父 Composite 的 childNodes,并设置新节点的 nodeParentId。
  4. 移除节点:从 Nodes 中移除,并从任何 Composite 的 childNodes 中移除其 ID。如果它是 Composite,则决定重新指定其子节点的父节点,还是一并移除子节点——切勿留下悬空的 nodeParentId 引用。
  5. 如果编辑涉及自定义节点名称、属性或类型,且这些内容可能在上次构建规范后已在项目中发生变化,请先重新运行步骤 0。
  6. 每次编辑后,重新运行步骤 7 的验证清单。

评论

支持 Markdown