关于 add-3d-models
面向 Decentraland 场景创作者的 3D 模型配置技能,介绍静态与运行时实体、碰撞器、模型目录搜索和常见加载问题。
主要功能
- 01在 main-entities.ts 放置静态模型
- 02配置 GLTF 与网格碰撞器
- 03搜索并下载目录模型
- 04设置模型变换与父实体
- 05排查模型加载和显示问题
适用场景
- 01为 Decentraland 场景添加静态道具
- 02在运行时动态生成模型
- 03查找场景所需模型资源
- 04诊断模型不可见或无法点击
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dcl-regenesislabs/ opendcl --skill add-3d-models安装后在 Claude Code 中输入/add-3d-models确认已加载。
详细介绍
向 Decentraland 场景中添加 3D 模型
模型放在哪里:main-entities.ts
在创作时放置的 3D 模型是静态可见实体。请在 main-entities.ts 中声明它,不要在 src/index.ts 中通过 engine.addEntity() 添加。构建时会将 main-entities.ts 编译为 main.crdt,引擎会在 main() 运行前预加载它,编辑器也可以交互式地拖动和旋转模型。
// main-entities.ts
import type { Scene } from '@dcl/sdk/scene-types'
export const scene = {
my_model: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 } },
GltfContainer: {
src: 'models/myModel.glb',
visibleMeshesCollisionMask: 3 // CL_PHYSICS | CL_POINTER
}
}
}
} satisfies Scene
始终为
GltfContainer设置visibleMeshesCollisionMask。目录中的模型不包含单独的碰撞体网格;使用可见网格作为碰撞体可确保模型具有实体碰撞效果且可点击。在main-entities.ts中使用整数值(ColliderLayer.CL_PHYSICS = 1、CL_POINTER = 2,两者都启用时为3),因为字面量中不允许使用枚举。
改为在 src/index.ts 中使用 engine.addEntity() 的情况:仅当模型是动态生成的(在循环中程序化放置、在事件触发时生成、由 NFT 所有权控制等)。对于静态道具,始终使用 main-entities.ts。
文件组织
将模型文件放在项目根目录下的 models/ 目录中:
project/
├── models/
│ ├── building.glb
│ ├── tree.glb
│ └── furniture/
│ ├── chair.glb
│ └── table.glb
├── src/
│ └── index.ts
└── scene.json
碰撞体
使用模型自带的碰撞体
// main-entities.ts — declared inline with GltfContainer
building: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 } },
GltfContainer: {
src: 'models/building.glb',
visibleMeshesCollisionMask: 3, // CL_PHYSICS | CL_POINTER
invisibleMeshesCollisionMask: 1 // CL_PHYSICS
}
}
}
添加简单碰撞体
对于基本形状(非 GLTF),请添加 MeshCollider:
// main-entities.ts
invisible_wall: {
components: {
Transform: { position: { x: 0, y: 1, z: 8 }, scale: { x: 0.1, y: 2, z: 16 } },
MeshCollider: { mesh: { $case: 'box', box: { uvs: [] } } }
}
}
其他形状:{ $case: 'sphere', sphere: {} }、{ $case: 'plane', plane: { uvs: [] } }、{ $case: 'cylinder', cylinder: {} }。
⚠️ 重要:切勿在 Transform 字段中传入 undefined
只要 Transform 字段(position、rotation、scale)存在但值为 undefined,SDK 序列化器就会崩溃。请直接省略整个键,无论是在 main-entities.ts 字面量中还是在运行时辅助函数中都如此。在 main-entities.ts 中这很简单(不写该字段即可)。
常见模型操作
缩放
// main-entities.ts
big_statue: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 }, scale: { x: 2, y: 2, z: 2 } },
GltfContainer: { src: 'models/statue.glb' }
}
}
旋转(在创作时将欧拉角转换为四元数)
四元数格式为 { x, y, z, w }。Quaternion.fromEulerDegrees(0, 90, 0) 对应的字面量是 { x: 0, y: 0.7071, z: 0, w: 0.7071 }。如果需要精确角度的旋转,又不想手动计算,请在 main-entities.ts 中将 rotation 设为单位旋转,并在运行时于 src/index.ts 中使用 Transform.getMutable(entity).rotation = Quaternion.fromEulerDegrees(0, 90, 0) 进行旋转。
父子关系
通过名称引用父实体(同一 scene 对象中的字符串键)。构建过程会在第二次遍历时将名称解析为实体 ID。
// main-entities.ts
character: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 } },
GltfContainer: { src: 'models/character.glb' }
}
},
hat: {
components: {
Transform: {
position: { x: 0, y: 2, z: 0 }, // 2m above parent's origin
parent: 'character'
},
GltfContainer: { src: 'models/hat.glb' }
}
}
免费 3D 模型——OpenDCL 目录(5,700+ 个模型)
目录文件位于 {baseDir}/references/model-catalog.md。每行的格式如下:
slug | dims | tris | size | category/sub | description [tags] [anim: clips] | curl command | preview: thumbnail_url
如何搜索
每次使用一个关键词搜索——先尝试最具体的词:
grep -i "zombie" {baseDir}/references/model-catalog.md
如果没有结果,请尝试同义词、更宽泛的词或相关词:
- “sofa” → “couch” → “seat” → “furniture”
- “car” → “vehicle” → “truck” → “van”
- “wall” → “fence” → “barrier” → “structure”
浏览所有类别,了解有哪些可用内容:
grep "^##" {baseDir}/references/model-catalog.md
在特定类别中搜索:
grep "^##\|chair" {baseDir}/references/model-catalog.md
如何使用模型
- 使用不同关键词搜索目录,直到找到匹配的模型
- 查看搜索结果——检查尺寸、三角形数量、动画和描述
- 使用 curl 命令将模型下载到
models/ - 在代码中通过
GltfContainer.create(entity, { src: 'models/{slug}.glb' })引用 - 如果模型带有动画(列在
[anim: ...]中),请使用Animator组件播放动画 - 放置模型后,可以获取其预览缩略图(
preview:URL),查看模型外观
示例工作流
# Search for zombie models
grep -i "zombie" {baseDir}/references/model-catalog.md
# Found: zombie-purple | 2.8×2.9×0.5m | 1472 tri | 271KB | character/zombie | ...
# [anim: Tpose, ZombieAttack, ZombieUP, ZombieWalk]
# preview: https://models.dclregenesislabs.xyz/blobs/bafkrei...
# Download the model
curl -o models/zombie-purple.glb "https://models.dclregenesislabs.xyz/blobs/bafybeiffc..."
// main-entities.ts — declare the entity with all its initial state
import type { Scene } from '@dcl/sdk/scene-types'
export const scene = {
zombie: {
components: {
Transform: { position: { x: 8, y: 0, z: 8 } },
GltfContainer: { src: 'models/zombie-purple.glb' },
Animator: {
states: [
{ clip: 'ZombieWalk', playing: true, loop: true },
{ clip: 'ZombieAttack', playing: false, loop: false }
]
}
}
}
} satisfies Scene
要在运行时切换动画(例如,点击时触发攻击),请使用 src/index.ts:
import { engine, Animator } from '@dcl/sdk/ecs'
export function main() {
const zombie = engine.getEntityOrNullByName('zombie')
if (zombie) Animator.playSingleAnimation(zombie, 'ZombieAttack')
}
重要:
GltfContainer仅适用于本地文件。模型的src字段绝不要使用外部 URL。请先将模型下载到models/。 切勿cd进入 models 目录。始终在项目根目录中运行 curl:curl -o models/slug.glb "URL"。不要使用cd models && curl -o slug.glb。
检查模型加载状态
加载状态轮询属于运行时行为——将其放入 src/index.ts,并通过名称引用实体:
import { engine, GltfContainerLoadingState, LoadingState } from '@dcl/sdk/ecs'
export function main() {
engine.addSystem(() => {
const model = engine.getEntityOrNullByName('zombie')
if (!model) return
const state = GltfContainerLoadingState.getOrNull(model)
if (state?.currentState === LoadingState.FINISHED) {
console.log('Model loaded successfully')
} else if (state?.currentState === LoadingState.FINISHED_WITH_ERROR) {
console.log('Model failed to load')
}
})
}
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 看不到模型 | 文件路径错误 | 确认文件位于相对于项目根目录的准确路径下(例如,models/myModel.glb) |
| 看不到模型 | 位置超出场景边界 | 检查 Transform 位置是否在每块地块的 0-16 范围内。单块地块场景的中心为 (8, 0, 8) |
| 看不到模型 | 缩放值为 0 或非常小 | 检查 Transform.scale——默认值为 (1,1,1)。如果模型导出时非常小,请尝试使用更大的值 |
| 看不到模型 | 模型位于摄像机后方 | 移动化身或旋转视角,使其朝向模型 |
| 模型已加载,但外观异常 | Y-up 与 Z-up 不匹配 | Decentraland 使用 Y-up。请从 Blender 重新导出,并勾选 "Y Up" |
| 加载状态为 "FINISHED_WITH_ERROR" | .glb 文件已损坏或不受支持 | 重新导出模型。使用 .glb(二进制 GLTF)格式。确保没有不受支持的扩展 |
| 点击模型没有反应 | 缺少碰撞体 | 在 GltfContainer 中添加 visibleMeshesCollisionMask: ColliderLayer.CL_POINTER,或添加 MeshCollider |
需要针对场景限制优化模型? 请参阅 optimize-scene 技能,了解三角形数量预算和 LOD 模式。 需要为模型添加动画? 请参阅 animations-tweens 技能,了解如何使用 Animator 播放 GLTF 动画片段。
模型最佳实践
- 为确保加载速度良好,请将每个文件中的模型控制在 50MB 以下
- 使用
.glb格式(二进制 GLTF)——比.gltf更小 - 优化三角形数量:小型道具的每个模型尽量控制在 1,500 个三角形以下
- 尽可能使用纹理图集来减少绘制调用
- 含有嵌入式动画的模型可使用
Animator组件播放 - 测试模型方向——Decentraland 使用 Y-up 坐标系
- 模型中的材质应使用 PBR(基于物理的渲染),以获得最佳效果