Adding 3D Models to Decentraland Scenes
Loading a 3D Model
Use GltfContainer to load .glb or .gltf files:
import { engine, Transform, GltfContainer, ColliderLayer } from '@dcl/sdk/ecs'
import { Vector3, Quaternion } from '@dcl/sdk/math'
const model = engine.addEntity()
Transform.create(model, {
position: Vector3.create(8, 0, 8),
rotation: Quaternion.fromEulerDegrees(0, 0, 0),
scale: Vector3.create(1, 1, 1)
})
GltfContainer.create(model, {
src: 'models/myModel.glb',
visibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS | ColliderLayer.CL_POINTER
})
Always set
visibleMeshesCollisionMaskwhen loading models. Catalog models don't include separate collider meshes — using the visible mesh as the collider ensures the model is solid and clickable.
File Organization
Place model files in a models/ directory at the project root:
project/
├── models/
│ ├── building.glb
│ ├── tree.glb
│ └── furniture/
│ ├── chair.glb
│ └── table.glb
├── src/
│ └── index.ts
└── scene.json
Colliders
Using Model's Built-in Colliders
Models exported with collision meshes work automatically. Set the collision mask:
GltfContainer.create(model, {
src: 'models/building.glb',
visibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS | ColliderLayer.CL_POINTER,
invisibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS
})
Adding Simple Colliders
For basic shapes, add MeshCollider:
import { MeshCollider } from '@dcl/sdk/ecs'
MeshCollider.setBox(model) // Box collider
MeshCollider.setSphere(model) // Sphere collider
Common Model Operations
Scaling
Transform.create(model, {
position: Vector3.create(8, 0, 8),
scale: Vector3.create(2, 2, 2) // 2x size
})
Rotation
Transform.create(model, {
position: Vector3.create(8, 0, 8),
rotation: Quaternion.fromEulerDegrees(0, 90, 0) // Rotate 90° on Y axis
})
Parenting (Attach to Another Entity)
const parent = engine.addEntity()
Transform.create(parent, { position: Vector3.create(8, 0, 8) })
const child = engine.addEntity()
Transform.create(child, {
position: Vector3.create(0, 2, 0), // 2m above parent
parent: parent
})
GltfContainer.create(child, { src: 'models/hat.glb' })
Free 3D Models — OpenDCL Catalog (5,700+ models)
The catalog file is at {baseDir}/references/model-catalog.md. Each line has this format:
slug | dims | tris | size | category/sub | description [tags] [anim: clips] | curl command | preview: thumbnail_url
How to search
Search with one keyword at a time — try the most specific word first:
grep -i "zombie" {baseDir}/references/model-catalog.md
If no results, try synonyms, broader terms, or related words:
- "sofa" → "couch" → "seat" → "furniture"
- "car" → "vehicle" → "truck" → "van"
- "wall" → "fence" → "barrier" → "structure"
Browse all categories to discover what's available:
grep "^##" {baseDir}/references/model-catalog.md
Search within a specific category:
grep "^##\|chair" {baseDir}/references/model-catalog.md
How to use models
- Search the catalog with different keywords until you find matching models
- Review the results — check dimensions, triangle count, animations, and description
- Download the model with the curl command into
models/ - Reference in code with
GltfContainer.create(entity, { src: 'models/{slug}.glb' }) - If the model has animations (listed in
[anim: ...]), use theAnimatorcomponent to play them - After placing the model, you can fetch its preview thumbnail (
preview:URL) to see what it looks like
Example workflow
# 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..."
// Use in code with animations
import { engine, Transform, GltfContainer, Animator } from '@dcl/sdk/ecs'
import { Vector3 } from '@dcl/sdk/math'
const zombie = engine.addEntity()
Transform.create(zombie, { position: Vector3.create(8, 0, 8) })
GltfContainer.create(zombie, { src: 'models/zombie-purple.glb' })
Animator.create(zombie, {
states: [
{ clip: 'ZombieWalk', playing: true, loop: true },
{ clip: 'ZombieAttack', playing: false, loop: false }
]
})
Important:
GltfContaineronly works with local files. Never use external URLs for the modelsrcfield. Always download models intomodels/first. Nevercdinto the models directory. Always run curl from the project root withcurl -o models/slug.glb "URL". Do NOT usecd models && curl -o slug.glb.
Checking Model Load State
Use GltfContainerLoadingState to check if a model has finished loading:
import { GltfContainer, GltfContainerLoadingState, LoadingState } from '@dcl/sdk/ecs'
engine.addSystem(() => {
const state = GltfContainerLoadingState.getOrNull(modelEntity)
if (state && state.currentState === LoadingState.FINISHED) {
console.log('Model loaded successfully')
} else if (state && state.currentState === LoadingState.FINISHED_WITH_ERROR) {
console.log('Model failed to load')
}
})
Troubleshooting
| Problem | Cause | Solution |
|---------|-------|----------|
| Model not visible | Wrong file path | Verify the file exists at the exact path relative to project root (e.g., models/myModel.glb) |
| Model not visible | Position outside scene boundaries | Check Transform position is within 0-16 per parcel. Center of 1-parcel scene is (8, 0, 8) |
| Model not visible | Scale is 0 or very small | Check Transform.scale — default is (1,1,1). Try larger values if model was exported very small |
| Model not visible | Behind the camera | Move the avatar or rotate to look in the model's direction |
| Model loads but looks wrong | Y-up vs Z-up mismatch | Decentraland uses Y-up. Re-export from Blender with "Y Up" checked |
| "FINISHED_WITH_ERROR" load state | Corrupted or unsupported .glb | Re-export the model. Use .glb (binary GLTF) format. Ensure no unsupported extensions |
| Clicking model does nothing | Missing collider | Add visibleMeshesCollisionMask: ColliderLayer.CL_POINTER to GltfContainer or add MeshCollider |
Need to optimize models for scene limits? See the optimize-scene skill for triangle budgets and LOD patterns. Need animations from your model? See the animations-tweens skill for playing GLTF animation clips with Animator.
Model Best Practices
- Keep models under 50MB per file for good loading times
- Use
.glbformat (binary GLTF) — smaller than.gltf - Optimize triangle count: aim for under 1,500 triangles per model for small props
- Use texture atlases when possible to reduce draw calls
- Models with embedded animations can be played with the
Animatorcomponent - Test model orientation — Decentraland uses Y-up coordinate system
- Materials in models should use PBR (physically-based rendering) for best results
微信扫一扫