msw-behaviourtree
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMSW BehaviourTree
MSW 行为树
End-to-end authoring skill for MSW files. Owns both the project-specific authoring spec () and the tree generation itself. Fixed graph rules and skeletons live in this skill's ; the per-project spec is (re)built by this skill's local .
.behaviourtree<ProjectRoot>/.behaviourDocs/bt-spec.mdreferences/scripts/build-spec.cjs这是用于MSW 文件的端到端编写技能。同时负责项目专属的编写规范()和行为树生成工作。固定的图规则和骨架存储在该技能的 目录中;每个项目的规范由该技能本地的 脚本(重新)构建。
.behaviourtree<ProjectRoot>/.behaviourDocs/bt-spec.mdreferences/scripts/build-spec.cjs🚦 Execution order (follow this sequence)
🚦 执行顺序(遵循此流程)
0. Build / refresh the project spec (bt-spec.md
)
bt-spec.md0. 构建/刷新项目规范(bt-spec.md
)
bt-spec.mdThe spec is the source of truth for every project-specific data point: each custom action/decorator node's , , visible names, and the serialized strings stamped to this project's .
definitionIdbtNodeTypepropertyKeyType.typeCoreVersionWhen to (re)build:
- First time working on BT in a project (no yet).
.behaviourDocs/bt-spec.md - After any change that affects BT node surface area:
- new / renamed / removed whose paired
.codeblockextends.mlua/ActionNodeDecoratorNode - added / removed / renamed lines in such a
property.mlua Environment/configbumped (the serialized type strings are version-tagged).CoreVersion
- new / renamed / removed
- The user says they recently added/changed a BT codeblock or a property — stale UUIDs / missing properties silently produce broken trees.
.mlua - The downstream validation (Step 7) flags a ,
definitionId, or version mismatch.propertyKey
How to run — invoke this skill's local script:
bash
node "<SKILL_PATH>/scripts/build-spec.cjs" --projectRoot "<MSW project root>"If the current working directory is already the MSW project root, can be omitted. Requires Node.js on (no other dependencies — pure stdlib /).
--projectRootPATHfspathOptional overrides (long flags, case-insensitive):
| Flag | Default | Notes |
|---|---|---|
| current working directory | MSW project root to scan |
| | folder is created if missing |
| read from | required if the config is missing |
Example with overrides:
bash
node "<SKILL_PATH>/scripts/build-spec.cjs" --projectRoot "C:/path/to/project" --coreVersion 26.5.0.0The script throws if is absent and is not passed — there is no fallback default.
Environment/config--coreVersionWhat the spec contains:
- Project metadata — project root, , generated time, discovered node counts.
CoreVersion - Built-in composite node names and their fixed /
definitionId.btNodeType - Custom action nodes — ,
Name,definitionId, visible property names.btNodeType - Custom decorator nodes — same shape as action nodes.
- Type map — mlua type to serialized plus Blackboard
MODNativeType.typeshape.ObjectValue
UUIDs come from real files in the project — the spec never invents them. properties are filtered out automatically. Fixed authoring rules, file skeletons, and validation checklists live in this skill's rather than in the generated spec.
.codeblock@HideFromInspectorreferences/After (re)building, read the freshly written and continue with the steps below. The compact spec intentionally lists only property names; when constructing , resolve each property's mlua type/default from the paired file, then use the type map in §4 for .
<ProjectRoot>/.behaviourDocs/bt-spec.mdnodeProperties.mluabt-spec.mdpropertyType.typeAlso read for the smallest valid tree, for a Composite+Decorator+Action+Blackboard example with all optional fields populated, for fixed graph rules, and any existing in the project () to mirror conventions. Replace in the skeletons with the from — both at the top level and inside every type string in Blackboard variables and .
references/skeleton-minimal.jsonreferences/skeleton-full.jsonreferences/node-catalog.md.behaviourtree**/*.behaviourtree{CORE_VERSION}CoreVersionbt-spec.mdMOD.Core.*nodeProperties该规范是所有项目专属数据的唯一可信来源:每个自定义动作/装饰器节点的 、、可见 名称,以及与项目 绑定的序列化 字符串。
definitionIdbtNodeTypepropertyKeyCoreVersionType.type何时(重新)构建:
- 首次在项目中处理行为树(尚未创建 )。
.behaviourDocs/bt-spec.md - 任何影响行为树节点表面定义的变更之后:
- 新增/重命名/删除配对 文件继承自
.mlua/ActionNode的DecoratorNode.codeblock - 此类 文件中新增/删除/重命名
.mlua行property - 中的
Environment/config升级(序列化类型字符串带有版本标记)。CoreVersion
- 新增/重命名/删除配对
- 用户表示最近新增/修改了行为树代码块或 属性——过期的UUID/缺失的属性会导致生成的行为树静默失效。
.mlua - 下游验证(步骤7)标记了 、
definitionId或版本不匹配问题。propertyKey
运行方式 — 调用该技能的本地脚本:
bash
node "<SKILL_PATH>/scripts/build-spec.cjs" --projectRoot "<MSW project root>"如果当前工作目录已是MSW项目根目录,则可省略 参数。要求Node.js已添加到 (无其他依赖——仅使用标准库 /)。
--projectRootPATHfspath可选覆盖参数(长标记,大小写不敏感):
| 标记 | 默认值 | 说明 |
|---|---|---|
| 当前工作目录 | 要扫描的MSW项目根目录 |
| | 目标目录不存在时会自动创建 |
| 从 | 当配置文件缺失时为必填项 |
带覆盖参数的示例:
bash
node "<SKILL_PATH>/scripts/build-spec.cjs" --projectRoot "C:/path/to/project" --coreVersion 26.5.0.0如果 文件缺失且未传入 参数,脚本会抛出错误——无 fallback 默认值。
Environment/config--coreVersion规范包含内容:
- 项目元数据——项目根目录、、生成时间、已发现节点数量。
CoreVersion - 内置组合节点名称及其固定的 /
definitionId。btNodeType - 自定义动作节点——、
Name、definitionId、可见属性名称。btNodeType - 自定义装饰器节点——与动作节点结构相同。
- 类型映射——mlua类型到序列化 以及Blackboard
MODNativeType.type结构。ObjectValue
UUID来自项目中的真实 文件——规范绝不会自行生成UUID。 属性会被自动过滤。固定的编写规则、文件骨架和验证清单存储在该技能的 目录中,而非生成的规范内。
.codeblock@HideFromInspectorreferences/(重新)构建后,阅读新生成的 并继续执行后续步骤。精简规范仅列出属性名称;构建 时,需从配对的 文件中解析每个属性的mlua类型/默认值,然后使用 第4节中的类型映射获取 。
<ProjectRoot>/.behaviourDocs/bt-spec.mdnodeProperties.mluabt-spec.mdpropertyType.type同时阅读 了解最小有效行为树结构,阅读 查看包含组合+装饰器+动作+Blackboard的完整示例(所有可选字段均已填充),阅读 了解固定图规则,以及项目中已有的 文件()以遵循现有约定。将骨架中的 替换为 中的 ——包括顶层字段以及Blackboard变量和 中所有 类型字符串内的版本标记。
references/skeleton-minimal.jsonreferences/skeleton-full.jsonreferences/node-catalog.md.behaviourtree**/*.behaviourtree{CORE_VERSION}bt-spec.mdCoreVersionnodePropertiesMOD.Core.*1. Collect input from the user
1. 收集用户输入
Confirm via context, or ask via AskUserQuestion if anything is ambiguous:
| Item | Description | Example |
|---|---|---|
| Display name for the tree | |
| Save path | | |
| Tree shape | Intended node graph (root composite + children) | |
| Custom nodes | Action/decorator codeblocks the tree references | |
| Blackboard variables | Variable name + type + initial value | |
| Node properties | For each custom node, which property maps to which Blackboard variable | |
Custom-node existence check (mandatory): every custom action/decorator name the user mentions must appear in §2 / §3. If a referenced node is not in the spec, stop and ask the user — do not invent a UUID, do not assume a node exists by name, and do not skip rerunning Step 0.
bt-spec.md通过上下文确认,若存在歧义则通过AskUserQuestion询问:
| 项 | 描述 | 示例 |
|---|---|---|
| 行为树的显示名称 | |
| 保存路径 | | |
| 树结构 | 预期的节点图(根组合节点+子节点) | |
| 自定义节点 | 行为树引用的动作/装饰器代码块 | |
| Blackboard变量 | 变量名称+类型+初始值 | |
| 节点属性 | 每个自定义节点的属性与Blackboard变量的映射关系 | |
自定义节点存在性检查(必填): 用户提及的每个自定义动作/装饰器名称必须出现在 的第2/3节中。若引用的节点未在规范中找到,立即停止并询问用户——不得自行生成UUID,不得假设节点按名称存在,不得跳过重新执行步骤0。
bt-spec.md2. Mint UUIDs
2. 生成UUID
You need:
- One UUID for the file → goes into and
EntryKey(both identical, both prefixedContentProto.Json.id).behaviourtree:// - One UUID for each node in (
Nodes).nodeId
bash
node -e "console.log(require('node:crypto').randomUUID())"Mint up front, write into a scratch table, then assemble. Don't reuse the file UUID as a .
nodeId需要生成:
- 一个文件UUID → 填入 和
EntryKey(两者完全相同,均以ContentProto.Json.id为前缀)。behaviourtree:// - 中每个节点对应一个UUID(
Nodes)。nodeId
bash
node -e "console.log(require('node:crypto').randomUUID())"提前生成所有UUID,写入临时表后再进行组装。不得将文件UUID复用为 。
nodeId3. Resolve every definitionId
definitionId3. 解析所有 definitionId
definitionId| Node category | | |
|---|---|---|
Built-in composite ( | Same string as | |
| Custom action node | value from | |
| Custom decorator node | value from | |
Custom-node UUIDs come from (which read them from real files) — never any other source.
bt-spec.md.codeblock| 节点类别 | | |
|---|---|---|
内置组合节点( | 与 | |
| 自定义动作节点 | 来自 | |
| 自定义装饰器节点 | 来自 | |
自定义节点的UUID来自 (从真实 文件读取)——绝不能来自其他来源。
bt-spec.md.codeblock4. Build the Blackboard
4. 构建Blackboard
For each variable, copy the string and shape verbatim from §4. The version-tagged substring () must match exactly — a typo silently breaks deserialization.
Type.typeObjectValuebt-spec.mdVersion=<CoreVersion>Variables{ Name, Type: { "$type": "MODNativeType", type: "<from spec>" }, ObjectValue: <from spec> }ObjectValue$typeValue.modelFor / , is (engine component) or (script component). Mirror an existing serialized example in the project.
ComponentComponentRefComponentId<entity-uuid>:<ComponentName><entity-uuid>:<scriptCodeblockUuid>:<ScriptComponentName>Numeric s use float literal form (, not ).
ObjectValue3.03对于每个变量,直接从 第4节复制 字符串和 结构。带版本标记的子串()必须完全匹配——拼写错误会导致反序列化静默失败。
bt-spec.mdType.typeObjectValueVersion=<CoreVersion>Variables{ Name, Type: { "$type": "MODNativeType", type: "<来自规范>" }, ObjectValue: <来自规范> }ObjectValue$type.modelValue对于 /, 格式为 (引擎组件)或 (脚本组件)。参考项目中已有的序列化示例。
ComponentComponentRefComponentId<entity-uuid>:<ComponentName><entity-uuid>:<scriptCodeblockUuid>:<ScriptComponentName>数值型 使用浮点数字面量(,而非 )。
ObjectValue3.034.5 Resolve node property values
4.5 解析节点属性值
For each custom node that needs :
nodeProperties- Confirm the exists in
propertyKey§2 / §3 for that node.bt-spec.md - Find the paired by searching for
.mluaorscript <NodeName> extends ActionNodeunder the project. If multiple files match, prefer the one whose siblingscript <NodeName> extends DecoratorNodehas the exact.codeblockUUID fromdefinitionId; if still ambiguous, ask the user.bt-spec.md - Read the visible declarations in that
property, ignoring.mluaproperties. This gives the mlua type and default value.@HideFromInspector - Include a entry only when the user provided a value, the behavior requires a non-default value, or a
nodePropertiesproperty must point at a Blackboard variable. Omit optional properties that can safely use the*Keydefault..mlua - For string properties, set
*Keyto the Blackboard variable name. Infer the variable by name and getter usage when obvious (propertyValue->MoveSpeedKey,MoveSpeed->TargetEntityKey). If more than one Blackboard variable could match, ask.TargetEntity - For literal properties, use the user-provided value. If no value is provided and the default is meaningful, omit the property instead of serializing a guessed value.
.mlua - If checks a property for
OnBehave, empty string, or invalid enum and no value can be inferred, ask the user before writing the tree.nil
nodePropertiesjson
{
"propertyKey": "<property name>",
"propertyType": { "$type": "MODNativeType", "type": "<type from bt-spec.md §4>" },
"propertyValue": <value>
}对于每个需要 的自定义节点:
nodeProperties- 确认 存在于该节点对应的
propertyKey第2/3节中。bt-spec.md - 通过搜索项目中包含 或
script <NodeName> extends ActionNode的文件找到配对的script <NodeName> extends DecoratorNode。若存在多个匹配文件,优先选择其同级.mlua的.codeblock与definitionId中UUID完全一致的文件;若仍存在歧义,询问用户。bt-spec.md - 读取该 中的可见
.mlua声明,忽略property属性。由此获取mlua类型和默认值。@HideFromInspector - 仅当用户提供了值、行为需要非默认值,或 属性必须指向Blackboard变量时,才添加
*Key条目。可安全使用nodeProperties默认值的可选属性应省略。.mlua - 对于 字符串属性,将
*Key设置为Blackboard变量名称。当名称和 getter 使用明显匹配时(如propertyValue→MoveSpeedKey、MoveSpeed→TargetEntityKey),自动推断变量。若存在多个可能匹配的Blackboard变量,询问用户。TargetEntity - 对于字面量属性,使用用户提供的值。若未提供值且 默认值有效,则省略该属性,而非序列化猜测的值。
.mlua - 若 检查属性是否为
OnBehave、空字符串或无效枚举,且无法推断值,则在写入行为树前询问用户。nil
nodePropertiesjson
{
"propertyKey": "<属性名称>",
"propertyType": { "$type": "MODNativeType", "type": "<来自bt-spec.md第4节的类型>" },
"propertyValue": <值>
}5. Assemble Nodes
5. 组装Nodes
Hard graph constraints (validate before writing):
- RootNode is not a parent node. It must not have . It only stores
childNodes, andstartNodeIdpoints to exactly one node instartNodeId.Nodes - If the tree needs several top-level behaviors, use either one Composite as the single , or one Decorator as the single
startNodeIdwhosestartNodeIdwraps a Composite or another Decorator chain that eventually wraps a Composite. Put the multiple behaviors under that Composite'sdecoChildNodes.childNodes - Exactly one node in may have
Nodes: the node referenced bynodeParentId: "". Do not create multiple root-level Action/Composite/Decorator nodes.RootNode.startNodeId - Composite () is the only node category that can own multiple children through
btNodeType: 1.childNodes - Decorator () is only a wrapper/parent for exactly one Action, Composite, or Decorator node. It can also be the child of another Decorator, so Decorator-to-Decorator chains are valid. It must use singular
btNodeType: 2(a singledecoChildNodesstring) for that one child, notnodeId; the wrapped child must also record the Decorator's id in itschildNodes.nodeParentId - Decorators applying to the same Action MUST be chained — never flattened as siblings. Each decorator owns exactly one downstream subtree. If two or more decorators are meant to gate/modify the same Action, build a single chain where each decorator's
Composite → ADeco → BDeco → CDeco → Actionpoints to the next decorator (and finally the Action). Concretely: within one chain leading to a single Action, no two decorators may share the samedecoChildNodes— each decorator's parent is the previous decorator, and only the topmost decorator's parent is the Composite. Sibling decorators under one Composite are still valid when each wraps a different downstream subtree. ✅nodeParentId(chain — every decorator has a unique parent within the chain). ❌Composite → ADeco → BDeco → CDeco → Action(Action duplicated to bypass chaining). ❌Composite → [ADeco→Action, BDeco→Action, CDeco→Action](decorators flattened — they don't wrap the Action and are effectively orphaned).Composite → [ADeco, BDeco, CDeco, Action] - Action () is a leaf — never has children.
btNodeType: 0
Node-write invariants:
- Every is unique within the file.
nodeId - of every non-root node points to a real
nodeParentIdthat is a Composite or Decorator. It must never point tonodeId, becauseRootNodeis not represented as a node inRootNode.Nodes - If a node's parent is a Composite, that Composite must include the node id in .
childNodes - If a node's parent is a Decorator, that Decorator's must equal that node's
decoChildNodes. This is valid even when both parent and child are Decorators.nodeId - Composite ↔ child
childNodesis bidirectionally consistent.nodeParentId - Action nodes omit . Decorator nodes omit
childNodesand use exactly onechildNodes(single stringdecoChildNodes) instead.nodeId - Never write . The editor strips this field on round-trip, and the supported composites (
probability,SequenceNode,SelectorNode) do not consume per-child weights. Older generated trees in the project may still carryParallelNodeon every node; treat that as legacy on read but do not write it on new nodes."probability": 1.0 - Decorator nodes () omit
btNodeType: 2. The editor positions a Decorator automatically relative to the child it wraps, and writes nonodePositionfield for it on save. Only Composites and Actions carrynodePosition. ThenodePositionblock also carries its ownRootNode(separate from the start node).nodePosition - Empty collection fields are omitted, not serialized as . A Composite with no children yet should omit
[]entirely; a node with no overrides should omitchildNodesentirely. Empty arrays are an editor-draft artifact — do not author them.nodeProperties - Decorator child field is (canonical — this is what the editor preserves on save;
decoChildNodesis silently stripped on round-trip). It is a single string holding the wrapped child'sChildNodeId(not an array). When reading legacy files you may still encounternodeIdon hand-authored decorators; treat it as the same field. When writing, always emitChildNodeId.decoChildNodes - references one of the
RootNode.startNodeIds — an Action, Composite, or Decorator — and that node is the only node withnodeId.nodeParentId: ""
*KeyBlackBoard:GetXxxKey严格的图约束(写入前必须验证):
- RootNode不是父节点。它不得包含 。仅存储
childNodes,且startNodeId指向startNodeId中恰好一个节点。Nodes - 若行为树需要多个顶层行为,可将单个组合节点作为唯一的 ,或将单个装饰器作为唯一的
startNodeId,其startNodeId包裹一个组合节点或另一个装饰器链(最终包裹组合节点)。将多个行为置于该组合节点的decoChildNodes下。childNodes - 中恰好有一个节点的
Nodes:即nodeParentId: ""引用的节点。不得创建多个顶层动作/组合/装饰器节点。RootNode.startNodeId - 组合节点()是唯一可通过
btNodeType: 1拥有多个子节点的节点类别。childNodes - 装饰器节点()仅能作为恰好一个动作、组合或装饰器节点的包装器/父节点。它也可以是另一个装饰器的子节点,因此装饰器到装饰器的链是有效的。必须使用单数形式的
btNodeType: 2(单个decoChildNodes字符串)指向该子节点,而非nodeId;被包裹的子节点必须将装饰器的ID记录在其childNodes中。nodeParentId - 应用于同一动作的装饰器必须链式排列——绝不能平级作为兄弟节点。每个装饰器仅拥有一个下游子树。若两个或多个装饰器用于控制/修改同一动作,需构建单个链 ,其中每个装饰器的
Composite → ADeco → BDeco → CDeco → Action指向下一个装饰器(最终指向动作)。具体规则:在指向单个动作的链中,任意两个装饰器不得拥有相同的decoChildNodes——每个装饰器的父节点是前一个装饰器,只有最顶层的装饰器父节点是组合节点。当每个装饰器包裹不同的下游子树时,组合节点下的兄弟装饰器仍然有效。✅nodeParentId(链式结构——链中每个装饰器的父节点唯一)。❌Composite → ADeco → BDeco → CDeco → Action(动作重复以绕过链式结构)。❌Composite → [ADeco→Action, BDeco→Action, CDeco→Action](装饰器平级排列——未包裹动作,相当于孤立节点)。Composite → [ADeco, BDeco, CDeco, Action] - 动作节点()是叶子节点——绝不能有子节点。
btNodeType: 0
节点写入规则:
- 文件内每个 必须唯一。
nodeId - 所有非根节点的 必须指向真实的组合或装饰器节点的
nodeParentId。绝不能指向nodeId,因为RootNode未在RootNode中作为节点表示。Nodes - 若节点的父节点是组合节点,该组合节点必须将该节点ID包含在 中。
childNodes - 若节点的父节点是装饰器节点,该装饰器的 必须等于该节点的
decoChildNodes。即使父节点和子节点均为装饰器,此规则依然有效。nodeId - 组合节点的 ↔ 子节点的
childNodes必须双向一致。nodeParentId - 动作节点省略 。装饰器节点省略
childNodes,并使用恰好一个childNodes(单个decoChildNodes字符串)替代。nodeId - 绝不能写入 字段。编辑器在往返序列化时会移除该字段,且支持的组合节点(
probability、SequenceNode、SelectorNode)不使用子节点权重。项目中旧的生成树可能仍在每个节点上带有ParallelNode;读取时视为遗留字段,但写入新节点时不得包含。"probability": 1.0 - 装饰器节点()省略
btNodeType: 2。编辑器会自动根据其包裹的子节点定位装饰器,保存时不会为其写入nodePosition字段。仅组合节点和动作节点带有nodePosition。nodePosition块也带有自己的RootNode(与起始节点分离)。nodePosition - 空集合字段应省略,而非序列化为 。尚无子节点的组合节点应完全省略
[];无覆盖属性的节点应完全省略childNodes。空数组是编辑器草稿阶段的产物——编写时不得包含。nodeProperties - 装饰器子节点字段为 (标准格式——编辑器保存时会保留此格式;
decoChildNodes在往返序列化时会被静默移除)。它是一个存储被包裹子节点ChildNodeId的单个字符串(而非数组)。读取遗留文件时可能仍会在手动编写的装饰器上遇到nodeId;视为同一字段处理。写入时,始终输出ChildNodeId。decoChildNodes - 引用其中一个
RootNode.startNodeId——动作、组合或装饰器节点——且该节点是唯一拥有nodeId的节点。nodeParentId: ""
后缀为 的字符串属性存储Blackboard变量的名称(运行时通过 解析)。非 属性存储字面量值。
*KeyBlackBoard:GetXxxKey6. nodePosition format
6. nodePosition格式
nodePositionxyjson
"nodePosition": { "x": 0.0, "y": 0.0 }Use float literals (, not ). The legacy string form may still appear in older hand-authored trees — read it as equivalent, but always write the object form (the BT editor canonicalizes to this shape on save, so the string form re-serializes to a noisy diff the first time the file is opened).
0.00"(0.000, 0.000)"Editor axes: the BT editor uses a math-convention canvas — +x is right, +y is up (upper-right quadrant is positive). So a child placed at a higher y than its parent appears above the parent on screen.
Layout rule — draw the tree downward: depth grows along −y (children sit below their parent), and siblings spread along ±x around the parent's x. Typical spacing: 200 units between depth levels and 200 units between siblings.
RootNodeRootNode.nodePosition{ "x": 0.0, "y": 0.0 }startNodeId(0, 0)- :
RootNode.nodePosition(fixed anchor — never moves){ "x": 0.0, "y": 0.0 } - Start node (depth 1, referenced by ):
startNodeId{ "x": 0.0, "y": -200.0 } - Single child of the start node (depth 2):
{ "x": 0.0, "y": -400.0 } - Two children of the start node (depth 2): and
{ "x": -100.0, "y": -400.0 }{ "x": 100.0, "y": -400.0 } - Each additional level: parent.y − 200
Never place a child at a y greater than or equal to its parent's y — that draws upward and overlaps the parent visually. The same rule applies between and the start node: the start node must be at (strictly below the anchor).
RootNodey ≤ -200Decorator nodes do not carry . The editor lays them out automatically relative to the wrapped child. Omit the field on every node; it appears only on , Composites (), and Actions ().
nodePositionbtNodeType: 2RootNodebtNodeType: 1btNodeType: 0nodePositionxyjson
"nodePosition": { "x": 0.0, "y": 0.0 }使用浮点数字面量(,而非 )。旧的手动编写树中可能仍存在遗留字符串格式 ;读取时视为等效,但写入时始终使用对象格式(行为树编辑器保存时会规范化为此格式,因此字符串格式首次打开文件时会产生无意义的差异)。
0.00"(0.000, 0.000)"编辑器坐标轴:行为树编辑器使用数学规范画布——+x向右,+y向上(右上象限为正)。因此,子节点的y值高于父节点时,在屏幕上显示为位于父节点上方。
布局规则——向下绘制树:深度沿 −y 方向增长(子节点位于父节点下方),兄弟节点沿 ±x 方向围绕父节点x轴分布。典型间距:深度层级间200单位,兄弟节点间200单位。
RootNodeRootNode.nodePosition{ "x": 0.0, "y": 0.0 }startNodeId(0, 0)- :
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值——这会导致向上绘制并与父节点视觉重叠。此规则同样适用于 和起始节点:起始节点的y值必须 ≤ -200.0(严格位于锚点下方)。
RootNode装饰器节点不携带 。编辑器会自动根据其包裹的子节点进行布局。所有 节点均省略该字段;仅 、组合节点()和动作节点()携带该字段。
nodePositionbtNodeType: 2RootNodebtNodeType: 1btNodeType: 07. Write and validate
7. 写入与验证
Write the JSON file, then run this checklist. In particular:
- is
EntryKeyand matchesbehaviourtree://{uuid}exactly.ContentProto.Json.id - Top-level ,
Id,GameIdareContent."",Usage,UseServiceareDynamicLoading.0isUsePublish.1matches the project (CoreVersion).Environment/configisStudioVersion.0.1.0.0isContentType.x-mod/behaviourtreeisContentProto.Use.Json - has no
RootNode;childNodesmatches exactly oneRootNode.startNodeIdinnodeId; that start node hasNodes; and no other node hasnodeParentId: "".nodeParentId: "" - Every is
nodeParentIdor an existing"".nodeId - All values are unique.
nodeId - For every Composite, the set of IDs equals the set of nodes whose
childNodesis this Composite.nodeParentId - Every Action has no . Every Decorator has no
childNodes, has exactly onechildNodes(singledecoChildNodesstring —nodeIdis the legacy variant; the editor strips it on round-trip), and that id points to exactly one Action, Composite, or Decorator child whoseChildNodeIdpoints back to the Decorator. Decorator-to-Decorator parent/child chains are valid and must be checked with the samenodeParentId↔decoChildNodesrule.nodeParentId - Decorator chain rule: when multiple decorators apply to the same Action, they form a single chain (). Verify by walking each Action upward to its enclosing Composite: the decorators encountered along that one path must all have unique
Composite → ADeco → BDeco → … → Actionvalues (i.e. each decorator's parent is the previous decorator, never another decorator that already appeared in the chain). Two decorators in the same chain sharing anodeParentIdis invalid. (Sibling decorators under one Composite that wrap different downstream subtrees are fine — uniqueness is per-chain, not global.)nodeParentId - No node serializes . (Legacy
"probability"values may appear on read but are never authored.)1.0 - Every Composite and Action carries in object form
nodePositionwith float literals — no legacy{ "x": <num>, "y": <num> }strings on write. Decorator nodes carry no"(x.xxx, y.yyy)"at all.nodePosition - Start node is not stacked on the RootNode anchor. is
RootNode.nodePositionand the node referenced by{ "x": 0.0, "y": 0.0 }hasstartNodeId(typicallyy ≤ -200.0). If the start node is a Decorator (no{ "x": 0.0, "y": -200.0 }), the first wrapped Composite/Action down the chain must satisfy this offset instead.nodePosition - No node serializes empty arrays — a Composite with no children omits ; a node with no overrides omits
childNodes. Do not writenodePropertiesor"childNodes": []."nodeProperties": [] - Every custom node's is copied from
definitionId(never invented).bt-spec.md - Every matches a property in
nodeProperties[].propertyKeyfor that node.bt-spec.md - Every property's
*Keymatches apropertyValueof the right type.Blackboard.Variables[].Name - Every type string is copied verbatim from §4 — version-tagged, typo-fragile.
bt-spec.md - Version cross-check: every type string's
MOD.Core.*substring (inVersion=X.Y.Z.ZandBlackboard.Variables[].Type.type) equals the file's top-levelNodes[].nodeProperties[].propertyType.type. Mismatch silently breaks deserialization — common whenCoreVersionis stale relative to the project's currentbt-spec.md. If they differ, re-run Step 0 before writing. (CoreVersiontypes use the immutableSystem.*and are exempt.)Version=4.0.0.0 - JSON parses:
bash
node -e "JSON.parse(require('node:fs').readFileSync(process.argv[1],'utf8'))" "<path>"
If any check fails, fix it before reporting done.
写入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 - 每个组合节点的 ID集合与
childNodes指向该组合节点的节点集合完全一致。nodeParentId - 所有动作节点无 。所有装饰器节点无
childNodes,且恰好有一个childNodes(单个decoChildNodes字符串——nodeId是遗留变体;编辑器往返序列化时会移除),且该ID指向恰好一个动作、组合或装饰器子节点,其ChildNodeId指向该装饰器。装饰器到装饰器的父/子链有效,必须使用相同的nodeParentId↔decoChildNodes规则进行检查。nodeParentId - 装饰器链规则:当多个装饰器应用于同一动作时,必须形成单个链()。通过从每个动作向上遍历至其所属的组合节点进行验证:该路径上遇到的所有装饰器必须拥有唯一的
Composite → ADeco → BDeco → … → Action值(即每个装饰器的父节点是前一个装饰器,而非链中已出现的其他装饰器)。同一链中两个装饰器拥有相同的nodeParentId是无效的。(组合节点下包裹不同下游子树的兄弟装饰器是允许的——唯一性是针对单条链,而非全局。)nodeParentId - 无节点序列化 字段。(读取时可能会遇到遗留的
"probability"值,但编写时绝不包含。)1.0 - 所有组合节点和动作节点均携带对象格式的
nodePosition,且使用浮点数字面量——写入时不得使用遗留的{ "x": <数值>, "y": <数值> }字符串。装饰器节点完全不携带"(x.xxx, y.yyy)"。nodePosition - 起始节点未与RootNode锚点重叠。为
RootNode.nodePosition,{ "x": 0.0, "y": 0.0 }引用的节点的y值 ≤ -200.0(通常为startNodeId)。若起始节点是装饰器(无{ "x": 0.0, "y": -200.0 }),则链中第一个被包裹的组合/动作节点必须满足此偏移要求。nodePosition - 无节点序列化空数组——无子女的组合节点省略 ;无覆盖属性的节点省略
childNodes。不得写入nodeProperties或"childNodes": []。"nodeProperties": [] - 所有自定义节点的 均从
definitionId复制(绝不自行生成)。bt-spec.md - 所有 均与该节点在
nodeProperties[].propertyKey中的属性匹配。bt-spec.md - 所有 属性的
*Key均与对应类型的propertyValue匹配。Blackboard.Variables[].Name - 所有类型字符串均直接从 第4节复制——带有版本标记,对拼写错误敏感。
bt-spec.md - 版本交叉检查:所有 类型字符串中的
MOD.Core.*子串(位于Version=X.Y.Z.Z和Blackboard.Variables[].Type.type中)与文件顶层的Nodes[].nodeProperties[].propertyType.type完全一致。版本不匹配会导致反序列化静默失败——常见于CoreVersion相对于项目当前bt-spec.md过期的情况。若版本不一致,写入前重新执行步骤0。(CoreVersion类型使用固定的System.*,不受此规则限制。)Version=4.0.0.0 - JSON可正常解析:
bash
node -e "JSON.parse(require('node:fs').readFileSync(process.argv[1],'utf8'))" "<路径>"
若任何检查未通过,修复后再报告完成。
📂 Files in / consumed by this skill
📂 该技能包含/使用的文件
- — Node.js script that scans the project and emits
scripts/build-spec.cjs. Invoked in Step 0.<ProjectRoot>/.behaviourDocs/bt-spec.md - — compact generated catalog. Source of truth for node names,
<ProjectRoot>/.behaviourDocs/bt-spec.md,definitionId, property names, and type strings. Written by the script above; consumed by Steps 1–7.btNodeType - — smallest valid tree (empty Blackboard, single Composite root with no children).
references/skeleton-minimal.json - — Composite → Decorator → Action with
references/skeleton-full.json(literal +nodeProperties-suffix) and a populatedKey. Use this as the shape reference whenever the tree is non-trivial.Blackboard - — narrative explanation of
references/node-catalog.mdvalues, valid graph shapes, thebtNodeType-suffix convention, and how the spec builder discovers nodes (kept for reference; the runtime catalog itself lives inKey).bt-spec.md
- — Node.js脚本,扫描项目并生成
scripts/build-spec.cjs。在步骤0中调用。<ProjectRoot>/.behaviourDocs/bt-spec.md - — 精简的生成目录。节点名称、
<ProjectRoot>/.behaviourDocs/bt-spec.md、definitionId、属性名称和类型字符串的唯一可信来源。由上述脚本生成;供步骤1–7使用。btNodeType - — 最小有效行为树(空Blackboard,无子女的单个组合根节点)。
references/skeleton-minimal.json - — 包含组合→装饰器→动作结构,带有
references/skeleton-full.json(字面量+后缀为Key的属性)和已填充的nodeProperties。当行为树非极简时,以此作为结构参考。Blackboard - — 关于
references/node-catalog.md值、有效图结构、Key后缀约定以及规范构建器如何发现节点的说明文档(仅作参考;运行时目录本身存储在btNodeType中)。bt-spec.md
🔁 Edit workflow (existing file)
🔁 编辑工作流(现有文件)
- Read the entire file — never Edit blind. UUIDs and the parent/child graph must stay consistent.
- Never change the file's wrapper UUID (/
EntryKey) — external references break.ContentProto.Json.id - Adding a node: mint a fresh , append to
nodeId, update the parent Composite'sNodes, set the new node'schildNodes.nodeParentId - Removing a node: remove from , remove its ID from any Composite's
Nodes. If it was a Composite, decide whether to re-parent or remove its children — never leave danglingchildNodesreferences.nodeParentId - If the edit involves a custom node name, property, or type that may have changed in the project since the spec was last built, re-run Step 0 first.
- Re-run the Step 7 validation checklist after every edit.
- 读取整个文件——绝不盲目编辑。UUID和父/子图必须保持一致。
- 绝不能修改文件的包装UUID(/
EntryKey)——否则会破坏外部引用。ContentProto.Json.id - 添加节点:生成新的 ,追加到
nodeId,更新父组合节点的Nodes,设置新节点的childNodes。nodeParentId - 删除节点:从 中移除,从任何组合节点的
Nodes中移除其ID。若删除的是组合节点,需决定是重新父化还是移除其子节点——绝不能留下悬空的childNodes引用。nodeParentId - 若编辑涉及自定义节点名称、属性或类型,且自上次构建规范后项目中可能发生了变更,先重新执行步骤0。
- 每次编辑后重新执行步骤7的验证检查清单。