@dcl-regenesislabs/opendcl 0.2.1-26238928766.commit-28648d7 → 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 -186
- 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 -137
- 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 -697
- package/skills/npcs/SKILL.md +0 -180
- package/skills/particle-system/SKILL.md +0 -222
- package/skills/player-physics/SKILL.md +0 -93
|
@@ -5,47 +5,22 @@ description: Dynamic lighting and environment in Decentraland scenes. LightSourc
|
|
|
5
5
|
|
|
6
6
|
# Lighting and Environment in Decentraland
|
|
7
7
|
|
|
8
|
-
## Authoring split
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
```typescript
|
|
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
|
-
```
|
|
30
|
-
|
|
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'))`.
|
|
32
|
-
|
|
33
8
|
## Point Lights
|
|
34
9
|
|
|
35
10
|
Emit light in all directions from a position:
|
|
36
11
|
|
|
37
12
|
```typescript
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
13
|
+
import { engine, Transform, LightSource } from '@dcl/sdk/ecs'
|
|
14
|
+
import { Vector3, Color3 } from '@dcl/sdk/math'
|
|
15
|
+
|
|
16
|
+
const light = engine.addEntity()
|
|
17
|
+
Transform.create(light, { position: Vector3.create(8, 3, 8) })
|
|
18
|
+
|
|
19
|
+
LightSource.create(light, {
|
|
20
|
+
type: LightSource.Type.Point({}),
|
|
21
|
+
color: Color3.White(),
|
|
22
|
+
intensity: 300 // candela
|
|
23
|
+
})
|
|
49
24
|
```
|
|
50
25
|
|
|
51
26
|
### Colored Point Light
|
|
@@ -54,31 +29,29 @@ ambient_point: {
|
|
|
54
29
|
LightSource.create(light, {
|
|
55
30
|
type: LightSource.Type.Point({}),
|
|
56
31
|
color: Color3.create(1, 0.5, 0), // Warm orange
|
|
57
|
-
intensity:
|
|
32
|
+
intensity: 200,
|
|
58
33
|
range: 15 // Maximum distance in meters
|
|
59
34
|
})
|
|
60
35
|
```
|
|
61
36
|
|
|
62
37
|
## Spot Lights
|
|
63
38
|
|
|
64
|
-
Emit a cone of light in a direction
|
|
39
|
+
Emit a cone of light in a direction:
|
|
65
40
|
|
|
66
41
|
```typescript
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
}
|
|
42
|
+
import { Quaternion } from '@dcl/sdk/math'
|
|
43
|
+
|
|
44
|
+
const spotlight = engine.addEntity()
|
|
45
|
+
Transform.create(spotlight, {
|
|
46
|
+
position: Vector3.create(8, 4, 8),
|
|
47
|
+
rotation: Quaternion.fromEulerDegrees(-90, 0, 0) // Point downward
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
LightSource.create(spotlight, {
|
|
51
|
+
type: LightSource.Type.Spot({ innerAngle: 25, outerAngle: 45 }),
|
|
52
|
+
color: Color3.White(),
|
|
53
|
+
intensity: 800
|
|
54
|
+
})
|
|
82
55
|
```
|
|
83
56
|
|
|
84
57
|
- `innerAngle` — full-brightness cone angle (degrees)
|
|
@@ -93,12 +66,10 @@ Enable shadows on point or spot lights:
|
|
|
93
66
|
LightSource.create(spotlight, {
|
|
94
67
|
type: LightSource.Type.Spot({ innerAngle: 25, outerAngle: 45 }),
|
|
95
68
|
shadow: true,
|
|
96
|
-
intensity:
|
|
69
|
+
intensity: 800
|
|
97
70
|
})
|
|
98
71
|
```
|
|
99
72
|
|
|
100
|
-
> Shadows are only available on **spot lights**, not point lights.
|
|
101
|
-
|
|
102
73
|
### Shadow Mask Textures (Gobos)
|
|
103
74
|
|
|
104
75
|
Project a pattern through the light:
|
|
@@ -120,11 +91,10 @@ lightData.active = !lightData.active
|
|
|
120
91
|
|
|
121
92
|
## Light Limits
|
|
122
93
|
|
|
123
|
-
- Maximum **one active light per parcel** (16m x 16m)
|
|
124
|
-
- The renderer auto-culls lights based on quality settings and proximity
|
|
125
|
-
-
|
|
126
|
-
- Intensity is in candela
|
|
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.
|
|
94
|
+
- Maximum **one active light per parcel** (16m x 16m)
|
|
95
|
+
- The renderer auto-culls lights based on quality settings and proximity
|
|
96
|
+
- Up to ~3 shadowed lights visible at once
|
|
97
|
+
- Intensity is in candela — visible distance grows roughly with `sqrt(intensity)`
|
|
128
98
|
|
|
129
99
|
## SkyboxTime (Day/Night Cycle)
|
|
130
100
|
|
|
@@ -201,55 +171,38 @@ executeTask(async () => {
|
|
|
201
171
|
|
|
202
172
|
## Emissive Materials (Glow Effects)
|
|
203
173
|
|
|
204
|
-
|
|
174
|
+
For a visual glow without casting light on surroundings:
|
|
205
175
|
|
|
206
176
|
```typescript
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
}
|
|
177
|
+
import { engine, Material } from '@dcl/sdk/ecs'
|
|
178
|
+
import { Color4, Color3 } from '@dcl/sdk/math'
|
|
179
|
+
|
|
180
|
+
// Self-illuminated material (emissiveColor uses Color3, not Color4)
|
|
181
|
+
Material.setPbrMaterial(entity, {
|
|
182
|
+
albedoColor: Color4.create(0, 0, 0, 1),
|
|
183
|
+
emissiveColor: Color3.create(0, 1, 0), // Green glow
|
|
184
|
+
emissiveIntensity: 2.0
|
|
185
|
+
})
|
|
224
186
|
```
|
|
225
187
|
|
|
226
188
|
### Combining Emissive + LightSource
|
|
227
189
|
|
|
228
|
-
|
|
190
|
+
For an object that both glows visually and casts light:
|
|
229
191
|
|
|
230
192
|
```typescript
|
|
231
|
-
//
|
|
232
|
-
bulb
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
}
|
|
193
|
+
// Visual glow on the mesh
|
|
194
|
+
Material.setPbrMaterial(bulb, {
|
|
195
|
+
emissiveColor: Color3.create(1, 0.9, 0.7),
|
|
196
|
+
emissiveIntensity: 1.5
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Actual light emission
|
|
200
|
+
LightSource.create(bulb, {
|
|
201
|
+
type: LightSource.Type.Point({}),
|
|
202
|
+
color: Color3.create(1, 0.9, 0.7),
|
|
203
|
+
intensity: 200,
|
|
204
|
+
range: 10
|
|
205
|
+
})
|
|
253
206
|
```
|
|
254
207
|
|
|
255
208
|
### Shadow Types
|
|
@@ -267,7 +220,7 @@ LightSource.create(spotEntity, {
|
|
|
267
220
|
shadow: PBLightSource_ShadowType.ST_SOFT
|
|
268
221
|
}),
|
|
269
222
|
shadow: true,
|
|
270
|
-
intensity:
|
|
223
|
+
intensity: 800
|
|
271
224
|
})
|
|
272
225
|
```
|
|
273
226
|
|
|
@@ -5,36 +5,7 @@ description: Synchronize state between players in Decentraland using CRDT networ
|
|
|
5
5
|
|
|
6
6
|
# Multiplayer Synchronization in Decentraland
|
|
7
7
|
|
|
8
|
-
|
|
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.
|
|
8
|
+
Decentraland scenes are inherently multiplayer. All players in the same scene share the same space. SDK7 uses CRDT-based synchronization.
|
|
38
9
|
|
|
39
10
|
> **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.
|
|
40
11
|
|
|
@@ -53,7 +24,7 @@ Choose the right networking approach based on what you need:
|
|
|
53
24
|
**Decision flow:**
|
|
54
25
|
1. Does every player need to see the same state, including late joiners? --> `syncEntity`
|
|
55
26
|
2. Is it a fire-and-forget event only for players currently in the scene? --> `MessageBus`
|
|
56
|
-
3. Do you need
|
|
27
|
+
3. Do you need to talk to an external server? --> `fetch` or `signedFetch`
|
|
57
28
|
4. Do you need continuous real-time server communication? --> `WebSocket`
|
|
58
29
|
5. Combine approaches freely: use `syncEntity` for world state, `MessageBus` for effects, and `fetch` for persistence.
|
|
59
30
|
|
|
@@ -7,27 +7,24 @@ description: NFT display and blockchain interaction in Decentraland. NftShape (f
|
|
|
7
7
|
|
|
8
8
|
## Display NFT Artwork
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Show an NFT from Ethereum in a decorative frame:
|
|
11
11
|
|
|
12
12
|
```typescript
|
|
13
|
-
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
28
|
-
```
|
|
13
|
+
import { engine, Transform, NftShape, NftFrameType } from '@dcl/sdk/ecs'
|
|
14
|
+
import { Vector3, Quaternion, Color4 } from '@dcl/sdk/math'
|
|
15
|
+
|
|
16
|
+
const nftFrame = engine.addEntity()
|
|
17
|
+
Transform.create(nftFrame, {
|
|
18
|
+
position: Vector3.create(8, 2, 8),
|
|
19
|
+
rotation: Quaternion.fromEulerDegrees(0, 0, 0)
|
|
20
|
+
})
|
|
29
21
|
|
|
30
|
-
|
|
22
|
+
NftShape.create(nftFrame, {
|
|
23
|
+
urn: 'urn:decentraland:ethereum:erc721:0x06012c8cf97bead5deae237070f9587f8e7a266d:558536',
|
|
24
|
+
color: Color4.White(),
|
|
25
|
+
style: NftFrameType.NFT_CLASSIC
|
|
26
|
+
})
|
|
27
|
+
```
|
|
31
28
|
|
|
32
29
|
### NFT URN Format
|
|
33
30
|
|
|
@@ -40,33 +37,31 @@ urn:decentraland:ethereum:erc721:<contractAddress>:<tokenId>
|
|
|
40
37
|
|
|
41
38
|
### Available Frame Styles
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
In `src/index.ts` where enum identifiers are allowed, use `NftFrameType.NFT_CLASSIC` etc. directly.
|
|
40
|
+
```typescript
|
|
41
|
+
NftFrameType.NFT_CLASSIC // Simple classic frame
|
|
42
|
+
NftFrameType.NFT_BAROQUE_ORNAMENT // Ornate baroque
|
|
43
|
+
NftFrameType.NFT_DIAMOND_ORNAMENT // Diamond pattern
|
|
44
|
+
NftFrameType.NFT_MINIMAL_WIDE // Minimal wide border
|
|
45
|
+
NftFrameType.NFT_MINIMAL_GREY // Minimal grey border
|
|
46
|
+
NftFrameType.NFT_BLOCKY // Pixelated/blocky
|
|
47
|
+
NftFrameType.NFT_GOLD_EDGES // Gold edge trim
|
|
48
|
+
NftFrameType.NFT_GOLD_CARVED // Carved gold
|
|
49
|
+
NftFrameType.NFT_GOLD_WIDE // Wide gold border
|
|
50
|
+
NftFrameType.NFT_GOLD_ROUNDED // Rounded gold
|
|
51
|
+
NftFrameType.NFT_METAL_MEDIUM // Medium metal
|
|
52
|
+
NftFrameType.NFT_METAL_WIDE // Wide metal
|
|
53
|
+
NftFrameType.NFT_METAL_SLIM // Slim metal
|
|
54
|
+
NftFrameType.NFT_METAL_ROUNDED // Rounded metal
|
|
55
|
+
NftFrameType.NFT_PINS // Pinned to wall
|
|
56
|
+
NftFrameType.NFT_MINIMAL_BLACK // Minimal black
|
|
57
|
+
NftFrameType.NFT_MINIMAL_WHITE // Minimal white
|
|
58
|
+
NftFrameType.NFT_TAPE // Taped to wall
|
|
59
|
+
NftFrameType.NFT_WOOD_SLIM // Slim wood
|
|
60
|
+
NftFrameType.NFT_WOOD_WIDE // Wide wood
|
|
61
|
+
NftFrameType.NFT_WOOD_TWIGS // Twig/branch wood
|
|
62
|
+
NftFrameType.NFT_CANVAS // Canvas style
|
|
63
|
+
NftFrameType.NFT_NONE // No frame
|
|
64
|
+
```
|
|
70
65
|
|
|
71
66
|
## Check Player Wallet
|
|
72
67
|
|
|
@@ -5,15 +5,9 @@ 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
|
-
|
|
14
8
|
## Scene Limits (Per Parcel Count)
|
|
15
9
|
|
|
16
|
-
All limits scale with parcel count `n`. Triangles, entities, and bodies scale linearly. Materials, textures, and height scale logarithmically.
|
|
10
|
+
All limits scale with parcel count `n`. Triangles, entities, and bodies scale linearly. Materials, textures, and height scale logarithmically.
|
|
17
11
|
|
|
18
12
|
| Resource | Formula | 1 parcel | 2 parcels | 4 parcels | 9 parcels | 16 parcels |
|
|
19
13
|
|---|---|---|---|---|---|---|
|
|
@@ -106,6 +100,7 @@ MeshRenderer.setPlane(entity) // Very cheap
|
|
|
106
100
|
|
|
107
101
|
- **Dimensions must be power-of-two**: 256, 512, 1024, 2048
|
|
108
102
|
- **Recommended sizes**: 512x512 for most objects, 1024x1024 max for hero pieces
|
|
103
|
+
- **Avoid textures over 2048x2048** — they consume excessive memory and often exceed limits
|
|
109
104
|
- Use `.png` for UI/sprites with transparency
|
|
110
105
|
- Use `.jpg` for photos and textures without transparency
|
|
111
106
|
- Prefer compressed formats (WebP) over raw PNG where possible
|
|
@@ -5,30 +5,6 @@ description: Player and avatar system in Decentraland. Read player position/prof
|
|
|
5
5
|
|
|
6
6
|
# Player and Avatar System in Decentraland
|
|
7
7
|
|
|
8
|
-
## Authoring split
|
|
9
|
-
|
|
10
|
-
`AvatarShape` (the component used for NPCs and pre-placed avatars) is supported in `main-entities.ts` — declare the NPC fully there with id, name, wearables, etc.:
|
|
11
|
-
|
|
12
|
-
```typescript
|
|
13
|
-
// main-entities.ts
|
|
14
|
-
shopkeeper: {
|
|
15
|
-
components: {
|
|
16
|
-
Transform: { position: { x: 8, y: 0, z: 8 } },
|
|
17
|
-
AvatarShape: {
|
|
18
|
-
id: 'shopkeeper-1',
|
|
19
|
-
name: 'Shopkeeper',
|
|
20
|
-
bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale',
|
|
21
|
-
wearables: [],
|
|
22
|
-
emotes: []
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
`AvatarAttach`, `AvatarModifierArea`, `AvatarBase`, `AvatarEquippedData` are **not** in the supported list — they're runtime by design (they bind to the live player, or apply to entities you create on-the-fly). Add them in `src/index.ts` and attach to entities looked up via `getEntityOrNullByName` (for static placement) or runtime-created entities (for player-bound effects).
|
|
29
|
-
|
|
30
|
-
The reserved `engine.PlayerEntity` is engine-managed and has no representation in `main-entities.ts`.
|
|
31
|
-
|
|
32
8
|
## Player Position and Movement
|
|
33
9
|
|
|
34
10
|
Access the player's position via the reserved `engine.PlayerEntity`:
|
|
@@ -85,36 +61,6 @@ function main() {
|
|
|
85
61
|
- `userId` — the player's Ethereum wallet address (or guest ID)
|
|
86
62
|
- `isGuest` — `true` if the player hasn't connected a wallet
|
|
87
63
|
|
|
88
|
-
### Fetch Full Avatar Profile from the Catalyst
|
|
89
|
-
|
|
90
|
-
`getPlayer()` returns the local view of the player. For full avatar data (wearables list, body shape, skin/hair/eye colors), fetch from the Catalyst:
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
import { executeTask, signedFetch } from '@dcl/sdk/network'
|
|
94
|
-
import { getPlayer } from '@dcl/sdk/players'
|
|
95
|
-
|
|
96
|
-
executeTask(async () => {
|
|
97
|
-
const player = getPlayer()
|
|
98
|
-
if (!player || player.isGuest) return
|
|
99
|
-
|
|
100
|
-
const res = await fetch(`https://peer.decentraland.org/lambdas/profiles/${player.userId}`)
|
|
101
|
-
const body = await res.json()
|
|
102
|
-
|
|
103
|
-
// Response is an array of profiles; the first entry holds the active avatar.
|
|
104
|
-
const profile = body?.avatars?.[0]
|
|
105
|
-
if (!profile) return
|
|
106
|
-
|
|
107
|
-
console.log('name:', profile.name)
|
|
108
|
-
console.log('wearables:', profile.avatar.wearables)
|
|
109
|
-
console.log('skin color:', profile.avatar.skin.color) // { r, g, b } — already unwrapped, NOT { color: { r,g,b } }
|
|
110
|
-
})
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
**Gotchas:**
|
|
114
|
-
- The response is `{ avatars: [...] }`, not a flat profile object. Always read `body.avatars[0]`.
|
|
115
|
-
- Color fields (`skin.color`, `hair.color`, `eyes.color`) are already `{ r, g, b }` objects — don't unwrap one more level.
|
|
116
|
-
- The fetch is unauthenticated; for endpoints that need the player's signed identity, use `signedFetch` from `@dcl/sdk/network` instead of plain `fetch`.
|
|
117
|
-
|
|
118
64
|
## Avatar Attachments
|
|
119
65
|
|
|
120
66
|
Attach 3D objects to a player's avatar:
|
|
@@ -133,36 +79,12 @@ AvatarAttach.create(hat, {
|
|
|
133
79
|
|
|
134
80
|
### Anchor Points
|
|
135
81
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
| 2 | AAPT_LEFT_HAND | left hand |
|
|
143
|
-
| 3 | AAPT_RIGHT_HAND | right hand |
|
|
144
|
-
| 4 | AAPT_HEAD | head bone |
|
|
145
|
-
| 5 | AAPT_NECK | neck |
|
|
146
|
-
| 6 | AAPT_SPINE | spine root |
|
|
147
|
-
| 7 | AAPT_SPINE1 | spine mid |
|
|
148
|
-
| 8 | AAPT_SPINE2 | spine top |
|
|
149
|
-
| 9 | AAPT_HIP | hip |
|
|
150
|
-
| 10 | AAPT_LEFT_SHOULDER | left shoulder |
|
|
151
|
-
| 11 | AAPT_LEFT_ARM | left upper arm |
|
|
152
|
-
| 12 | AAPT_LEFT_FOREARM | left forearm |
|
|
153
|
-
| 13 | AAPT_LEFT_HAND_INDEX | left index finger |
|
|
154
|
-
| 14 | AAPT_RIGHT_SHOULDER | right shoulder |
|
|
155
|
-
| 15 | AAPT_RIGHT_ARM | right upper arm |
|
|
156
|
-
| 16 | AAPT_RIGHT_FOREARM | right forearm |
|
|
157
|
-
| 17 | AAPT_RIGHT_HAND_INDEX | right index finger |
|
|
158
|
-
| 18 | AAPT_LEFT_UP_LEG | left thigh |
|
|
159
|
-
| 19 | AAPT_LEFT_LEG | left calf |
|
|
160
|
-
| 20 | AAPT_LEFT_FOOT | left foot |
|
|
161
|
-
| 21 | AAPT_LEFT_TOE_BASE | left toes |
|
|
162
|
-
| 22 | AAPT_RIGHT_UP_LEG | right thigh |
|
|
163
|
-
| 23 | AAPT_RIGHT_LEG | right calf |
|
|
164
|
-
| 24 | AAPT_RIGHT_FOOT | right foot |
|
|
165
|
-
| 25 | AAPT_RIGHT_TOE_BASE | right toes |
|
|
82
|
+
```typescript
|
|
83
|
+
AvatarAnchorPointType.AAPT_NAME_TAG // Above the head
|
|
84
|
+
AvatarAnchorPointType.AAPT_RIGHT_HAND // Right hand
|
|
85
|
+
AvatarAnchorPointType.AAPT_LEFT_HAND // Left hand
|
|
86
|
+
AvatarAnchorPointType.AAPT_POSITION // Avatar root position
|
|
87
|
+
```
|
|
166
88
|
|
|
167
89
|
### Attach to a Specific Player
|
|
168
90
|
|
|
@@ -308,46 +230,9 @@ AvatarLocomotionSettings.createOrReplace(engine.PlayerEntity, {
|
|
|
308
230
|
})
|
|
309
231
|
```
|
|
310
232
|
|
|
311
|
-
## InputModifier — Restrict Player Movement
|
|
312
|
-
|
|
313
|
-
Disable specific movement modes for the local player. Useful for cutscenes, dialogue freezes, traversal puzzles. Applies to `engine.PlayerEntity`.
|
|
314
|
-
|
|
315
|
-
```typescript
|
|
316
|
-
import { engine, InputModifier } from '@dcl/sdk/ecs'
|
|
317
|
-
|
|
318
|
-
InputModifier.createOrReplace(engine.PlayerEntity, {
|
|
319
|
-
mode: InputModifier.Mode.Standard({
|
|
320
|
-
disableWalk: false,
|
|
321
|
-
disableJog: false,
|
|
322
|
-
disableRun: false,
|
|
323
|
-
disableJump: false,
|
|
324
|
-
disableDoubleJump: false,
|
|
325
|
-
disableGliding: false
|
|
326
|
-
})
|
|
327
|
-
})
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
Set any field to `true` to disable that mode. While disabled:
|
|
331
|
-
- Gravity still applies (the player still falls).
|
|
332
|
-
- The camera can still rotate freely.
|
|
333
|
-
- The player can still trigger pointer / proximity events.
|
|
334
|
-
- All restrictions are auto-lifted when the player leaves the scene.
|
|
335
|
-
|
|
336
|
-
### Freeze All Movement
|
|
337
|
-
|
|
338
|
-
```typescript
|
|
339
|
-
InputModifier.createOrReplace(engine.PlayerEntity, {
|
|
340
|
-
mode: InputModifier.Mode.Standard({ disableAll: true })
|
|
341
|
-
})
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
To release: `InputModifier.deleteFrom(engine.PlayerEntity)` — or `createOrReplace` with all flags `false`.
|
|
345
|
-
|
|
346
233
|
## Teleporting the Player
|
|
347
234
|
|
|
348
|
-
**You MUST use `movePlayerTo` from `~system/RestrictedActions` to move or teleport the player.** Setting `Transform.getMutable(engine.PlayerEntity).position` does NOT work — the runtime ignores direct writes to the player transform.
|
|
349
|
-
|
|
350
|
-
`movePlayerTo` only teleports the player **within the same scene**. Cross-scene teleports require explicit player consent and a different API.
|
|
235
|
+
**You MUST use `movePlayerTo` from `~system/RestrictedActions` to move or teleport the player.** Setting `Transform.getMutable(engine.PlayerEntity).position` does NOT work — the runtime ignores direct writes to the player transform.
|
|
351
236
|
|
|
352
237
|
```typescript
|
|
353
238
|
import { movePlayerTo } from '~system/RestrictedActions'
|
|
@@ -364,17 +249,6 @@ void movePlayerTo({
|
|
|
364
249
|
})
|
|
365
250
|
```
|
|
366
251
|
|
|
367
|
-
`movePlayerTo` returns a Promise — `await` it if you need to chain actions:
|
|
368
|
-
|
|
369
|
-
```typescript
|
|
370
|
-
import { executeTask } from '@dcl/sdk/ecs'
|
|
371
|
-
|
|
372
|
-
executeTask(async () => {
|
|
373
|
-
await movePlayerTo({ newRelativePosition: Vector3.create(8, 0, 8) })
|
|
374
|
-
console.log('teleported, continuing flow')
|
|
375
|
-
})
|
|
376
|
-
```
|
|
377
|
-
|
|
378
252
|
### Avatar Change Listeners
|
|
379
253
|
|
|
380
254
|
React to avatar changes in real-time:
|
|
@@ -153,16 +153,12 @@ changeRealm({ realm: 'other-realm.dcl.eth', message: 'Join this realm?' })
|
|
|
153
153
|
|
|
154
154
|
## Timers
|
|
155
155
|
|
|
156
|
-
**
|
|
156
|
+
**setTimeout / setInterval** are supported via the QuickJS runtime polyfill:
|
|
157
157
|
|
|
158
158
|
```typescript
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
timers.clearTimeout(t)
|
|
163
|
-
|
|
164
|
-
const id = timers.setInterval(() => console.log('tick'), 1000)
|
|
165
|
-
timers.clearInterval(id)
|
|
159
|
+
setTimeout(() => console.log('delayed'), 2000)
|
|
160
|
+
const id = setInterval(() => console.log('tick'), 1000)
|
|
161
|
+
clearInterval(id)
|
|
166
162
|
```
|
|
167
163
|
|
|
168
164
|
**System-based timers** (recommended for game logic — synchronized with the frame loop):
|
|
@@ -255,7 +251,7 @@ npx @dcl/sdk-commands test
|
|
|
255
251
|
|
|
256
252
|
- Always wrap async code in `executeTask()` — bare promises will be silently dropped
|
|
257
253
|
- Use `signedFetch` (not plain `fetch`) when your backend needs to verify the player's identity
|
|
258
|
-
- Prefer system-based timers over `
|
|
254
|
+
- Prefer system-based timers over `setTimeout`/`setInterval` for game logic — they stay in sync with the frame loop
|
|
259
255
|
- Check `realm.realmInfo?.isPreview` to detect preview mode and enable debug features
|
|
260
256
|
- Use `readFile()` for data files (JSON configs, level data) deployed alongside the scene
|
|
261
257
|
- `removeEntityWithChildren()` is essential when cleaning up complex entity hierarchies
|
|
@@ -106,7 +106,6 @@ Keep it to 1-2 screenshots per task. Each screenshot consumes significant tokens
|
|
|
106
106
|
| Missing textures (pink/magenta) | Texture file not found or wrong path | Verify file exists in project and path matches code |
|
|
107
107
|
| Objects appear wrong scale | Scale values off or model exported at wrong scale | Check Transform.scale values; 1,1,1 is original model size |
|
|
108
108
|
| Lighting looks flat | No LightSource components in scene | Add point or spot lights — see **lighting-environment** skill |
|
|
109
|
-
| Custom lights look too dim or invisible | Intensity value too low | Point/spot lights typically need `intensity: 8000–32000` candela. Values below ~1000 are usually invisible |
|
|
110
109
|
|
|
111
110
|
## What to Look For in Screenshots
|
|
112
111
|
|