@dcl-regenesislabs/opendcl 0.2.0 → 0.2.1-26238928766.commit-28648d7
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 +5 -3
- package/context/sdk7-cheat-sheet.md +4 -0
- package/dist/index.js +0 -12
- package/dist/index.js.map +1 -1
- package/extensions/dcl-init.ts +58 -6
- package/package.json +7 -3
- package/prompts/system.md +71 -41
- package/skills/add-3d-models/SKILL.md +120 -70
- package/skills/add-interactivity/SKILL.md +74 -2
- package/skills/advanced-input/SKILL.md +34 -1
- package/skills/advanced-rendering/SKILL.md +82 -9
- package/skills/animations-tweens/SKILL.md +203 -98
- package/skills/audio-analysis/SKILL.md +164 -0
- package/skills/audio-video/SKILL.md +184 -83
- package/skills/build-ui/SKILL.md +25 -2
- package/skills/camera-control/SKILL.md +78 -7
- package/skills/create-scene/SKILL.md +56 -13
- package/skills/deploy-scene/SKILL.md +12 -0
- package/skills/deploy-worlds/SKILL.md +35 -0
- package/skills/editor-gizmo/.gitignore +11 -0
- package/skills/editor-gizmo/SKILL.md +222 -0
- package/skills/editor-gizmo/src/__editor/camera.ts +277 -0
- package/skills/editor-gizmo/src/__editor/discovery.ts +186 -0
- package/skills/editor-gizmo/src/__editor/drag.ts +265 -0
- package/skills/editor-gizmo/src/__editor/gizmo.ts +496 -0
- package/skills/editor-gizmo/src/__editor/history.ts +72 -0
- package/skills/editor-gizmo/src/__editor/index.ts +137 -0
- package/skills/editor-gizmo/src/__editor/input.ts +55 -0
- package/skills/editor-gizmo/src/__editor/math-utils.ts +114 -0
- package/skills/editor-gizmo/src/__editor/persistence.ts +113 -0
- package/skills/editor-gizmo/src/__editor/selection.ts +157 -0
- package/skills/editor-gizmo/src/__editor/state.ts +117 -0
- package/skills/editor-gizmo/src/__editor/ui.tsx +697 -0
- package/skills/game-design/SKILL.md +1 -2
- package/skills/lighting-environment/SKILL.md +103 -56
- package/skills/multiplayer-sync/SKILL.md +31 -2
- package/skills/nft-blockchain/SKILL.md +45 -40
- package/skills/npcs/SKILL.md +180 -0
- package/skills/optimize-scene/SKILL.md +7 -2
- package/skills/particle-system/SKILL.md +222 -0
- package/skills/player-avatar/SKILL.md +133 -7
- package/skills/player-physics/SKILL.md +93 -0
- package/skills/scene-runtime/SKILL.md +9 -5
- package/skills/visual-feedback/SKILL.md +1 -0
- package/extensions/dcl-setup-ollama.ts +0 -312
|
@@ -17,7 +17,7 @@ Decentraland is a **continuous, shared 3D world**. Design around these constrain
|
|
|
17
17
|
|
|
18
18
|
## 2. Scene Limitation Formulas
|
|
19
19
|
|
|
20
|
-
All limits scale with parcel count `n`.
|
|
20
|
+
All limits scale with parcel count `n`. **Except for hard MB file-size limits on deploy, all other limits CAN be exceeded** — scenes won't crash, but performance degrades and the scene may be unusable on low-end devices. Treat the table as guidelines, not enforcement.
|
|
21
21
|
|
|
22
22
|
| Resource | Formula | 1 parcel | 2 parcels | 4 parcels | 9 parcels | 16 parcels |
|
|
23
23
|
|---|---|---|---|---|---|---|
|
|
@@ -35,7 +35,6 @@ All limits scale with parcel count `n`. Know these formulas and design within th
|
|
|
35
35
|
|
|
36
36
|
- **Dimensions must be power-of-two**: 256, 512, 1024, 2048
|
|
37
37
|
- **Recommended sizes**: 1024x1024 for scene objects, 512x512 for wearables
|
|
38
|
-
- **Avoid textures over 2048x2048** — they consume excessive memory and often exceed limits
|
|
39
38
|
- **Use texture atlases** to combine multiple small textures into one, reducing draw calls and material count
|
|
40
39
|
- Prefer compressed formats (WebP) over raw PNG where possible
|
|
41
40
|
- Share texture references across materials — do not duplicate texture files
|
|
@@ -5,22 +5,47 @@ description: Dynamic lighting and environment in Decentraland scenes. LightSourc
|
|
|
5
5
|
|
|
6
6
|
# Lighting and Environment in Decentraland
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Authoring split
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
`LightSource` is supported in `main-entities.ts` — declare the lamp's fixture model AND its light in the same entry. The user can drag the fixture in the editor and the light follows.
|
|
11
11
|
|
|
12
12
|
```typescript
|
|
13
|
-
|
|
14
|
-
import {
|
|
13
|
+
// main-entities.ts
|
|
14
|
+
import type { Scene } from '@dcl/sdk/scene-types'
|
|
15
|
+
|
|
16
|
+
export const scene = {
|
|
17
|
+
ceiling_lamp: {
|
|
18
|
+
components: {
|
|
19
|
+
Transform: { position: { x: 8, y: 3, z: 8 } },
|
|
20
|
+
GltfContainer: { src: 'models/lamp.glb' },
|
|
21
|
+
LightSource: {
|
|
22
|
+
type: { $case: 'point', point: {} },
|
|
23
|
+
color: { r: 1, g: 1, b: 1 },
|
|
24
|
+
intensity: 16000
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} satisfies Scene
|
|
29
|
+
```
|
|
15
30
|
|
|
16
|
-
|
|
17
|
-
Transform.create(light, { position: Vector3.create(8, 3, 8) })
|
|
31
|
+
For purely decorative lights with no mesh, declare a Transform-only entity and skip `GltfContainer`. Toggling lights at runtime still happens in `src/index.ts` via `LightSource.getMutable(getEntityOrNullByName('ceiling_lamp'))`.
|
|
18
32
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
## Point Lights
|
|
34
|
+
|
|
35
|
+
Emit light in all directions from a position:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// main-entities.ts entry
|
|
39
|
+
ambient_point: {
|
|
40
|
+
components: {
|
|
41
|
+
Transform: { position: { x: 8, y: 3, z: 8 } },
|
|
42
|
+
LightSource: {
|
|
43
|
+
type: { $case: 'point', point: {} },
|
|
44
|
+
color: { r: 1, g: 1, b: 1 },
|
|
45
|
+
intensity: 16000 // candela — point lights typically need 8000–32000 to be visible
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
24
49
|
```
|
|
25
50
|
|
|
26
51
|
### Colored Point Light
|
|
@@ -29,29 +54,31 @@ LightSource.create(light, {
|
|
|
29
54
|
LightSource.create(light, {
|
|
30
55
|
type: LightSource.Type.Point({}),
|
|
31
56
|
color: Color3.create(1, 0.5, 0), // Warm orange
|
|
32
|
-
intensity:
|
|
57
|
+
intensity: 12000,
|
|
33
58
|
range: 15 // Maximum distance in meters
|
|
34
59
|
})
|
|
35
60
|
```
|
|
36
61
|
|
|
37
62
|
## Spot Lights
|
|
38
63
|
|
|
39
|
-
Emit a cone of light in a direction
|
|
64
|
+
Emit a cone of light in a direction. The cone's orientation comes from the entity's Transform rotation.
|
|
40
65
|
|
|
41
66
|
```typescript
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
Transform
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
LightSource
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
67
|
+
// main-entities.ts
|
|
68
|
+
stage_spot: {
|
|
69
|
+
components: {
|
|
70
|
+
Transform: {
|
|
71
|
+
position: { x: 8, y: 4, z: 8 },
|
|
72
|
+
rotation: { x: -0.7071, y: 0, z: 0, w: 0.7071 } // pitch -90° (down)
|
|
73
|
+
},
|
|
74
|
+
GltfContainer: { src: 'models/spotlight.glb' },
|
|
75
|
+
LightSource: {
|
|
76
|
+
type: { $case: 'spot', spot: { innerAngle: 25, outerAngle: 45 } },
|
|
77
|
+
color: { r: 1, g: 1, b: 1 },
|
|
78
|
+
intensity: 16000
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
55
82
|
```
|
|
56
83
|
|
|
57
84
|
- `innerAngle` — full-brightness cone angle (degrees)
|
|
@@ -66,10 +93,12 @@ Enable shadows on point or spot lights:
|
|
|
66
93
|
LightSource.create(spotlight, {
|
|
67
94
|
type: LightSource.Type.Spot({ innerAngle: 25, outerAngle: 45 }),
|
|
68
95
|
shadow: true,
|
|
69
|
-
intensity:
|
|
96
|
+
intensity: 16000
|
|
70
97
|
})
|
|
71
98
|
```
|
|
72
99
|
|
|
100
|
+
> Shadows are only available on **spot lights**, not point lights.
|
|
101
|
+
|
|
73
102
|
### Shadow Mask Textures (Gobos)
|
|
74
103
|
|
|
75
104
|
Project a pattern through the light:
|
|
@@ -91,10 +120,11 @@ lightData.active = !lightData.active
|
|
|
91
120
|
|
|
92
121
|
## Light Limits
|
|
93
122
|
|
|
94
|
-
- Maximum **one active light per parcel** (16m x 16m)
|
|
95
|
-
- The renderer auto-culls lights based on quality settings and proximity
|
|
96
|
-
-
|
|
97
|
-
- Intensity is in candela
|
|
123
|
+
- Maximum **one active light per parcel** (16m x 16m) — multi-parcel scenes can group lights close together when needed.
|
|
124
|
+
- The renderer auto-culls lights based on quality settings and proximity. Quality range allows **4–10 lights visible simultaneously**.
|
|
125
|
+
- Shadows are only available on spot lights; up to ~3 shadowed lights visible at once.
|
|
126
|
+
- Intensity is in candela. Practical visible range: point lights ~8000–32000, spot lights ~10000–24000. Values below ~1000 are usually invisible.
|
|
127
|
+
- Emissive materials **don't illuminate surrounding entities** — they only have a glow effect on themselves. Combine an emissive material with a `LightSource` for both.
|
|
98
128
|
|
|
99
129
|
## SkyboxTime (Day/Night Cycle)
|
|
100
130
|
|
|
@@ -171,38 +201,55 @@ executeTask(async () => {
|
|
|
171
201
|
|
|
172
202
|
## Emissive Materials (Glow Effects)
|
|
173
203
|
|
|
174
|
-
|
|
204
|
+
`Material` is supported in `main-entities.ts`, so a glow that doesn't need to cast light on surroundings can be declared inline:
|
|
175
205
|
|
|
176
206
|
```typescript
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
207
|
+
// main-entities.ts
|
|
208
|
+
glowing_orb: {
|
|
209
|
+
components: {
|
|
210
|
+
Transform: { position: { x: 8, y: 1, z: 8 } },
|
|
211
|
+
MeshRenderer: { mesh: { $case: 'sphere', sphere: {} } },
|
|
212
|
+
Material: {
|
|
213
|
+
material: {
|
|
214
|
+
$case: 'pbr',
|
|
215
|
+
pbr: {
|
|
216
|
+
albedoColor: { r: 0, g: 0, b: 0, a: 1 },
|
|
217
|
+
emissiveColor: { r: 0, g: 1, b: 0 },
|
|
218
|
+
emissiveIntensity: 2.0
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
186
224
|
```
|
|
187
225
|
|
|
188
226
|
### Combining Emissive + LightSource
|
|
189
227
|
|
|
190
|
-
|
|
228
|
+
A single entity can carry both — emissive glow on the material AND light emission.
|
|
191
229
|
|
|
192
230
|
```typescript
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
231
|
+
// main-entities.ts
|
|
232
|
+
bulb: {
|
|
233
|
+
components: {
|
|
234
|
+
Transform: { position: { x: 8, y: 3, z: 8 } },
|
|
235
|
+
GltfContainer: { src: 'models/bulb.glb' },
|
|
236
|
+
Material: {
|
|
237
|
+
material: {
|
|
238
|
+
$case: 'pbr',
|
|
239
|
+
pbr: {
|
|
240
|
+
emissiveColor: { r: 1, g: 0.9, b: 0.7 },
|
|
241
|
+
emissiveIntensity: 1.5
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
LightSource: {
|
|
246
|
+
type: { $case: 'point', point: {} },
|
|
247
|
+
color: { r: 1, g: 0.9, b: 0.7 },
|
|
248
|
+
intensity: 12000,
|
|
249
|
+
range: 10
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
206
253
|
```
|
|
207
254
|
|
|
208
255
|
### Shadow Types
|
|
@@ -220,7 +267,7 @@ LightSource.create(spotEntity, {
|
|
|
220
267
|
shadow: PBLightSource_ShadowType.ST_SOFT
|
|
221
268
|
}),
|
|
222
269
|
shadow: true,
|
|
223
|
-
intensity:
|
|
270
|
+
intensity: 16000
|
|
224
271
|
})
|
|
225
272
|
```
|
|
226
273
|
|
|
@@ -5,7 +5,36 @@ description: Synchronize state between players in Decentraland using CRDT networ
|
|
|
5
5
|
|
|
6
6
|
# Multiplayer Synchronization in Decentraland
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## Authoring split
|
|
9
|
+
|
|
10
|
+
`syncEntity()` adds a `NetworkEntity` component at runtime — and `NetworkEntity` is **not** in `main-entities.ts`'s supported component list. The pattern is:
|
|
11
|
+
|
|
12
|
+
- **Predefined synced entities** (a door, a switch, a scoreboard — known at author time): declare them in `main-entities.ts` with their visual components. Look them up in `src/index.ts` and call `syncEntity(entity, [...componentIds], SyncIds.NAME)` there.
|
|
13
|
+
- **Dynamically-spawned synced entities** (projectiles, drops, runtime-created game objects): create with `engine.addEntity()` in `src/index.ts` and `syncEntity` immediately.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// main-entities.ts
|
|
17
|
+
door: {
|
|
18
|
+
components: {
|
|
19
|
+
Transform: { position: { x: 8, y: 1, z: 8 } },
|
|
20
|
+
MeshRenderer: { mesh: { $case: 'box', box: { uvs: [] } } }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// src/index.ts
|
|
27
|
+
import { engine, Transform, syncEntity } from '@dcl/sdk/network'
|
|
28
|
+
|
|
29
|
+
export function main() {
|
|
30
|
+
const door = engine.getEntityOrNullByName('door')
|
|
31
|
+
if (door) syncEntity(door, [Transform.componentId], SyncIds.DOOR)
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Decentraland scenes are inherently multiplayer — every player in the same scene shares the same space. **By default, however, players interact with the environment independently — changes one player makes are NOT shared with others.** Opt in to sharing by wrapping mutable state in `syncEntity`, broadcasting events through `MessageBus`, or persisting through your own backend.
|
|
36
|
+
|
|
37
|
+
`syncEntity` state persists only as long as **at least one player remains in the scene**. The state resets as soon as the scene is empty. For durable state across scene resets, persist to your own server.
|
|
9
38
|
|
|
10
39
|
> **Runtime constraint:** Decentraland runs in a QuickJS sandbox. No Node.js APIs (`fs`, `http`, `path`, `process`). Use `fetch()` and `WebSocket` for network communication. See the **scene-runtime** skill for async patterns.
|
|
11
40
|
|
|
@@ -24,7 +53,7 @@ Choose the right networking approach based on what you need:
|
|
|
24
53
|
**Decision flow:**
|
|
25
54
|
1. Does every player need to see the same state, including late joiners? --> `syncEntity`
|
|
26
55
|
2. Is it a fire-and-forget event only for players currently in the scene? --> `MessageBus`
|
|
27
|
-
3. Do you need
|
|
56
|
+
3. Do you need state persisted after all players leave, or server-side validation? --> external server via `fetch` or `signedFetch`
|
|
28
57
|
4. Do you need continuous real-time server communication? --> `WebSocket`
|
|
29
58
|
5. Combine approaches freely: use `syncEntity` for world state, `MessageBus` for effects, and `fetch` for persistence.
|
|
30
59
|
|
|
@@ -7,25 +7,28 @@ description: NFT display and blockchain interaction in Decentraland. NftShape (f
|
|
|
7
7
|
|
|
8
8
|
## Display NFT Artwork
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
`NftShape` is supported in `main-entities.ts` — declare the framed NFT directly. The user can drag it around in the visual editor.
|
|
11
11
|
|
|
12
12
|
```typescript
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
13
|
+
// main-entities.ts
|
|
14
|
+
import type { Scene } from '@dcl/sdk/scene-types'
|
|
15
|
+
|
|
16
|
+
export const scene = {
|
|
17
|
+
hero_nft: {
|
|
18
|
+
components: {
|
|
19
|
+
Transform: { position: { x: 8, y: 2, z: 8 } },
|
|
20
|
+
NftShape: {
|
|
21
|
+
urn: 'urn:decentraland:ethereum:erc721:0x06012c8cf97bead5deae237070f9587f8e7a266d:558536',
|
|
22
|
+
color: { r: 1, g: 1, b: 1, a: 1 },
|
|
23
|
+
style: 0 // NftFrameType.NFT_CLASSIC — enum values are integers in the literal
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
} satisfies Scene
|
|
27
28
|
```
|
|
28
29
|
|
|
30
|
+
The `style` field is a numeric enum — the literal cannot reference `NftFrameType.NFT_CLASSIC` directly because the AST walker only accepts JSON-compatible expressions. Use the integer value (table below) and leave a comment.
|
|
31
|
+
|
|
29
32
|
### NFT URN Format
|
|
30
33
|
|
|
31
34
|
```
|
|
@@ -37,31 +40,33 @@ urn:decentraland:ethereum:erc721:<contractAddress>:<tokenId>
|
|
|
37
40
|
|
|
38
41
|
### Available Frame Styles
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
43
|
+
| value | enum name | description |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| 0 | NFT_CLASSIC | Simple classic frame |
|
|
46
|
+
| 1 | NFT_BAROQUE_ORNAMENT | Ornate baroque |
|
|
47
|
+
| 2 | NFT_DIAMOND_ORNAMENT | Diamond pattern |
|
|
48
|
+
| 3 | NFT_MINIMAL_WIDE | Minimal wide border |
|
|
49
|
+
| 4 | NFT_MINIMAL_GREY | Minimal grey border |
|
|
50
|
+
| 5 | NFT_BLOCKY | Pixelated/blocky |
|
|
51
|
+
| 6 | NFT_GOLD_EDGES | Gold edge trim |
|
|
52
|
+
| 7 | NFT_GOLD_CARVED | Carved gold |
|
|
53
|
+
| 8 | NFT_GOLD_WIDE | Wide gold border |
|
|
54
|
+
| 9 | NFT_GOLD_ROUNDED | Rounded gold |
|
|
55
|
+
| 10 | NFT_METAL_MEDIUM | Medium metal |
|
|
56
|
+
| 11 | NFT_METAL_WIDE | Wide metal |
|
|
57
|
+
| 12 | NFT_METAL_SLIM | Slim metal |
|
|
58
|
+
| 13 | NFT_METAL_ROUNDED | Rounded metal |
|
|
59
|
+
| 14 | NFT_PINS | Pinned to wall |
|
|
60
|
+
| 15 | NFT_MINIMAL_BLACK | Minimal black |
|
|
61
|
+
| 16 | NFT_MINIMAL_WHITE | Minimal white |
|
|
62
|
+
| 17 | NFT_TAPE | Taped to wall |
|
|
63
|
+
| 18 | NFT_WOOD_SLIM | Slim wood |
|
|
64
|
+
| 19 | NFT_WOOD_WIDE | Wide wood |
|
|
65
|
+
| 20 | NFT_WOOD_TWIGS | Twig/branch wood |
|
|
66
|
+
| 21 | NFT_CANVAS | Canvas style |
|
|
67
|
+
| 22 | NFT_NONE | No frame |
|
|
68
|
+
|
|
69
|
+
In `src/index.ts` where enum identifiers are allowed, use `NftFrameType.NFT_CLASSIC` etc. directly.
|
|
65
70
|
|
|
66
71
|
## Check Player Wallet
|
|
67
72
|
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: npcs
|
|
3
|
+
description: Create NPCs (non-player characters) in Decentraland scenes. Two approaches: the NPC Toolkit library (dcl-npc-toolkit) for GLB-based NPCs with built-in dialogue, movement, and state machines; and AvatarShape for avatar-look NPCs dressed in wearables. Use when the user wants to add an NPC, character, shopkeeper, quest giver, guard, or any non-player entity with behavior or dialogue. For live player data (position, profile, wearables) see player-avatar instead.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# NPCs in Decentraland
|
|
7
|
+
|
|
8
|
+
Two approaches — choose based on what the NPC needs to do:
|
|
9
|
+
|
|
10
|
+
| Approach | Use when |
|
|
11
|
+
|---|---|
|
|
12
|
+
| **NPC Toolkit** (`dcl-npc-toolkit`) | GLB model, needs dialogue, walking, state machine behavior |
|
|
13
|
+
| **AvatarShape** | Needs to look like a Decentraland avatar (wearables, expressions) |
|
|
14
|
+
|
|
15
|
+
## Authoring split
|
|
16
|
+
|
|
17
|
+
- **AvatarShape NPCs** are static and fully declarable in `main-entities.ts` (`AvatarShape` is in the supported component list). Click handlers / proximity systems live in `src/index.ts`.
|
|
18
|
+
- **NPC Toolkit NPCs** are created via `createNPC(...)` from the toolkit library, which is a runtime API — call it from `src/index.ts`. The placement entity itself (a marker for the spawn position) can still live in `main-entities.ts` if you want it editable in the gizmo.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Approach 1 — NPC Toolkit (GLB-based)
|
|
23
|
+
|
|
24
|
+
The toolkit handles dialogue UI, movement along paths, animations, and interaction out of the box.
|
|
25
|
+
|
|
26
|
+
**Install:**
|
|
27
|
+
```bash
|
|
28
|
+
npm i dcl-npc-toolkit
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Basic usage:**
|
|
32
|
+
```typescript
|
|
33
|
+
// src/index.ts
|
|
34
|
+
import { createNPC, Dialog } from 'dcl-npc-toolkit'
|
|
35
|
+
import { Vector3, Quaternion } from '@dcl/sdk/math'
|
|
36
|
+
|
|
37
|
+
export function main() {
|
|
38
|
+
const npcEntity = createNPC(
|
|
39
|
+
{ position: Vector3.create(8, 0, 8), rotation: Quaternion.fromEulerDegrees(0, 180, 0) },
|
|
40
|
+
'models/guard.glb',
|
|
41
|
+
(entity) => {
|
|
42
|
+
// called when player clicks the NPC
|
|
43
|
+
startDialogue(entity)
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
idleAnim: 'Idle',
|
|
47
|
+
walkingAnim: 'Walk',
|
|
48
|
+
hoverText: 'Talk',
|
|
49
|
+
onlyExternalTrigger: false
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Gotchas (NPC Toolkit)
|
|
56
|
+
|
|
57
|
+
- **Button labels are visually truncated.** Dialog button labels render with `textWrap: 'nowrap'` in a fixed-width slot (~217px at default font 16). Anything past ~15 characters is silently clipped — no ellipsis. Use short labels like `"Yes"`, `"No thanks"`, `"Tell me more"`, `"Decline"`. To fit longer text, drop `fontSize` (e.g. 12) or set `size` on the button.
|
|
58
|
+
- Opening dialogs on an entity not created via `createNPC` requires `addDialog(entity)` and a minimal `npcDataComponent.set(entity, ...)` — see the toolkit reference for the full setup.
|
|
59
|
+
- Speech bubbles need `createDialogBubble(entity)` before `talkBubble`. Bubbles do not render question buttons; questions are HUD-only.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Approach 2 — AvatarShape (Decentraland avatar look)
|
|
64
|
+
|
|
65
|
+
Create an NPC that looks like a Decentraland player avatar, dressed in any wearables. **Supported in `main-entities.ts`** — declare the NPC declaratively:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// main-entities.ts
|
|
69
|
+
import type { Scene } from '@dcl/sdk/scene-types'
|
|
70
|
+
|
|
71
|
+
export const scene = {
|
|
72
|
+
guard: {
|
|
73
|
+
components: {
|
|
74
|
+
Transform: { position: { x: 8, y: 0, z: 8 } },
|
|
75
|
+
AvatarShape: {
|
|
76
|
+
id: 'npc-1', // unique (required)
|
|
77
|
+
name: 'Guard', // shown above head
|
|
78
|
+
bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale',
|
|
79
|
+
wearables: [
|
|
80
|
+
'urn:decentraland:off-chain:base-avatars:eyebrows_00',
|
|
81
|
+
'urn:decentraland:off-chain:base-avatars:mouth_00',
|
|
82
|
+
'urn:decentraland:off-chain:base-avatars:eyes_00',
|
|
83
|
+
'urn:decentraland:off-chain:base-avatars:blue_tshirt',
|
|
84
|
+
'urn:decentraland:off-chain:base-avatars:brown_pants',
|
|
85
|
+
'urn:decentraland:off-chain:base-avatars:classic_shoes',
|
|
86
|
+
'urn:decentraland:off-chain:base-avatars:short_hair'
|
|
87
|
+
],
|
|
88
|
+
emotes: [],
|
|
89
|
+
hairColor: { r: 0.92, g: 0.76, b: 0.62 },
|
|
90
|
+
skinColor: { r: 0.94, g: 0.85, b: 0.6 }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} satisfies Scene
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Notes:**
|
|
98
|
+
- Always include eyebrows, mouth, and eyes wearables — the avatar won't render face features without them.
|
|
99
|
+
- Moving the `Transform` position causes the NPC to walk/run to the destination (it does **not** teleport).
|
|
100
|
+
- Use `expressionTriggerTimestamp` as a Lamport timestamp to replay the same emote: first play = 0, second play = 1, etc.
|
|
101
|
+
|
|
102
|
+
### Playing expressions on an AvatarShape NPC
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// src/index.ts
|
|
106
|
+
const npc = engine.getEntityOrNullByName('guard')
|
|
107
|
+
if (npc) {
|
|
108
|
+
AvatarShape.getMutable(npc).expressionTriggerId = 'wave'
|
|
109
|
+
AvatarShape.getMutable(npc).expressionTriggerTimestamp = 1
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Mannequin mode (show wearables without a body)
|
|
114
|
+
|
|
115
|
+
Useful for storefronts and wearable displays:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// main-entities.ts
|
|
119
|
+
mannequin: {
|
|
120
|
+
components: {
|
|
121
|
+
Transform: { position: { x: 4, y: 0, z: 4 } },
|
|
122
|
+
AvatarShape: {
|
|
123
|
+
id: 'mannequin-1',
|
|
124
|
+
name: 'Display',
|
|
125
|
+
bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale',
|
|
126
|
+
wearables: ['urn:decentraland:matic:collections-v2:0x...:0'],
|
|
127
|
+
emotes: [],
|
|
128
|
+
show_only_wearables: true
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
For the full `AvatarShape` field reference and common base wearable URNs, see the `player-avatar` skill's references.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Adding interactivity to AvatarShape NPCs
|
|
139
|
+
|
|
140
|
+
AvatarShape entities are **not clickable by default** — they have no collider, so pointer events won't register on them directly. Use one of:
|
|
141
|
+
|
|
142
|
+
### Option A — Add a MeshCollider for click interaction
|
|
143
|
+
|
|
144
|
+
Declare the collider in `main-entities.ts` alongside the AvatarShape:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// main-entities.ts (added to the guard entry above)
|
|
148
|
+
MeshCollider: { mesh: { $case: 'cylinder', cylinder: {} } }
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// src/index.ts
|
|
153
|
+
import { engine, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
|
|
154
|
+
|
|
155
|
+
export function main() {
|
|
156
|
+
const npc = engine.getEntityOrNullByName('guard')
|
|
157
|
+
if (npc) pointerEventsSystem.onPointerDown(
|
|
158
|
+
{ entity: npc, opts: { button: InputAction.IA_POINTER, hoverText: 'Talk' } },
|
|
159
|
+
() => { console.log('Player clicked NPC') }
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Option B — Proximity-based interaction
|
|
165
|
+
|
|
166
|
+
Trigger the interaction when the player walks near the NPC (no collider required):
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { engine, pointerEventsSystem } from '@dcl/sdk/ecs'
|
|
170
|
+
|
|
171
|
+
export function main() {
|
|
172
|
+
const npc = engine.getEntityOrNullByName('guard')
|
|
173
|
+
if (npc) pointerEventsSystem.onProximityEnter(
|
|
174
|
+
{ entity: npc, opts: { maxPlayerDistance: 4 } },
|
|
175
|
+
() => { /* start dialogue or other interaction */ }
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
See the `add-interactivity` skill for the full Proximity Events API.
|
|
@@ -5,9 +5,15 @@ description: Optimize Decentraland scene performance. Scene limit formulas (tria
|
|
|
5
5
|
|
|
6
6
|
# Optimizing Decentraland Scenes
|
|
7
7
|
|
|
8
|
+
## Authoring split
|
|
9
|
+
|
|
10
|
+
The patterns in this skill — object pools, LOD, asset preloading, system throttling — are all **runtime mechanics** and live in `src/index.ts`. The static layout (chairs, walls, lamps, the props that get LOD'd) should still be declared in `main-entities.ts`; the optimization code reads those entities by name (`engine.getEntityOrNullByName`) or by component query (`engine.getEntitiesWith(Transform)`).
|
|
11
|
+
|
|
12
|
+
The parenting optimization (a static container with many children) is best expressed in `main-entities.ts` using `Transform.parent: 'parent_name'` — that way the editor can move the entire group as a unit.
|
|
13
|
+
|
|
8
14
|
## Scene Limits (Per Parcel Count)
|
|
9
15
|
|
|
10
|
-
All limits scale with parcel count `n`. Triangles, entities, and bodies scale linearly. Materials, textures, and height scale logarithmically.
|
|
16
|
+
All limits scale with parcel count `n`. Triangles, entities, and bodies scale linearly. Materials, textures, and height scale logarithmically. **Except for hard MB size limits on deploy, all other limits CAN be exceeded** — scenes won't crash, but performance degrades and the scene may become unusable on lower-end devices. Treat the numbers as guidelines, not enforcement.
|
|
11
17
|
|
|
12
18
|
| Resource | Formula | 1 parcel | 2 parcels | 4 parcels | 9 parcels | 16 parcels |
|
|
13
19
|
|---|---|---|---|---|---|---|
|
|
@@ -100,7 +106,6 @@ MeshRenderer.setPlane(entity) // Very cheap
|
|
|
100
106
|
|
|
101
107
|
- **Dimensions must be power-of-two**: 256, 512, 1024, 2048
|
|
102
108
|
- **Recommended sizes**: 512x512 for most objects, 1024x1024 max for hero pieces
|
|
103
|
-
- **Avoid textures over 2048x2048** — they consume excessive memory and often exceed limits
|
|
104
109
|
- Use `.png` for UI/sprites with transparency
|
|
105
110
|
- Use `.jpg` for photos and textures without transparency
|
|
106
111
|
- Prefer compressed formats (WebP) over raw PNG where possible
|