关于 composites
为 Decentraland 场景开发提供 composite 格式与编辑指导,供创建或修改场景实体的 Agent 使用。说明 Creator Hub MCP 优先工作流、场景边界计算和校验要求。
主要功能
- 01Creator Hub MCP 工作流门槛
- 02根据地块计算场景边界
- 03GLB 模型边界框检查
- 04Composite 格式参考
- 05创建与编辑校验清单
适用场景
- 01在 Creator Hub 中编辑场景实体
- 02在文件模式下创建 composite
- 03检查模型是否超出场景边界
MCP 相关
说明 Creator Hub MCP 工具可用于实时创建实体、设置组件和场景设置,并将其作为手工编辑 composite 文件的优先方式。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ decentraland/ sdk-skills --skill composites安装后在 Claude Code 中输入/composites确认已加载。
附带文件composite-reference.md
详细介绍
复合体
此技能包含其他 Decentraland 技能(create-scene、add-3d-models、sdk-scenes)共用的复合体格式参考。
以下强制工作流适用于你编写或编辑的每个复合体:先检查 Creator Hub MCP 门槛,计算场景边界(步骤 0),编写实体时查阅参考资料中的格式目录,并在结束前运行验证门槛。
门槛 — 使用 Creator Hub MCP,而非编辑文件(先检查)
手动编辑 main.composite 是备用方案,而非默认做法。 Creator Hub 提供了一个 MCP 服务(技能:creator-hub-mcp),其工具——scene_state、create_entity、set_component、remove_entity、place_smart_item、attach_script、set_scene_settings,等等——可以实时更改已打开的场景:视口会更新,编辑器会自动保存复合体本身,inspector::Nodes 和编辑器的其他簿记信息会保持一致,并且用户可以撤销每项更改。反之,编辑文件可能导致典型问题:编辑器的自动保存会重新生成整个文件,你的工作会悄无声息地消失。
修改文件之前,请选择第一个符合条件的条目:
- 当前会话中提供 Creator Hub MCP 工具(Claude Code 中的
mcp__creator-hub__*;Creator Hub 自带 AI 助手中的裸scene_state/create_entity)→ 不要手动编辑main.composite、main.crdt或scene.json。所有实体、组件和设置的更改都使用这些工具。此技能中的格式目录仍可作为组件形状的参考——set_component接受的 JSON 与复合体中存储的 JSON 相同;步骤 0(边界)仍然适用。 - Creator Hub 正在运行,但工具尚未连接 → 告知用户 MCP 是更安全的方案,并说明如何连接(
{baseDir}/../creator-hub-mcp/reference/connect.md:Settings > AI > Expose AI assistant MCP server,复制代码片段)。只有用户拒绝后,才可改为编辑文件;且只能在 Creator Hub 中关闭场景后进行。如果场景已打开(或可能已打开),而 MCP 未连接,则必须暂停并询问:提供关闭场景、连接 MCP 或通过代码构建这几种选择,然后等待用户回复。是否改为在src/中构建静态实体由用户决定,绝不能由你决定——无论是为了搭建灰盒,还是因为会话处于非交互模式(规范规则:sdk-scenes → Agent Behavioral Guidelines,第 5 项)。 - 没有使用 Creator Hub(仅 CLI 项目、CI、应用已关闭且用户希望保持关闭)→ 完整采用以下文件工作流。
步骤 0 — 读取 scene.json 并计算边界(必须执行)
在编写任何实体位置之前,读取 scene.json 并计算场景边界。 必须先执行此步骤——所有实体位置都必须在这些边界内,否则将无法渲染。
如何计算边界
- 打开
scene.json,找到scene.parcels(由"x,y"字符串组成的数组)和scene.base。 - 将每个地块解析为整数。找出所有地块中 X 和 Y 的最小值与最大值。
- 计算:
parcelsWide = max(parcel_x) - min(parcel_x) + 1
parcelsDeep = max(parcel_y) - min(parcel_y) + 1
maxX = parcelsWide * 16
maxZ = parcelsDeep * 16
- 有效实体位置:X 在 [0, maxX] 内,Z 在 [0, maxZ] 内。负值以及大于 maxX/maxZ 的值均在场景范围之外,不会渲染。
步骤 0b — 计算 3D 模型的包围盒(GLB 模型必须执行)
模型的 Transform.position 是其局部原点,不是其视觉范围。 树木和植被模型通常会在一个或多个方向上延伸到原点之外 6–12 m。将树木放置在 x=2 处,可能导致其渲染在 x=–10 处,从而超出场景边界。
如何查找模型的包围盒 — 解析 GLB 二进制文件并应用节点级变换。仅凭原始 accessor 的 min/max 值并不可靠,因为许多 GLB 模型在 GLTF 节点层级中烘焙了较大的缩放系数或平移值(例如,某个模型的 accessor 显示为 0.6 m,但节点缩放为 24×,实际渲染尺寸便是 14 m)。
node -e "
const buf = require('fs').readFileSync('assets/Models/MyModel.glb');
const jsonLen = buf.readUInt32LE(12);
const json = JSON.parse(buf.slice(20, 20+jsonLen));
let minW=[Infinity,Infinity,Infinity], maxW=[-Infinity,-Infinity,-Infinity];
json.nodes?.forEach(n => {
if (n.mesh === undefined) return;
const s = n.scale || [1,1,1];
const t = n.translation || [0,0,0];
for (const prim of json.meshes[n.mesh].primitives) {
const acc = json.accessors[prim.attributes.POSITION];
if (!acc.min || !acc.max) continue;
for (let i = 0; i < 3; i++) {
const lo = acc.min[i]*s[i]+t[i], hi = acc.max[i]*s[i]+t[i];
minW[i] = Math.min(minW[i], lo, hi);
maxW[i] = Math.max(maxW[i], lo, hi);
}
}
});
const w=maxW[0]-minW[0], h=maxW[1]-minW[1], d=maxW[2]-minW[2];
console.log('Rendered size:', w.toFixed(2)+'m x', h.toFixed(2)+'m x', d.toFixed(2)+'m');
console.log('World min:', minW.map(v=>v.toFixed(2)), 'max:', maxW.map(v=>v.toFixed(2)));
"
逐个模型测量,不要猜测或硬编码。 模型范围差异很大:对典型树木运行上述脚本,通常会发现其在某个水平方向上从原点延伸约 11 m(安全的最小原点 z≥12);而柱子在各个方向上的延伸都不到 1 m。务必计算你要放置的特定 GLB 的包围盒。
规则: 对每个 GLB 模型,计算:
minSafeX = max(0, -bbox.minX) + margin (≥1 m)
minSafeZ = max(0, -bbox.minZ) + margin (≥1 m)
maxSafeX = maxX - (bbox.maxX + margin)
maxSafeZ = maxZ - (bbox.maxZ + margin)
只有当模型的 Transform 位置满足所有四项边界条件时,才放置该模型。
对于包围盒未知的树木/植被模型,假设其距所有边缘有12 m 的安全缓冲距离——也就是说,将原点放在 [12, maxX-12] × [12, maxZ-12] 范围内。
放置建筑时:按第三人称视角的比例规划,而非真实比例。 房间过大会改变楼层平面图所需的地块面积,因此应在确定边界时调整房间尺寸。尺寸说明见 add-3d-models → “规则:按第三人称相机视角放大室内空间”。
示例
| scene.json 地块 | parcelsWide | parcelsDeep | 有效 X | 有效 Z |
|---|---|---|---|---|
["0,0"] | 1 | 1 | 0 – 16 | 0 – 16 |
["0,0","1,0"] | 2 | 1 | 0 – 32 | 0 – 16 |
["0,0","1,0","0,1","1,1"] | 2 | 2 | 0 – 32 | 0 – 32 |
未经用户明确指示,绝不要更改 scene.json 的地块数量
如果设计所需空间超出地块允许范围,请询问用户是否要添加地块。 不要擅自添加,也不要为了避免询问而压缩设计(例如垂直叠放房间,或将房间缩小到第三人称视角比例以下)。并非总能向 scene.json 添加地块,这取决于场景的发布位置。如果发布到 Genesis City,部署钱包必须拥有或租用这些地块;如果发布到 World,则可能可以添加。如果用户拒绝,请在现有地块范围内工作,并尽可能丰富场景内容。
Composite 格式目录(参考)
阅读 {baseDir}/composite-reference.md,了解 main.composite JSON 格式的完整规范:JSON 结构和 jsonSchema 规则、实体 ID 分配、各组件的格式目录(core::Transform、core::GltfContainer、… core::NftShape)、组件分组模式、编辑模式规则(inspector::Nodes)、运行时生成,以及从 TypeScript 获取 composite 实体的模式。
验证清单
步骤 1 — 检测模式。 扫描 composite,查找 inspector::*、composite::root 或 asset-packs::ActionTypes。如果存在其中任意一项,则处于编辑模式——使用下方的编辑模式清单。否则使用从头创作清单。
从零编写检查清单
编写新的复合体之前,请确认:
-
version为1 - 不包含任何
inspector::*组件——包括inspector::Nodes、inspector::SceneMetadata(任何版本)、inspector::Selection、inspector::TransformConfig、inspector::UIState。这些组件均由 Creator Hub 自动生成,在新文件中加入它们会破坏实体树或导致构建错误。 - 不包含
composite::root或asset-packs::ActionTypes——它们由引擎自动生成 - 每个用户实体(512+)都有
core::Transform和core-schema::Name - 复合体中没有重复的实体 ID
- 与通过代码创建且指定了 ID 的实体之间没有重复的实体 ID
-
core::组件不得包含jsonSchema——这是硬性要求;在core::组件中加入jsonSchema会导致 Creator Hub 无法正确解析实体 - 非核心组件(
asset-packs::*、core-schema::*)必须包含jsonSchema(从目录复制) - 所有
GltfContainer.src路径均使用 slug 化名称格式:assets/asset-packs/<slug>/<filename> - 所有引用的资源文件都已下载到磁盘(GLB、音频、图像)
- GltfContainer 上已设置默认碰撞掩码(
visibleMeshesCollisionMask: 0、invisibleMeshesCollisionMask: 3) - 所有位置均在地块边界内——边界已在 Step 0 中根据实际的
scene.json地块列表计算得出。每个实体的 X 都在[0, maxX]范围内,Z 都在[0, maxZ]范围内。负值以及超过 maxX/maxZ 的值都不会渲染。如果用户要求创建“大型”场景,但地块数量未变更,则所有实体都必须位于原始边界内。 - 对于每个
GltfContainer实体:已检查 GLB 是否包含动画(嵌入在文件中的片段名称)。如果包含,则该实体上存在core::Animator组件。包含动画但没有 Animator 的模型会静默循环播放第一个片段,且无法控制。 - 对于每个
GltfContainer实体:已检查 GLB 是否包含碰撞网格(名称中包含字符串_collider的任意网格)。如果包含,则将invisibleMeshesCollisionMask设置为3(CL_POINTER + CL_PHYSICS)以启用它们。如果没有内置碰撞体,则已评估是否需要使用core::MeshCollider盒体/球体来覆盖模型的大致形状(适用于可行走表面、墙壁或可点击对象)。 - 如果复合体中的任何位置存在
asset-packs::Actions、asset-packs::Triggers或asset-packs::States,则实体 0 上必须存在asset-packs::Counter,且其value等于 Actions/Triggers/States 数据中使用的最高id(它是这些 ID 的分配器) - 不包含
{self}、{assetPath}或占位字符串——所有内容均已解析为具体值 - 组件名称使用基础名称(例如
asset-packs::Actions,而非asset-packs::Actions-v1)。切勿使用带版本号的后缀,例如-v3。 - 仅使用核心组件的复合体不需要额外库。如果其中包含
asset-packs::*组件,较旧的 SDK 需要将@dcl/asset-packs作为项目依赖;当前 SDK 则会回退到@dcl/inspector中捆绑的副本
编辑模式检查清单(组合文件中已包含 inspector::*)
- Creator Hub MCP 不可用。 如果会话中包含其工具,请停止——以下所有事项都可由
create_entity/set_component为你完成(请参阅此技能顶部的门槛说明和 creator-hub-mcp)。 - 场景当前未在 Creator Hub 中打开——每次写入
main.composite或scene.json之前都要再次检查,而不是每个会话只检查一次。所有者随时可能重新打开场景;当 Hub 处于打开状态时写入的 parcel 更改曾被其自动保存还原(2026-10)。检查器会自动保存,并从其内存中的引擎整体覆盖main.composite,丢弃外部编辑且不会报错。写入前请让用户关闭场景,并在写入后让用户重新打开。如果场景已打开而 MCP 不可用,请停止并询问——除非用户明确选择,否则不要改为将静态实体移到src/(sdk-scenes → Agent 行为指南,第 5 项,其中也列出了场景处于打开状态的迹象)。
对于你添加的每个新实体 <id>,除了遵循上述从头创作规则(但保留 inspector::* 等,不要删除)之外:
- 已将
<id>追加到inspector::Nodes.data["0"].json.value中 entity-0条目的children数组末尾。 - 已将新条目
{ "entity": <id>, "children": [...] }追加到inspector::Nodes.data["0"].json的value数组中(如果该实体没有自己的子实体,则使用[])。 - 如果
<id>的Transform.parent不为0,则<id>位于父实体的children数组中(而非根实体的数组中)。 -
core-schema::Name.data["<id>"]中有一个{ "json": { "value": "..." } }条目——实体必须有名称才能正确显示在实体树中并可通过代码查找。 -
inspector::TransformConfig.data["<id>"]中有一个{ "json": {} }条目(空对象即可)。 - 已更新
entity-names.ts,在EntityNames中加入新名称,或者保持不变,以便 Creator Hub 在下次保存时重新生成。不要手动编辑自动生成的文件头。 - 未删除或移除现有的
inspector::Nodes、inspector::SceneMetadata-*、inspector::Selection、inspector::TransformConfig、composite::root或asset-packs::ActionTypes。这些由 Creator Hub 管理,必须保留。 -
inspector::SceneMetadata-*保持不变,除非scene.json中的 parcel 已更改(若有更改,布局块必须与scene.json一致)。 - 保留实体
1(PlayerEntity)和2(CameraEntity),并确保它们在inspector::Nodes中仍各自拥有顶层条目{ "entity": 1, "children": [] }/{ "entity": 2, "children": [] }。它们是独立的树根——不得出现在 entity0的children数组(或任何其他children数组)中。
**验证命令(编辑模式):**编辑后,core::Transform.data 中的每个实体 ID 都应同时出现在以下位置:
- 作为顶层条目
{ "entity": <id>, ... }出现在inspector::Nodes.data["0"].json.value中;并且 - 出现在同一
value列表中恰好一个children数组(即其父实体的children数组)中。
实体 0、1 和 2 是规则 2 的例外——它们是树根,不会出现在任何 children 数组中。
此处缺少条目是“实体已渲染,但在 Creator Hub 实体树中不可见”的根本原因。
写入后验证
写入复合体后,运行 SDK 构建进行验证:
npx sdk-commands build
构建必须零错误通过。如果失败,复合体无效。常见错误:
Composite references undefined component "X". Ensure provider.schemas was registered pre-seal via setCompositeProvider().(较旧或已发布的 SDK 会将其表述为"X is not defined and there is no schema to define it")→ 非核心组件缺少jsonSchema,或包含了不应存在的inspector::*组件- TypeScript 错误 → 修复生成的脚本
- 错误指出了你原本不打算发布的文件(
Composite 'art/.../before.composite' can't be read,或来自备份文件的架构错误)→ 构建会匹配项目下的所有*.composite(sdk-commandslogic/composite.ts),包括恢复副本。请使用其他扩展名(before-composite.json)保存备份,或将备份放在项目之外,并检查构建调试输出中的.composite:列表;部署忽略规则不会让构建忽略文件。 - 你添加的
inspector::*组件出现架构错误 → 切勿臆造 inspector 组件(inspector::TransformConfig并不存在);只编写文件中已有条目的 inspector 组件,并照抄其准确结构。