@dcl-regenesislabs/opendcl 0.2.1-26165320302.commit-e6effe4 → 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 +3 -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
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: particle-system
|
|
3
|
+
description: Emit particles (fire, smoke, sparks, snow, magic, fireworks) from an entity in a Decentraland SDK7 scene with the ParticleSystem component. Covers emitter shapes (Point, Sphere, Cone, Box), continuous rate vs Burst emission, lifetime/size/color/velocity ranges, gravity and additionalForce, blend modes (ALPHA/ADD/MULTIPLY), billboard and faceTravelDirection, sprite-sheet texture animation, simulation space (local vs world), playback state, and per-scene particle budget. Use when the user asks for particles, sparks, fire, smoke, dust, fog, fireworks, magic effects, snowfall, rain, embers, trails, or atmospheric effects. Do NOT use for procedural entity motion (see animations-tweens), GLTF model effects (see add-3d-models), or 2D UI effects (see build-ui).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ParticleSystem (SDK7)
|
|
7
|
+
|
|
8
|
+
Emit particles from an entity. One `ParticleSystem` component per entity, attached alongside a `Transform`. No mesh required — particles render from the component itself.
|
|
9
|
+
|
|
10
|
+
## Authoring split
|
|
11
|
+
|
|
12
|
+
The emitter **position** (entity Transform + optional GltfContainer of a torch/fire pit/fog vent) is static and belongs in `main-entities.ts`. `ParticleSystem` itself is **not** in the supported declarative list — attach it at runtime in `src/index.ts` via `getEntityOrNullByName`.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
// main-entities.ts — emitter placement
|
|
16
|
+
campfire: {
|
|
17
|
+
components: {
|
|
18
|
+
Transform: { position: { x: 8, y: 0, z: 8 } },
|
|
19
|
+
GltfContainer: { src: 'models/campfire.glb' }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// src/index.ts — attach ParticleSystem
|
|
26
|
+
import { engine, ParticleSystem, PBParticleSystem_BlendMode } from '@dcl/sdk/ecs'
|
|
27
|
+
import { Color4 } from '@dcl/sdk/math'
|
|
28
|
+
|
|
29
|
+
export function main() {
|
|
30
|
+
const fire = engine.getEntityOrNullByName('campfire')
|
|
31
|
+
if (!fire) return
|
|
32
|
+
ParticleSystem.create(fire, { /* ... config ... */ })
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## RULE: Transform.scale does NOT scale particles
|
|
37
|
+
|
|
38
|
+
Particle size is controlled exclusively by `initialSize` and `sizeOverTime` (`FloatRange`). The `ParticleSystem` also sets the emitter shape's spatial dimensions when the shape has size fields (Sphere radius, Box size, Cone radius). These are not affected by the entity's `Transform.scale`.
|
|
39
|
+
|
|
40
|
+
## RULE: Particles only render to players inside scene parcels
|
|
41
|
+
|
|
42
|
+
Players viewing the scene from outside its parcels see nothing. Particles are not part of the scene LOD silhouette. Position emitters within parcel bounds.
|
|
43
|
+
|
|
44
|
+
## RULE: Particles only work in the Unity explorer
|
|
45
|
+
|
|
46
|
+
The mobile Godot explorer and the Bevy explorer don't have this feature implemented. The renderer ignores the component. Design fallbacks (a glowing emissive sphere, a baked-in GLTF animation) for scenes that should look reasonable everywhere.
|
|
47
|
+
|
|
48
|
+
## RULE: Engine caps total particles at ~1000
|
|
49
|
+
|
|
50
|
+
The engine enforces a per-scene particle budget and will scale down emission rates across all active systems if total live particles would exceed the limit. Cap each system with `maxParticles` and prefer fewer impactful systems over many small ones.
|
|
51
|
+
|
|
52
|
+
## RULE: prewarm requires loop = true
|
|
53
|
+
|
|
54
|
+
`prewarm: true` only takes effect when `loop: true`. On a one-shot system (`loop: false`) prewarm is silently ignored.
|
|
55
|
+
|
|
56
|
+
## RULE: faceTravelDirection overrides billboard
|
|
57
|
+
|
|
58
|
+
When `faceTravelDirection: true`, particles orient along their velocity vector and `billboard` is ignored. Use this for trails/streaks (asteroids, bullets, sparks). Set `billboard: false` explicitly to avoid confusion.
|
|
59
|
+
|
|
60
|
+
## Import
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { engine, ParticleSystem } from '@dcl/sdk/ecs'
|
|
64
|
+
import {
|
|
65
|
+
PBParticleSystem_BlendMode,
|
|
66
|
+
PBParticleSystem_PlaybackState,
|
|
67
|
+
PBParticleSystem_SimulationSpace
|
|
68
|
+
} from '@dcl/sdk/ecs'
|
|
69
|
+
import { Color4, Vector3, Quaternion } from '@dcl/sdk/math'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Aliases `ParticleSystemBlendMode` and `ParticleSystemPlaybackState` are also exported from `@dcl/sdk/ecs` and are interchangeable with the `PB`-prefixed names. There is no `ParticleSystemSimulationSpace` alias — only `PBParticleSystem_SimulationSpace`. Prefer the `PB`-prefixed names everywhere for consistency.
|
|
73
|
+
|
|
74
|
+
## Field reference
|
|
75
|
+
|
|
76
|
+
| Field | Type | Default | Notes |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| `active` | `boolean` | `true` | Master on/off for new emission. |
|
|
79
|
+
| `rate` | `number` | `10` | Particles emitted per second (continuous). Set to `0` when using `bursts`. |
|
|
80
|
+
| `maxParticles` | `number` | `1000` | Hard cap on simultaneous live particles for this system. |
|
|
81
|
+
| `lifetime` | `number` | `5` | Particle lifespan in seconds. |
|
|
82
|
+
| `gravity` | `number` | `0` | Multiplier on scene gravity (~ -9.81 m/s²). Negative = particles rise. |
|
|
83
|
+
| `additionalForce` | `Vector3` | — | Constant force vector applied each frame (world space). |
|
|
84
|
+
| `initialSize` | `FloatRange` | `{start:1, end:1}` | Random size at spawn. |
|
|
85
|
+
| `sizeOverTime` | `FloatRange` | `{start:1, end:1}` | Size lerped start→end over particle lifetime. |
|
|
86
|
+
| `initialRotation` | `Quaternion` | identity | Spawn orientation. |
|
|
87
|
+
| `rotationOverTime` | `Quaternion` | identity | Per-axis angular velocity. |
|
|
88
|
+
| `faceTravelDirection` | `boolean` | `false` | Orient along velocity. Overrides `billboard`. |
|
|
89
|
+
| `initialColor` | `ColorRange` | `{white,white}` | Random color at spawn. |
|
|
90
|
+
| `colorOverTime` | `ColorRange` | `{white,white}` | Color lerped start→end. Use alpha=0 at end to fade out. |
|
|
91
|
+
| `initialVelocitySpeed` | `FloatRange` | `{start:1, end:1}` | Initial speed in m/s, randomized per particle. |
|
|
92
|
+
| `texture` | `Texture` | white quad | Particle sprite. Same `Texture` shape as Material textures. |
|
|
93
|
+
| `blendMode` | `PBParticleSystem_BlendMode` | `PSB_ALPHA` | `PSB_ALPHA` / `PSB_ADD` / `PSB_MULTIPLY`. |
|
|
94
|
+
| `billboard` | `boolean` | `true` | Particles always face camera. |
|
|
95
|
+
| `spriteSheet` | `{tilesX, tilesY, framesPerSecond?}` | — | Texture-atlas frame animation. |
|
|
96
|
+
| `shape` | oneof Point/Sphere/Cone/Box | Point | Emitter geometry. |
|
|
97
|
+
| `loop` | `boolean` | `true` | Loop emission cycle. `false` = one-shot. |
|
|
98
|
+
| `prewarm` | `boolean` | `false` | Start as if one full loop already simulated. Requires `loop: true`. |
|
|
99
|
+
| `simulationSpace` | `PBParticleSystem_SimulationSpace` | `PSS_LOCAL` | `PSS_LOCAL` (move with entity) / `PSS_WORLD` (stay put after spawn). |
|
|
100
|
+
| `limitVelocity` | `{speed, dampen?}` | — | Clamp top speed. `dampen` 0–1, default `1` = hard clamp. |
|
|
101
|
+
| `playbackState` | `PBParticleSystem_PlaybackState` | `PS_PLAYING` | `PS_PLAYING` / `PS_PAUSED` / `PS_STOPPED`. |
|
|
102
|
+
| `bursts` | `{values: Burst[]}` | — | Discrete emission events. |
|
|
103
|
+
|
|
104
|
+
`FloatRange = { start: number, end: number }`. `ColorRange = { start: Color4, end: Color4 }`.
|
|
105
|
+
|
|
106
|
+
## Emitter shapes
|
|
107
|
+
|
|
108
|
+
Use `ParticleSystem.Shape.*` helpers — never assemble the `oneof` manually:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
shape: ParticleSystem.Shape.Point()
|
|
112
|
+
shape: ParticleSystem.Shape.Sphere({ radius: 1 })
|
|
113
|
+
shape: ParticleSystem.Shape.Cone({ angle: 25, radius: 1 }) // angle = half-angle in degrees
|
|
114
|
+
shape: ParticleSystem.Shape.Box({ size: Vector3.create(1, 1, 1) })
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
- **Point** — emits from entity origin.
|
|
118
|
+
- **Sphere** — random points inside a sphere of `radius`.
|
|
119
|
+
- **Cone** — base disk projecting outward within `angle` half-angle. Cone direction is the entity's local forward; rotate the parent Transform to aim it.
|
|
120
|
+
- **Box** — random points inside an axis-aligned box of `size`.
|
|
121
|
+
|
|
122
|
+
To rotate the emission direction (snow falling, rain), rotate the parent entity's `Transform.rotation` (in `main-entities.ts`).
|
|
123
|
+
|
|
124
|
+
## Bursts
|
|
125
|
+
|
|
126
|
+
Discrete emission events at specific times. Set `rate: 0` to use bursts only, or combine with `rate > 0` for continuous + bursty.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
bursts: {
|
|
130
|
+
values: [{ time: 0, count: 100, cycles: 1, interval: 0.01, probability: 1.0 }]
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Burst fields: `time` (s from cycle start), `count` (particles per burst), `cycles` (default `1`, `0` = infinite), `interval` (s between cycles, default `0.01`), `probability` (0–1 chance per cycle, default `1`). Multiple bursts in one cycle = staggered ignition pattern (fireworks).
|
|
135
|
+
|
|
136
|
+
## Common patterns
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// 1. Fire ember — Point + ADD blend, slight upward drift
|
|
140
|
+
const fire = engine.getEntityOrNullByName('campfire')
|
|
141
|
+
if (fire) ParticleSystem.create(fire, {
|
|
142
|
+
rate: 40,
|
|
143
|
+
lifetime: 2,
|
|
144
|
+
maxParticles: 200,
|
|
145
|
+
initialSize: { start: 0.1, end: 0.3 },
|
|
146
|
+
sizeOverTime: { start: 1.0, end: 0.0 },
|
|
147
|
+
initialColor: { start: Color4.create(1, 0.6, 0.1, 1), end: Color4.create(1, 0.2, 0, 1) },
|
|
148
|
+
colorOverTime: { start: Color4.create(1, 0.5, 0.1, 1), end: Color4.create(0.2, 0, 0, 0) },
|
|
149
|
+
initialVelocitySpeed: { start: 1.5, end: 2.5 },
|
|
150
|
+
gravity: -0.3,
|
|
151
|
+
blendMode: PBParticleSystem_BlendMode.PSB_ADD,
|
|
152
|
+
shape: ParticleSystem.Shape.Point()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// 2. One-shot burst — explosion/pickup VFX
|
|
156
|
+
ParticleSystem.create(entity, {
|
|
157
|
+
loop: false,
|
|
158
|
+
rate: 0,
|
|
159
|
+
lifetime: 3,
|
|
160
|
+
maxParticles: 150,
|
|
161
|
+
initialSize: { start: 0.1, end: 0.25 },
|
|
162
|
+
sizeOverTime: { start: 1.0, end: 0.0 },
|
|
163
|
+
initialVelocitySpeed: { start: 2, end: 4 },
|
|
164
|
+
shape: ParticleSystem.Shape.Sphere({ radius: 0.5 }),
|
|
165
|
+
bursts: {
|
|
166
|
+
values: [{ time: 0, count: 100, cycles: 1, interval: 0.01, probability: 1.0 }]
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Playback control
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const ps = ParticleSystem.getMutable(entity)
|
|
175
|
+
ps.playbackState = PBParticleSystem_PlaybackState.PS_PAUSED // pause + freeze particles
|
|
176
|
+
ps.playbackState = PBParticleSystem_PlaybackState.PS_PLAYING // resume
|
|
177
|
+
ps.playbackState = PBParticleSystem_PlaybackState.PS_STOPPED // hard cut, clear live particles
|
|
178
|
+
ps.active = false // graceful trail-off
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Sprite-sheet animation
|
|
182
|
+
|
|
183
|
+
Texture atlas with frames laid out in a grid (left-to-right, top-to-bottom). Total frames = `tilesX * tilesY`.
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
texture: { src: 'images/flame-sheet.png' },
|
|
187
|
+
spriteSheet: { tilesX: 4, tilesY: 3, framesPerSecond: 12 }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Simulation space (local vs world)
|
|
191
|
+
|
|
192
|
+
- `PSS_LOCAL` (default) — particles move with the emitter. A moving emitter drags its particle cloud. Good for auras / halos on moving entities.
|
|
193
|
+
- `PSS_WORLD` — particles stay at their spawn position in world space. A moving emitter leaves a trail. Required for proper trails combined with `Tween` movement on the emitter.
|
|
194
|
+
|
|
195
|
+
## Texture field
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
texture: { src: 'images/spark.png' }
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The full `Texture` form supports filterMode/wrapMode but particle systems generally only need `src`. Avatar/Video textures on particles are unverified — stick with file textures.
|
|
202
|
+
|
|
203
|
+
## Gotchas
|
|
204
|
+
|
|
205
|
+
- **`rotationOverTime`** is interpreted as per-axis angular velocity. `Quaternion.fromEulerDegrees(0, 90, 0)` = spin 90°/s on Y. Identity = no spin.
|
|
206
|
+
- **`additionalForce` is world-space** even when `simulationSpace = PSS_LOCAL`. Wind/drift directions stay constant regardless of emitter rotation.
|
|
207
|
+
- **`limitVelocity.dampen = 1`** = hard clamp. Lower values let velocity exceed cap briefly then decay.
|
|
208
|
+
- **Color alpha = 0 at end of `colorOverTime`** is the standard way to fade particles out.
|
|
209
|
+
- **No mesh attached to the emitter entity** — adding `MeshRenderer` is unrelated; particles render from the ParticleSystem component itself.
|
|
210
|
+
|
|
211
|
+
## Performance
|
|
212
|
+
|
|
213
|
+
- Cap each system with `maxParticles`. Total scene budget across all systems is ~1000.
|
|
214
|
+
- Keep `lifetime * rate` low; that product is the steady-state live count.
|
|
215
|
+
- Disable systems out of view via `playbackState = PS_STOPPED` or `active = false`.
|
|
216
|
+
- Prefer `PSB_ALPHA` for opaque/translucent effects. `PSB_ADD` is best for glow/fire (it stacks visually) but multi-layer additive overdraw is the most expensive case.
|
|
217
|
+
- One texture per system.
|
|
218
|
+
|
|
219
|
+
## Resources
|
|
220
|
+
|
|
221
|
+
- Test scene: https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/0%2C7-particle-system
|
|
222
|
+
- Live tuner: `ParticleLab.dcl.eth` (open with Decentraland client).
|
|
@@ -5,6 +5,30 @@ 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
|
+
|
|
8
32
|
## Player Position and Movement
|
|
9
33
|
|
|
10
34
|
Access the player's position via the reserved `engine.PlayerEntity`:
|
|
@@ -61,6 +85,36 @@ function main() {
|
|
|
61
85
|
- `userId` — the player's Ethereum wallet address (or guest ID)
|
|
62
86
|
- `isGuest` — `true` if the player hasn't connected a wallet
|
|
63
87
|
|
|
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
|
+
|
|
64
118
|
## Avatar Attachments
|
|
65
119
|
|
|
66
120
|
Attach 3D objects to a player's avatar:
|
|
@@ -79,12 +133,36 @@ AvatarAttach.create(hat, {
|
|
|
79
133
|
|
|
80
134
|
### Anchor Points
|
|
81
135
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
136
|
+
Full enum of bones / positions an attachment can track. Inside `main-entities.ts` use the integer value (left). In `src/index.ts` use `AvatarAnchorPointType.<NAME>`.
|
|
137
|
+
|
|
138
|
+
| value | enum | location |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| 0 | AAPT_POSITION | avatar feet (deprecated — prefer `parent: engine.PlayerEntity`) |
|
|
141
|
+
| 1 | AAPT_NAME_TAG | above the name tag |
|
|
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 |
|
|
88
166
|
|
|
89
167
|
### Attach to a Specific Player
|
|
90
168
|
|
|
@@ -230,9 +308,46 @@ AvatarLocomotionSettings.createOrReplace(engine.PlayerEntity, {
|
|
|
230
308
|
})
|
|
231
309
|
```
|
|
232
310
|
|
|
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
|
+
|
|
233
346
|
## Teleporting the Player
|
|
234
347
|
|
|
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.
|
|
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. `Transform` on `engine.PlayerEntity` is **read-only**; the same applies to `engine.CameraEntity`.
|
|
349
|
+
|
|
350
|
+
`movePlayerTo` only teleports the player **within the same scene**. Cross-scene teleports require explicit player consent and a different API.
|
|
236
351
|
|
|
237
352
|
```typescript
|
|
238
353
|
import { movePlayerTo } from '~system/RestrictedActions'
|
|
@@ -249,6 +364,17 @@ void movePlayerTo({
|
|
|
249
364
|
})
|
|
250
365
|
```
|
|
251
366
|
|
|
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
|
+
|
|
252
378
|
### Avatar Change Listeners
|
|
253
379
|
|
|
254
380
|
React to avatar changes in real-time:
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: player-physics
|
|
3
|
+
description: Apply physics forces to the player in Decentraland scenes. Impulses (one-shot pushes), knockback (push away from a point with falloff), continuous forces (wind tunnels, anti-gravity, lift, levitation, hover), timed forces, and repulsion fields. Use when the user wants launch pads, knockback on hit, wind zones, gravity fields, jumps, lifting/floating the player, pushing the player up/sideways/back, hover effects, or any scene-applied force on the player. THIS is also the right skill when an agent's first instinct is to mutate `Transform` on `engine.PlayerEntity` to move/lift/push the player — that does NOT work (the player Transform is engine-controlled and read-only); use the Physics API instead. Do NOT use for player movement speed (see player-avatar AvatarLocomotionSettings) or platform movement (see animations-tweens).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Player Physics in Decentraland
|
|
7
|
+
|
|
8
|
+
Apply forces to the player's avatar using the `Physics` API from `@dcl/sdk/ecs`. All physics operations affect the **local player** only.
|
|
9
|
+
|
|
10
|
+
## Authoring split
|
|
11
|
+
|
|
12
|
+
Force calls are runtime — they live in `src/index.ts`. The **trigger entities** that cause forces (the launch pad, the wind tunnel volume, the repulsion field marker) are static placements and belong in `main-entities.ts` with a Transform (and optional `MeshRenderer` / `GltfContainer`). Wire pointer/proximity events or trigger systems in `src/index.ts` to call the `Physics.*` methods.
|
|
13
|
+
|
|
14
|
+
## Why this skill exists — the Transform mistake
|
|
15
|
+
|
|
16
|
+
The player's `Transform` (on `engine.PlayerEntity`) is **engine-controlled and read-only from scene code**. Writing to it via `Transform.getMutable`, `Transform.createOrReplace`, or direct `.position` / `.rotation` mutation **silently does nothing** — the code compiles, the system ticks, no error is thrown, and the avatar never moves.
|
|
17
|
+
|
|
18
|
+
**If your goal is to lift, float, push, knock back, or apply any sustained force to the player, use this skill's `Physics` API.** For instant teleports / smooth slides to a specific position, use `movePlayerTo` from `~system/RestrictedActions` (skill: `player-avatar`).
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// WRONG — has no effect in-world
|
|
22
|
+
const t = Transform.getMutable(engine.PlayerEntity)
|
|
23
|
+
t.position.y += 0.1 // ignored every frame
|
|
24
|
+
|
|
25
|
+
// CORRECT — lift the player upward
|
|
26
|
+
import { Physics } from '@dcl/sdk/ecs'
|
|
27
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
28
|
+
Physics.applyImpulseToPlayer(Vector3.create(0, 50, 0)) // one-shot upward launch
|
|
29
|
+
// or, sustained lift / hover:
|
|
30
|
+
const lifter = engine.getEntityOrNullByName('hover_zone') // named entity from main-entities.ts
|
|
31
|
+
if (lifter) Physics.applyForceToPlayer(lifter, Vector3.create(0, 1, 0), 12)
|
|
32
|
+
// stop with: Physics.removeForceFromPlayer(lifter)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Impulse (One-Shot Push)
|
|
36
|
+
|
|
37
|
+
Apply a single instantaneous force with `Physics.applyImpulseToPlayer(direction, magnitude?)`. Direction is auto-normalized when magnitude is passed separately. Multiple calls within the same frame are accumulated. Use for launch pads, jumps, and explosions.
|
|
38
|
+
|
|
39
|
+
## Knockback (Push Away from a Point)
|
|
40
|
+
|
|
41
|
+
Push the player away from a source position with `Physics.applyKnockbackToPlayer(sourcePos, magnitude, radius?, falloff?)`. Direction is computed automatically from source to player.
|
|
42
|
+
|
|
43
|
+
### KnockbackFalloff Options
|
|
44
|
+
|
|
45
|
+
| Falloff | Behavior |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `KnockbackFalloff.CONSTANT` | Same magnitude at any distance within radius (default) |
|
|
48
|
+
| `KnockbackFalloff.LINEAR` | Smooth linear decrease to 0 at the radius edge |
|
|
49
|
+
| `KnockbackFalloff.INVERSE_SQUARE` | Sharp, physically-realistic drop-off |
|
|
50
|
+
|
|
51
|
+
If the player is exactly at the source, they are pushed straight up. A **negative magnitude** pulls the player toward the point (gravity well). The same falloff values apply to `applyRepulsionForceToPlayer()`.
|
|
52
|
+
|
|
53
|
+
## Continuous Force
|
|
54
|
+
|
|
55
|
+
Apply a persistent directional force identified by an **entity** (the force "owner"). Multiple forces from different entities stack.
|
|
56
|
+
|
|
57
|
+
- Apply: `Physics.applyForceToPlayer(entity, direction, magnitude?)`
|
|
58
|
+
- Remove: `Physics.removeForceFromPlayer(entity)`
|
|
59
|
+
|
|
60
|
+
Use with trigger zones for wind tunnels, conveyor belts, and gravity fields. The owner entity is just an ID handle for later removal — typically the named main-entities.ts entity representing the trigger zone.
|
|
61
|
+
|
|
62
|
+
## Timed Force
|
|
63
|
+
|
|
64
|
+
Apply a force for a fixed duration: `Physics.applyForceToPlayerForDuration(entity, seconds, direction, magnitude?)`. Expires automatically.
|
|
65
|
+
|
|
66
|
+
## Repulsion Force
|
|
67
|
+
|
|
68
|
+
Continuous push away from a fixed point with distance-based falloff, recalculated each frame: `Physics.applyRepulsionForceToPlayer(entity, position, magnitude, radius)`. The position must be passed explicitly — not read from the entity's Transform automatically. Remove with `Physics.removeForceFromPlayer(entity)`.
|
|
69
|
+
|
|
70
|
+
## Coordinate Conversion (Local to World Direction)
|
|
71
|
+
|
|
72
|
+
Convert local direction to world space with `Transform.localToWorldDirection(entity, localDir)`. Use when pushing relative to an entity's orientation (e.g. "forward from this cannon").
|
|
73
|
+
|
|
74
|
+
## Quick Reference
|
|
75
|
+
|
|
76
|
+
| Method | Type | Description |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `Physics.applyImpulseToPlayer(dir, mag?)` | One-shot | Instant directional push |
|
|
79
|
+
| `Physics.applyKnockbackToPlayer(pos, mag, radius?, falloff?)` | One-shot | Push away from point |
|
|
80
|
+
| `Physics.applyForceToPlayer(entity, dir, mag?)` | Continuous | Persistent push while active |
|
|
81
|
+
| `Physics.removeForceFromPlayer(entity)` | Control | Stop a continuous force |
|
|
82
|
+
| `Physics.applyForceToPlayerForDuration(entity, secs, dir, mag?)` | Timed | Force that expires automatically |
|
|
83
|
+
| `Physics.applyRepulsionForceToPlayer(entity, pos, mag, radius)` | Continuous | Distance-based push from point |
|
|
84
|
+
| `Transform.localToWorldDirection(entity, dir)` | Utility | Convert local direction to world space |
|
|
85
|
+
|
|
86
|
+
## Best Practices
|
|
87
|
+
|
|
88
|
+
- Use `applyImpulseToPlayer` for one-off events (jump pads, explosions, hits).
|
|
89
|
+
- Use `applyForceToPlayer` + `removeForceFromPlayer` with trigger zones for areas (wind tunnels, conveyor belts).
|
|
90
|
+
- Use `KnockbackFalloff.LINEAR` for most area effects — it feels natural and predictable.
|
|
91
|
+
- Always check `result.trigger?.entity !== engine.PlayerEntity` in trigger callbacks to only affect the local player.
|
|
92
|
+
- A negative knockback magnitude creates a pull/gravity well effect.
|
|
93
|
+
- Multiple forces from different entities stack independently.
|
|
@@ -153,12 +153,16 @@ changeRealm({ realm: 'other-realm.dcl.eth', message: 'Join this realm?' })
|
|
|
153
153
|
|
|
154
154
|
## Timers
|
|
155
155
|
|
|
156
|
-
**
|
|
156
|
+
**Use the `timers` API from `@dcl/sdk/ecs`**, not the global `setTimeout`/`setInterval`. The globals are not reliable in the QuickJS runtime.
|
|
157
157
|
|
|
158
158
|
```typescript
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
import { timers } from '@dcl/sdk/ecs'
|
|
160
|
+
|
|
161
|
+
const t = timers.setTimeout(() => console.log('delayed'), 2000)
|
|
162
|
+
timers.clearTimeout(t)
|
|
163
|
+
|
|
164
|
+
const id = timers.setInterval(() => console.log('tick'), 1000)
|
|
165
|
+
timers.clearInterval(id)
|
|
162
166
|
```
|
|
163
167
|
|
|
164
168
|
**System-based timers** (recommended for game logic — synchronized with the frame loop):
|
|
@@ -251,7 +255,7 @@ npx @dcl/sdk-commands test
|
|
|
251
255
|
|
|
252
256
|
- Always wrap async code in `executeTask()` — bare promises will be silently dropped
|
|
253
257
|
- Use `signedFetch` (not plain `fetch`) when your backend needs to verify the player's identity
|
|
254
|
-
- Prefer system-based timers over `setTimeout`/`setInterval` for game logic — they stay in sync with the frame loop
|
|
258
|
+
- Prefer system-based timers over `timers.setTimeout`/`timers.setInterval` for game logic — they stay in sync with the frame loop. Use `timers.*` for one-shot scheduled actions (auto-close door, delayed sound, etc.).
|
|
255
259
|
- Check `realm.realmInfo?.isPreview` to detect preview mode and enable debug features
|
|
256
260
|
- Use `readFile()` for data files (JSON configs, level data) deployed alongside the scene
|
|
257
261
|
- `removeEntityWithChildren()` is essential when cleaning up complex entity hierarchies
|
|
@@ -106,6 +106,7 @@ 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 |
|
|
109
110
|
|
|
110
111
|
## What to Look For in Screenshots
|
|
111
112
|
|