@dcl-regenesislabs/opendcl 0.2.1-26502482653.commit-5089b10 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -5
- package/context/sdk7-cheat-sheet.md +0 -4
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/extensions/dcl-init.ts +6 -58
- package/extensions/dcl-setup-ollama.ts +312 -0
- package/package.json +4 -3
- package/prompts/system.md +41 -71
- package/skills/add-3d-models/SKILL.md +70 -120
- package/skills/add-interactivity/SKILL.md +2 -74
- package/skills/advanced-input/SKILL.md +1 -34
- package/skills/advanced-rendering/SKILL.md +9 -82
- package/skills/animations-tweens/SKILL.md +98 -203
- package/skills/audio-video/SKILL.md +83 -184
- package/skills/build-ui/SKILL.md +2 -25
- package/skills/camera-control/SKILL.md +7 -78
- package/skills/create-scene/SKILL.md +13 -56
- package/skills/deploy-scene/SKILL.md +0 -12
- package/skills/deploy-worlds/SKILL.md +0 -35
- package/skills/game-design/SKILL.md +2 -1
- package/skills/lighting-environment/SKILL.md +56 -103
- package/skills/multiplayer-sync/SKILL.md +2 -31
- package/skills/nft-blockchain/SKILL.md +40 -45
- package/skills/optimize-scene/SKILL.md +2 -7
- package/skills/player-avatar/SKILL.md +7 -133
- package/skills/scene-runtime/SKILL.md +5 -9
- package/skills/visual-feedback/SKILL.md +0 -1
- package/skills/audio-analysis/SKILL.md +0 -164
- package/skills/editor-gizmo/.gitignore +0 -11
- package/skills/editor-gizmo/SKILL.md +0 -222
- package/skills/editor-gizmo/src/__editor/camera.ts +0 -277
- package/skills/editor-gizmo/src/__editor/discovery.ts +0 -210
- package/skills/editor-gizmo/src/__editor/drag.ts +0 -265
- package/skills/editor-gizmo/src/__editor/gizmo.ts +0 -496
- package/skills/editor-gizmo/src/__editor/history.ts +0 -72
- package/skills/editor-gizmo/src/__editor/index.ts +0 -138
- package/skills/editor-gizmo/src/__editor/input.ts +0 -55
- package/skills/editor-gizmo/src/__editor/math-utils.ts +0 -114
- package/skills/editor-gizmo/src/__editor/persistence.ts +0 -113
- package/skills/editor-gizmo/src/__editor/selection.ts +0 -157
- package/skills/editor-gizmo/src/__editor/state.ts +0 -117
- package/skills/editor-gizmo/src/__editor/ui.tsx +0 -699
- package/skills/npcs/SKILL.md +0 -180
- package/skills/particle-system/SKILL.md +0 -222
- package/skills/player-physics/SKILL.md +0 -93
package/prompts/system.md
CHANGED
|
@@ -25,58 +25,26 @@ You are **OpenDCL**, an AI coding assistant specialized in Decentraland SDK7 sce
|
|
|
25
25
|
- 1 parcel: ~512 entities, ~10,000 triangles. Scales with parcel count.
|
|
26
26
|
- All coordinates are in meters. Y is up. Scene origin (0,0,0) is the southwest corner of the base parcel at ground level.
|
|
27
27
|
|
|
28
|
-
###
|
|
28
|
+
### Key Patterns
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- **`main-entities.ts`** at the scene root — typed declarative entities keyed by Name, with their data components (Transform, GltfContainer, MeshRenderer, Material, AudioSource, etc.). Compiled to `main.crdt` at build time and preloaded by the engine before `main()` runs.
|
|
33
|
-
- **`src/index.ts`** — behavior only. References entities by Name and attaches systems, pointer events, tweens.
|
|
34
|
-
|
|
35
|
-
**Adding a declared entity (in `main-entities.ts`):**
|
|
36
|
-
```typescript
|
|
37
|
-
import type { Scene } from '@dcl/sdk/scene-types'
|
|
38
|
-
|
|
39
|
-
export const scene = {
|
|
40
|
-
blue_cube: {
|
|
41
|
-
components: {
|
|
42
|
-
Transform: { position: { x: 8, y: 1, z: 8 }, rotation: { x: 0, y: 0, z: 0, w: 1 }, scale: { x: 1, y: 1, z: 1 } },
|
|
43
|
-
MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } },
|
|
44
|
-
Material: { material: { $case: 'pbr', pbr: { albedoColor: { r: 1, g: 0, b: 0, a: 1 } } } },
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
} satisfies Scene
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
**Referencing it in code:**
|
|
30
|
+
**Creating an entity with components:**
|
|
51
31
|
```typescript
|
|
52
|
-
import { engine,
|
|
53
|
-
import
|
|
32
|
+
import { engine, Transform, MeshRenderer, Material } from '@dcl/sdk/ecs'
|
|
33
|
+
import { Vector3, Color4 } from '@dcl/sdk/math'
|
|
54
34
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (cube === null) return
|
|
60
|
-
|
|
61
|
-
pointerEventsSystem.onPointerDown(
|
|
62
|
-
{ entity: cube, opts: { button: InputAction.IA_POINTER, hoverText: 'Click me' } },
|
|
63
|
-
() => { /* handle click */ },
|
|
64
|
-
)
|
|
65
|
-
}
|
|
35
|
+
const cube = engine.addEntity()
|
|
36
|
+
Transform.create(cube, { position: Vector3.create(8, 1, 8) })
|
|
37
|
+
MeshRenderer.setBox(cube)
|
|
38
|
+
Material.setPbrMaterial(cube, { albedoColor: Color4.Red() })
|
|
66
39
|
```
|
|
67
40
|
|
|
68
|
-
**
|
|
69
|
-
- Every declarative entity goes in `main-entities.ts` with a unique Name. The `satisfies Scene` clause keeps literal keys typed for safe references.
|
|
70
|
-
- Parents are referenced by Name (`parent: 'barrel_1'`); the build resolves them.
|
|
71
|
-
- Pure-data components (Transform, GltfContainer, MeshRenderer, MeshCollider, Material, AudioSource, VideoPlayer, TextShape, Animator config, NftShape, Billboard, VisibilityComponent) all live in `main-entities.ts`.
|
|
72
|
-
- Behavior, callbacks, systems, conditional logic stay in `src/`.
|
|
73
|
-
- The `scene` literal must contain only JSON-compatible values — no function calls, no spreads, no comments inside the object.
|
|
74
|
-
|
|
75
|
-
**Dynamic entities** spawned at runtime (effects, projectiles, runtime markers) still use `engine.addEntity()` and **don't get Names** — they're invisible to the editor and not persisted:
|
|
41
|
+
**Adding interactivity:**
|
|
76
42
|
```typescript
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
43
|
+
import { pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
|
|
44
|
+
|
|
45
|
+
pointerEventsSystem.onPointerDown({ entity: cube, opts: { button: InputAction.IA_POINTER, hoverText: 'Click me' } }, () => {
|
|
46
|
+
// Handle click
|
|
47
|
+
})
|
|
80
48
|
```
|
|
81
49
|
|
|
82
50
|
**Systems (per-frame logic):**
|
|
@@ -105,11 +73,10 @@ export function setupUi() {
|
|
|
105
73
|
```
|
|
106
74
|
scene-project/
|
|
107
75
|
├── scene.json # Scene metadata (parcels, title, main entry)
|
|
108
|
-
├── main-entities.ts # Declarative entities (data) — preloaded into the engine before main() runs
|
|
109
76
|
├── package.json # Dependencies (@dcl/sdk)
|
|
110
|
-
├── tsconfig.json # TypeScript config
|
|
77
|
+
├── tsconfig.json # TypeScript config
|
|
111
78
|
└── src/
|
|
112
|
-
├── index.ts # Main entry point
|
|
79
|
+
├── index.ts # Main entry point (export function main)
|
|
113
80
|
└── ui.tsx # UI components (optional)
|
|
114
81
|
```
|
|
115
82
|
|
|
@@ -127,25 +94,19 @@ scene-project/
|
|
|
127
94
|
## How to Help Users
|
|
128
95
|
|
|
129
96
|
### Empty Folder (No scene.json)
|
|
130
|
-
|
|
97
|
+
1. Ask the user what they want to build.
|
|
98
|
+
2. **Use the `init` tool first** — this uses the official SDK scaffolding to create scene.json, package.json, tsconfig.json, and src/index.ts with the correct, up-to-date configuration, and installs dependencies. Never create these files manually.
|
|
99
|
+
3. After init completes, customize `scene.json` (title, description, parcels) and add the first element to `src/index.ts`. Then offer next steps — don't build the entire scene at once.
|
|
131
100
|
|
|
132
101
|
### Existing Scene
|
|
133
|
-
1. Read scene.json
|
|
102
|
+
1. Read scene.json and src/index.ts to understand the project.
|
|
134
103
|
2. Offer contextual help — adding features, fixing bugs, optimizing.
|
|
135
104
|
3. Always preserve existing code when making edits.
|
|
136
|
-
4. When adding a new declarative entity (cube, model, lamp, etc.), edit `main-entities.ts` and reference the new Name from `src/index.ts` if it needs behavior. Don't reach for `engine.addEntity()` in code as a default.
|
|
137
105
|
|
|
138
106
|
### Best Practices
|
|
139
|
-
|
|
140
|
-
**Always declare static / editable entities in `main-entities.ts`** — never via `engine.addEntity()` in `src/index.ts` for things the user might want to move, rotate, or see in the visual editor. Entities declared in `main-entities.ts` are preloaded by the engine before `main()` runs, are visible in the editor's hierarchy panel, and are draggable. Entities created at runtime via `engine.addEntity()` are invisible to the editor and lost when the user reloads.
|
|
141
|
-
|
|
142
|
-
Use `engine.addEntity()` only for genuinely dynamic things (effects spawned at runtime, projectiles, throwaway markers). Those entities should NOT have a `Name` component — Naming is the marker for "this entity belongs in main-entities.ts."
|
|
143
|
-
|
|
144
|
-
When the user asks to add a barrel, a tree, a prop, a model — that goes in `main-entities.ts` first, and code references it via `engine.getEntityOrNullByName<EntityName>(name)`. Don't fall back to the old `addEntity + Transform.create + GltfContainer.create` pattern in code unless the user explicitly asks for runtime spawning.
|
|
145
|
-
|
|
146
107
|
- Always position objects within the scene boundaries (based on parcels).
|
|
147
|
-
-
|
|
148
|
-
- For 3D models,
|
|
108
|
+
- Use `Vector3.create()` and `Quaternion.fromEulerDegrees()` for transforms.
|
|
109
|
+
- For 3D models, use `GltfContainer.create(entity, { src: 'models/myModel.glb' })`.
|
|
149
110
|
- `GltfContainer` only works with **local files** — never use external URLs for the `src` field. Always download models into the scene's `models/` directory first.
|
|
150
111
|
- Place `.glb` files in a `models/` directory, textures in `images/`.
|
|
151
112
|
- Don't start the preview server automatically after writing code. The user will type `/preview` when ready.
|
|
@@ -154,30 +115,39 @@ When the user asks to add a barrel, a tree, a prop, a model — that goes in `ma
|
|
|
154
115
|
- Search with `grep -i "keyword" skills/add-3d-models/references/model-catalog.md`, fetch the preview thumbnail to confirm, then download with curl.
|
|
155
116
|
- Download matching models with `curl -o models/filename.glb "URL"` before referencing them in code.
|
|
156
117
|
|
|
157
|
-
### Visual
|
|
118
|
+
### Visual Iteration Workflow
|
|
119
|
+
|
|
120
|
+
When the preview server is running, **proactively use the `screenshot` tool after making scene changes**. Don't wait for the user to check — verify your own work:
|
|
121
|
+
|
|
122
|
+
1. Write code or modify the scene.
|
|
123
|
+
2. Use `screenshot` (with a `wait` of ~2000ms for hot-reload) to see the result.
|
|
124
|
+
3. Describe what you see honestly — what's working, what's missing, what looks wrong.
|
|
125
|
+
4. If something is off, fix it and screenshot again.
|
|
158
126
|
|
|
159
|
-
|
|
127
|
+
This way the user gets a working scene without having to open a browser and report issues back to you.
|
|
160
128
|
|
|
161
|
-
|
|
162
|
-
2. Take **one** screenshot (with `wait: 2000` for hot-reload) to verify.
|
|
163
|
-
3. Describe what you see honestly — what works, what's wrong.
|
|
164
|
-
4. If something is off, fix the code and take **one more** screenshot to confirm.
|
|
129
|
+
The screenshot tool supports actions before capture — move around (moveForward, moveLeft, etc.), look around (lookLeft, lookUp), click objects, press keys. Use these to explore from different angles or test interactivity.
|
|
165
130
|
|
|
166
|
-
|
|
131
|
+
The browser stays open between calls — only the first screenshot takes ~15s (launch + enter scene). Subsequent ones are instant.
|
|
167
132
|
|
|
168
|
-
|
|
133
|
+
If the user asks you to iterate autonomously (e.g., "keep going until it looks right"):
|
|
134
|
+
1. Make code changes.
|
|
135
|
+
2. Wait for hot reload (~2s), then take a screenshot.
|
|
136
|
+
3. Analyze whether the result matches the goal.
|
|
137
|
+
4. If not, make targeted fixes and screenshot again.
|
|
138
|
+
5. Repeat (up to 5 iterations) until done.
|
|
169
139
|
|
|
170
140
|
## Tools & Commands
|
|
171
141
|
|
|
172
142
|
You have these Decentraland-specific tools — **use them directly** when the user's request matches:
|
|
173
143
|
- `init` — Scaffold a new scene (**always use this first** in an empty folder)
|
|
174
144
|
- `preview` — Start the Bevy-web preview server
|
|
175
|
-
- `screenshot` — Capture a screenshot of the running preview
|
|
145
|
+
- `screenshot` — Capture a screenshot of the running preview. Supports movement and interaction actions before capture.
|
|
176
146
|
- `deploy` — Deploy to Genesis City or a World (auto-detects from scene.json)
|
|
177
147
|
- `tasks` — List or stop running background processes
|
|
178
148
|
|
|
179
149
|
The user can also type these as `/init`, `/preview`, `/deploy`, `/tasks` slash commands directly.
|
|
180
|
-
Additional user-only commands: `/review`, `/explain`, `/setup`
|
|
150
|
+
Additional user-only commands: `/review`, `/explain`, `/setup`, `/setup-ollama`
|
|
181
151
|
|
|
182
152
|
## Pacing
|
|
183
153
|
|
|
@@ -5,30 +5,27 @@ description: Add 3D models (.glb/.gltf) to a Decentraland scene using GltfContai
|
|
|
5
5
|
|
|
6
6
|
# Adding 3D Models to Decentraland Scenes
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Loading a 3D Model
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Use `GltfContainer` to load `.glb` or `.gltf` files:
|
|
11
11
|
|
|
12
12
|
```typescript
|
|
13
|
-
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
export const scene = {
|
|
17
|
-
my_model: {
|
|
18
|
-
components: {
|
|
19
|
-
Transform: { position: { x: 8, y: 0, z: 8 } },
|
|
20
|
-
GltfContainer: {
|
|
21
|
-
src: 'models/myModel.glb',
|
|
22
|
-
visibleMeshesCollisionMask: 3 // CL_PHYSICS | CL_POINTER
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
} satisfies Scene
|
|
27
|
-
```
|
|
13
|
+
import { engine, Transform, GltfContainer, ColliderLayer } from '@dcl/sdk/ecs'
|
|
14
|
+
import { Vector3, Quaternion } from '@dcl/sdk/math'
|
|
28
15
|
|
|
29
|
-
|
|
16
|
+
const model = engine.addEntity()
|
|
17
|
+
Transform.create(model, {
|
|
18
|
+
position: Vector3.create(8, 0, 8),
|
|
19
|
+
rotation: Quaternion.fromEulerDegrees(0, 0, 0),
|
|
20
|
+
scale: Vector3.create(1, 1, 1)
|
|
21
|
+
})
|
|
22
|
+
GltfContainer.create(model, {
|
|
23
|
+
src: 'models/myModel.glb',
|
|
24
|
+
visibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS | ColliderLayer.CL_POINTER
|
|
25
|
+
})
|
|
26
|
+
```
|
|
30
27
|
|
|
31
|
-
**
|
|
28
|
+
> **Always set `visibleMeshesCollisionMask`** when loading models. Catalog models don't include separate collider meshes — using the visible mesh as the collider ensures the model is solid and clickable.
|
|
32
29
|
|
|
33
30
|
## File Organization
|
|
34
31
|
|
|
@@ -49,80 +46,52 @@ project/
|
|
|
49
46
|
## Colliders
|
|
50
47
|
|
|
51
48
|
### Using Model's Built-in Colliders
|
|
52
|
-
|
|
49
|
+
Models exported with collision meshes work automatically. Set the collision mask:
|
|
53
50
|
```typescript
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
src: 'models/building.glb',
|
|
60
|
-
visibleMeshesCollisionMask: 3, // CL_PHYSICS | CL_POINTER
|
|
61
|
-
invisibleMeshesCollisionMask: 1 // CL_PHYSICS
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
51
|
+
GltfContainer.create(model, {
|
|
52
|
+
src: 'models/building.glb',
|
|
53
|
+
visibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS | ColliderLayer.CL_POINTER,
|
|
54
|
+
invisibleMeshesCollisionMask: ColliderLayer.CL_PHYSICS
|
|
55
|
+
})
|
|
65
56
|
```
|
|
66
57
|
|
|
67
58
|
### Adding Simple Colliders
|
|
68
|
-
|
|
69
|
-
For basic shapes (no GLTF), add `MeshCollider`:
|
|
70
|
-
|
|
59
|
+
For basic shapes, add `MeshCollider`:
|
|
71
60
|
```typescript
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
Transform: { position: { x: 0, y: 1, z: 8 }, scale: { x: 0.1, y: 2, z: 16 } },
|
|
76
|
-
MeshCollider: { mesh: { $case: 'box', box: { uvs: [] } } }
|
|
77
|
-
}
|
|
78
|
-
}
|
|
61
|
+
import { MeshCollider } from '@dcl/sdk/ecs'
|
|
62
|
+
MeshCollider.setBox(model) // Box collider
|
|
63
|
+
MeshCollider.setSphere(model) // Sphere collider
|
|
79
64
|
```
|
|
80
65
|
|
|
81
|
-
Other shapes: `{ $case: 'sphere', sphere: {} }`, `{ $case: 'plane', plane: { uvs: [] } }`, `{ $case: 'cylinder', cylinder: {} }`.
|
|
82
|
-
|
|
83
|
-
## ⚠️ Important: Never Pass `undefined` in Transform Fields
|
|
84
|
-
|
|
85
|
-
The SDK serializer crashes if any Transform field (`position`, `rotation`, `scale`) is present but `undefined`. **Omit the key entirely** instead — both in `main-entities.ts` literals and in any runtime helpers. In `main-entities.ts` this is natural (you just don't write the field).
|
|
86
|
-
|
|
87
66
|
## Common Model Operations
|
|
88
67
|
|
|
89
68
|
### Scaling
|
|
90
|
-
|
|
91
69
|
```typescript
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
GltfContainer: { src: 'models/statue.glb' }
|
|
97
|
-
}
|
|
98
|
-
}
|
|
70
|
+
Transform.create(model, {
|
|
71
|
+
position: Vector3.create(8, 0, 8),
|
|
72
|
+
scale: Vector3.create(2, 2, 2) // 2x size
|
|
73
|
+
})
|
|
99
74
|
```
|
|
100
75
|
|
|
101
|
-
### Rotation
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
76
|
+
### Rotation
|
|
77
|
+
```typescript
|
|
78
|
+
Transform.create(model, {
|
|
79
|
+
position: Vector3.create(8, 0, 8),
|
|
80
|
+
rotation: Quaternion.fromEulerDegrees(0, 90, 0) // Rotate 90° on Y axis
|
|
81
|
+
})
|
|
82
|
+
```
|
|
108
83
|
|
|
84
|
+
### Parenting (Attach to Another Entity)
|
|
109
85
|
```typescript
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Transform: {
|
|
120
|
-
position: { x: 0, y: 2, z: 0 }, // 2m above parent's origin
|
|
121
|
-
parent: 'character'
|
|
122
|
-
},
|
|
123
|
-
GltfContainer: { src: 'models/hat.glb' }
|
|
124
|
-
}
|
|
125
|
-
}
|
|
86
|
+
const parent = engine.addEntity()
|
|
87
|
+
Transform.create(parent, { position: Vector3.create(8, 0, 8) })
|
|
88
|
+
|
|
89
|
+
const child = engine.addEntity()
|
|
90
|
+
Transform.create(child, {
|
|
91
|
+
position: Vector3.create(0, 2, 0), // 2m above parent
|
|
92
|
+
parent: parent
|
|
93
|
+
})
|
|
94
|
+
GltfContainer.create(child, { src: 'models/hat.glb' })
|
|
126
95
|
```
|
|
127
96
|
|
|
128
97
|
## Free 3D Models — OpenDCL Catalog (5,700+ models)
|
|
@@ -177,34 +146,19 @@ curl -o models/zombie-purple.glb "https://models.dclregenesislabs.xyz/blobs/bafy
|
|
|
177
146
|
```
|
|
178
147
|
|
|
179
148
|
```typescript
|
|
180
|
-
//
|
|
181
|
-
import
|
|
182
|
-
|
|
183
|
-
export const scene = {
|
|
184
|
-
zombie: {
|
|
185
|
-
components: {
|
|
186
|
-
Transform: { position: { x: 8, y: 0, z: 8 } },
|
|
187
|
-
GltfContainer: { src: 'models/zombie-purple.glb' },
|
|
188
|
-
Animator: {
|
|
189
|
-
states: [
|
|
190
|
-
{ clip: 'ZombieWalk', playing: true, loop: true },
|
|
191
|
-
{ clip: 'ZombieAttack', playing: false, loop: false }
|
|
192
|
-
]
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
} satisfies Scene
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
To switch animations at runtime (e.g., trigger attack on click), use `src/index.ts`:
|
|
149
|
+
// Use in code with animations
|
|
150
|
+
import { engine, Transform, GltfContainer, Animator } from '@dcl/sdk/ecs'
|
|
151
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
200
152
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
153
|
+
const zombie = engine.addEntity()
|
|
154
|
+
Transform.create(zombie, { position: Vector3.create(8, 0, 8) })
|
|
155
|
+
GltfContainer.create(zombie, { src: 'models/zombie-purple.glb' })
|
|
156
|
+
Animator.create(zombie, {
|
|
157
|
+
states: [
|
|
158
|
+
{ clip: 'ZombieWalk', playing: true, loop: true },
|
|
159
|
+
{ clip: 'ZombieAttack', playing: false, loop: false }
|
|
160
|
+
]
|
|
161
|
+
})
|
|
208
162
|
```
|
|
209
163
|
|
|
210
164
|
> **Important**: `GltfContainer` only works with **local files**. Never use external URLs for the model `src` field. Always download models into `models/` first.
|
|
@@ -212,23 +166,19 @@ export function main() {
|
|
|
212
166
|
|
|
213
167
|
### Checking Model Load State
|
|
214
168
|
|
|
215
|
-
|
|
169
|
+
Use `GltfContainerLoadingState` to check if a model has finished loading:
|
|
216
170
|
|
|
217
171
|
```typescript
|
|
218
|
-
import {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
console.log('Model failed to load')
|
|
229
|
-
}
|
|
230
|
-
})
|
|
231
|
-
}
|
|
172
|
+
import { GltfContainer, GltfContainerLoadingState, LoadingState } from '@dcl/sdk/ecs'
|
|
173
|
+
|
|
174
|
+
engine.addSystem(() => {
|
|
175
|
+
const state = GltfContainerLoadingState.getOrNull(modelEntity)
|
|
176
|
+
if (state && state.currentState === LoadingState.FINISHED) {
|
|
177
|
+
console.log('Model loaded successfully')
|
|
178
|
+
} else if (state && state.currentState === LoadingState.FINISHED_WITH_ERROR) {
|
|
179
|
+
console.log('Model failed to load')
|
|
180
|
+
}
|
|
181
|
+
})
|
|
232
182
|
```
|
|
233
183
|
|
|
234
184
|
## Troubleshooting
|
|
@@ -5,37 +5,6 @@ description: Add click handlers, hover effects, pointer events, trigger areas, r
|
|
|
5
5
|
|
|
6
6
|
# Adding Interactivity to Decentraland Scenes
|
|
7
7
|
|
|
8
|
-
## Authoring split
|
|
9
|
-
|
|
10
|
-
The clickable entity (cube, button, model) is static — declare it in `main-entities.ts` with its Transform / Mesh / Material. The clickability itself is **always** added at runtime in `src/index.ts` via `pointerEventsSystem.onPointerDown(...)` (or the related helpers). The helper writes the `PointerEvents` component AND registers the callback in a single call — do NOT also declare `PointerEvents` in `main-entities.ts`; the helper would just overwrite it and the duplication invites drift.
|
|
11
|
-
|
|
12
|
-
```typescript
|
|
13
|
-
// main-entities.ts — entity only, no PointerEvents
|
|
14
|
-
clickable_cube: {
|
|
15
|
-
components: {
|
|
16
|
-
Transform: { position: { x: 8, y: 1, z: 8 } },
|
|
17
|
-
MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } }
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
// src/index.ts — register clickability via the helper system
|
|
24
|
-
import { engine, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
|
|
25
|
-
|
|
26
|
-
export function main() {
|
|
27
|
-
const cube = engine.getEntityOrNullByName('clickable_cube')
|
|
28
|
-
if (cube) {
|
|
29
|
-
pointerEventsSystem.onPointerDown(
|
|
30
|
-
{ entity: cube, opts: { button: InputAction.IA_POINTER, hoverText: 'Open' } },
|
|
31
|
-
() => { /* what happens on click */ }
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
`TriggerArea` and `Raycast` are also runtime — they live in `src/index.ts`. Code examples below that create entities inline with `engine.addEntity()` are for runtime/technical entities (raycast probes, trigger volumes generated from data); for static clickable props, declare the prop in `main-entities.ts` and attach handlers in `src/index.ts` as above.
|
|
38
|
-
|
|
39
8
|
## Decision Tree
|
|
40
9
|
|
|
41
10
|
| Need | Approach | API |
|
|
@@ -120,10 +89,10 @@ pointerEventsSystem.removeOnPointerUp(cube)
|
|
|
120
89
|
```
|
|
121
90
|
|
|
122
91
|
### Important: Colliders Required
|
|
123
|
-
Pointer events only work on entities with a **collider
|
|
92
|
+
Pointer events only work on entities with a **collider**. Add one if your entity doesn't have a mesh:
|
|
124
93
|
```typescript
|
|
125
94
|
import { MeshCollider } from '@dcl/sdk/ecs'
|
|
126
|
-
MeshCollider.setBox(entity) // Invisible box collider
|
|
95
|
+
MeshCollider.setBox(entity) // Invisible box collider
|
|
127
96
|
```
|
|
128
97
|
|
|
129
98
|
For GLTF models, set the collision mask:
|
|
@@ -136,47 +105,6 @@ GltfContainer.create(entity, {
|
|
|
136
105
|
|
|
137
106
|
---
|
|
138
107
|
|
|
139
|
-
## Proximity Events (Pointer-Free Triggers)
|
|
140
|
-
|
|
141
|
-
Like `pointerEventsSystem.onPointerDown`, but fires based on **player distance** to the entity instead of a click. Useful for "press E when near" interactions and signposts that highlight on approach. No collider required — the system polls the player position vs the entity transform.
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
import { engine, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
|
|
145
|
-
|
|
146
|
-
const door = engine.getEntityOrNullByName('shop_door')
|
|
147
|
-
if (door) {
|
|
148
|
-
pointerEventsSystem.onProximityDown(
|
|
149
|
-
{
|
|
150
|
-
entity: door,
|
|
151
|
-
opts: {
|
|
152
|
-
button: InputAction.IA_PRIMARY,
|
|
153
|
-
hoverText: 'Open shop',
|
|
154
|
-
maxPlayerDistance: 3 // metres
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
() => { /* run when the player presses the button within range */ }
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
pointerEventsSystem.onProximityEnter(
|
|
161
|
-
{ entity: door, opts: { maxPlayerDistance: 5 } },
|
|
162
|
-
() => { /* fired once when the player enters the radius */ }
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
pointerEventsSystem.onProximityLeave(
|
|
166
|
-
{ entity: door, opts: { maxPlayerDistance: 5 } },
|
|
167
|
-
() => { /* fired once when the player leaves the radius */ }
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
- `maxPlayerDistance` is required and is measured from the **avatar root**, not the camera.
|
|
173
|
-
- `priority` (number) — if multiple proximity events overlap, the higher value wins.
|
|
174
|
-
- Remove with `pointerEventsSystem.removeOnProximityDown(entity)` etc.
|
|
175
|
-
|
|
176
|
-
Prefer **proximity events** over `pointerEventsSystem.onPointerDown` when the entity has no visible collider or when the player shouldn't need to aim at it (signs, doors that just open when approached, etc.).
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
108
|
## Trigger Areas (Proximity Detection)
|
|
181
109
|
|
|
182
110
|
Detect when the player enters, exits, or stays inside an area:
|
|
@@ -83,8 +83,6 @@ engine.addSystem(myInputSystem)
|
|
|
83
83
|
|
|
84
84
|
### Global Input Checks
|
|
85
85
|
|
|
86
|
-
Fires regardless of whether the player's cursor was over an entity.
|
|
87
|
-
|
|
88
86
|
```typescript
|
|
89
87
|
function globalInputSystem() {
|
|
90
88
|
// Was the key just pressed this frame?
|
|
@@ -101,31 +99,6 @@ function globalInputSystem() {
|
|
|
101
99
|
engine.addSystem(globalInputSystem)
|
|
102
100
|
```
|
|
103
101
|
|
|
104
|
-
### Tag-Based Input Batching
|
|
105
|
-
|
|
106
|
-
If you have many similar entities that all respond to the same input (e.g., every barrel responds to E to break), tag them via the `Tag` component and iterate the tag query each frame:
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
import { engine, Tag, inputSystem, InputAction, PointerEventType } from '@dcl/sdk/ecs'
|
|
110
|
-
|
|
111
|
-
// At setup: tag the entities (or declare Tag in main-entities.ts).
|
|
112
|
-
for (const barrel of [b1, b2, b3]) {
|
|
113
|
-
Tag.createOrReplace(barrel, { value: 'breakable' })
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
engine.addSystem(() => {
|
|
117
|
-
for (const [entity] of engine.getEntitiesByTag('breakable')) {
|
|
118
|
-
const cmd = inputSystem.getInputCommand(InputAction.IA_PRIMARY, PointerEventType.PET_DOWN, entity)
|
|
119
|
-
if (cmd) {
|
|
120
|
-
// entity was the IA_PRIMARY target this frame
|
|
121
|
-
engine.removeEntity(entity)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Cleaner than registering N individual `pointerEventsSystem.onPointerDown` handlers when the behavior is uniform.
|
|
128
|
-
|
|
129
102
|
## All InputAction Values
|
|
130
103
|
|
|
131
104
|
| InputAction | Key/Button |
|
|
@@ -165,15 +138,11 @@ InputModifier.create(engine.PlayerEntity, {
|
|
|
165
138
|
mode: InputModifier.Mode.Standard({ disableAll: true })
|
|
166
139
|
})
|
|
167
140
|
|
|
168
|
-
// Restrict specific movement
|
|
141
|
+
// Restrict specific movement
|
|
169
142
|
InputModifier.createOrReplace(engine.PlayerEntity, {
|
|
170
143
|
mode: InputModifier.Mode.Standard({
|
|
171
|
-
disableWalk: false,
|
|
172
|
-
disableJog: false,
|
|
173
144
|
disableRun: true,
|
|
174
145
|
disableJump: true,
|
|
175
|
-
disableDoubleJump: true,
|
|
176
|
-
disableGliding: true,
|
|
177
146
|
disableEmote: true
|
|
178
147
|
})
|
|
179
148
|
})
|
|
@@ -182,8 +151,6 @@ InputModifier.createOrReplace(engine.PlayerEntity, {
|
|
|
182
151
|
InputModifier.deleteFrom(engine.PlayerEntity)
|
|
183
152
|
```
|
|
184
153
|
|
|
185
|
-
While restricted: gravity still applies, the camera still rotates, and pointer / proximity events still fire. All restrictions auto-lift when the player leaves the scene.
|
|
186
|
-
|
|
187
154
|
**Important:** InputModifier only works in the DCL 2.0 desktop client. It has no effect in the web browser explorer.
|
|
188
155
|
|
|
189
156
|
### Cutscene Pattern
|