@dcl-regenesislabs/opendcl 0.1.0-22234509684.commit-63dfd19
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 +234 -0
- package/context/components-reference.md +113 -0
- package/context/open-source-3d-assets.md +705 -0
- package/context/sdk7-complete-reference.md +3684 -0
- package/context/sdk7-examples.md +1709 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/scene-context.d.ts +97 -0
- package/dist/scene-context.d.ts.map +1 -0
- package/dist/scene-context.js +203 -0
- package/dist/scene-context.js.map +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +4 -0
- package/dist/utils.js.map +1 -0
- package/extensions/dcl-context.ts +123 -0
- package/extensions/dcl-deploy.ts +89 -0
- package/extensions/dcl-header.ts +162 -0
- package/extensions/dcl-init.ts +62 -0
- package/extensions/dcl-preview.ts +144 -0
- package/extensions/dcl-setup-ollama.ts +312 -0
- package/extensions/dcl-setup.ts +96 -0
- package/extensions/dcl-status.ts +88 -0
- package/extensions/dcl-tasks.ts +102 -0
- package/extensions/dcl-update-check.ts +79 -0
- package/extensions/dcl-validate.ts +80 -0
- package/extensions/plan-mode/index.ts +340 -0
- package/extensions/plan-mode/utils.ts +168 -0
- package/extensions/process-registry.ts +25 -0
- package/extensions/scene-utils.ts +31 -0
- package/package.json +65 -0
- package/prompts/explain.md +16 -0
- package/prompts/review.md +19 -0
- package/prompts/system.md +126 -0
- package/skills/add-3d-models/SKILL.md +115 -0
- package/skills/add-interactivity/SKILL.md +176 -0
- package/skills/advanced-input/SKILL.md +238 -0
- package/skills/advanced-rendering/SKILL.md +235 -0
- package/skills/animations-tweens/SKILL.md +173 -0
- package/skills/audio-video/SKILL.md +167 -0
- package/skills/authoritative-server/SKILL.md +329 -0
- package/skills/build-ui/SKILL.md +231 -0
- package/skills/camera-control/SKILL.md +199 -0
- package/skills/create-scene/SKILL.md +67 -0
- package/skills/deploy-scene/SKILL.md +106 -0
- package/skills/deploy-worlds/SKILL.md +107 -0
- package/skills/lighting-environment/SKILL.md +216 -0
- package/skills/multiplayer-sync/SKILL.md +132 -0
- package/skills/nft-blockchain/SKILL.md +246 -0
- package/skills/optimize-scene/SKILL.md +160 -0
- package/skills/player-avatar/SKILL.md +239 -0
- package/skills/smart-items/SKILL.md +181 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: advanced-rendering
|
|
3
|
+
description: Advanced rendering features in Decentraland scenes. Use Billboard to make entities face the camera, TextShape for 3D text, advanced PBR material properties like metallic/roughness/transparency, GltfNodeModifiers for per-node visibility and material overrides in GLTF models, and VisibilityComponent to show/hide entities. Use when user wants billboards, 3D text, text labels, material effects, transparency, glow, or model node control.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Advanced Rendering in Decentraland
|
|
7
|
+
|
|
8
|
+
## Billboard (Face the Camera)
|
|
9
|
+
|
|
10
|
+
Make entities always rotate to face the player's camera:
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { engine, Transform, Billboard, BillboardMode, MeshRenderer } from '@dcl/sdk/ecs'
|
|
14
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
15
|
+
|
|
16
|
+
const sign = engine.addEntity()
|
|
17
|
+
Transform.create(sign, { position: Vector3.create(8, 2, 8) })
|
|
18
|
+
MeshRenderer.setPlane(sign)
|
|
19
|
+
|
|
20
|
+
// Rotate only on Y axis (most common — stays upright)
|
|
21
|
+
Billboard.create(sign, {
|
|
22
|
+
billboardMode: BillboardMode.BM_Y
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Billboard Modes
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
BillboardMode.BM_Y // Rotate on Y axis only (stays upright) — most common
|
|
30
|
+
BillboardMode.BM_ALL // Rotate on all axes (fully faces camera)
|
|
31
|
+
BillboardMode.BM_X // Rotate on X axis only
|
|
32
|
+
BillboardMode.BM_Z // Rotate on Z axis only
|
|
33
|
+
BillboardMode.BM_NONE // No billboard rotation
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- Prefer `BM_Y` over `BM_ALL` for most use cases — it looks more natural and is cheaper to render.
|
|
37
|
+
- `BM_ALL` is useful for particles or effects that should always directly face the camera.
|
|
38
|
+
|
|
39
|
+
## TextShape (3D Text)
|
|
40
|
+
|
|
41
|
+
Render text directly in 3D space:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { engine, Transform, TextShape, TextAlignMode } from '@dcl/sdk/ecs'
|
|
45
|
+
import { Vector3, Color4 } from '@dcl/sdk/math'
|
|
46
|
+
|
|
47
|
+
const label = engine.addEntity()
|
|
48
|
+
Transform.create(label, { position: Vector3.create(8, 3, 8) })
|
|
49
|
+
|
|
50
|
+
TextShape.create(label, {
|
|
51
|
+
text: 'Hello World!',
|
|
52
|
+
fontSize: 24,
|
|
53
|
+
fontWeight: 'bold',
|
|
54
|
+
color: Color4.White(),
|
|
55
|
+
outlineColor: Color4.Black(),
|
|
56
|
+
outlineWidth: 0.1,
|
|
57
|
+
textAlign: TextAlignMode.TAM_MIDDLE_CENTER
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Text Alignment Options
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
TextAlignMode.TAM_TOP_LEFT
|
|
65
|
+
TextAlignMode.TAM_TOP_CENTER
|
|
66
|
+
TextAlignMode.TAM_TOP_RIGHT
|
|
67
|
+
TextAlignMode.TAM_MIDDLE_LEFT
|
|
68
|
+
TextAlignMode.TAM_MIDDLE_CENTER
|
|
69
|
+
TextAlignMode.TAM_MIDDLE_RIGHT
|
|
70
|
+
TextAlignMode.TAM_BOTTOM_LEFT
|
|
71
|
+
TextAlignMode.TAM_BOTTOM_CENTER
|
|
72
|
+
TextAlignMode.TAM_BOTTOM_RIGHT
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Floating Label (Billboard + TextShape)
|
|
76
|
+
|
|
77
|
+
Combine Billboard and TextShape to create labels that always face the player:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const floatingLabel = engine.addEntity()
|
|
81
|
+
Transform.create(floatingLabel, { position: Vector3.create(8, 4, 8) })
|
|
82
|
+
|
|
83
|
+
TextShape.create(floatingLabel, {
|
|
84
|
+
text: 'NPC Name',
|
|
85
|
+
fontSize: 16,
|
|
86
|
+
color: Color4.White(),
|
|
87
|
+
outlineColor: Color4.Black(),
|
|
88
|
+
outlineWidth: 0.08,
|
|
89
|
+
textAlign: TextAlignMode.TAM_BOTTOM_CENTER
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
Billboard.create(floatingLabel, {
|
|
93
|
+
billboardMode: BillboardMode.BM_Y
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Advanced PBR Materials
|
|
98
|
+
|
|
99
|
+
### Metallic and Roughness
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { engine, Transform, MeshRenderer, Material, MaterialTransparencyMode } from '@dcl/sdk/ecs'
|
|
103
|
+
import { Color4, Color3 } from '@dcl/sdk/math'
|
|
104
|
+
|
|
105
|
+
// Shiny metal
|
|
106
|
+
Material.setPbrMaterial(entity, {
|
|
107
|
+
albedoColor: Color4.create(0.8, 0.8, 0.9, 1),
|
|
108
|
+
metallic: 1.0,
|
|
109
|
+
roughness: 0.1
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// Rough stone
|
|
113
|
+
Material.setPbrMaterial(entity, {
|
|
114
|
+
albedoColor: Color4.create(0.5, 0.5, 0.5, 1),
|
|
115
|
+
metallic: 0.0,
|
|
116
|
+
roughness: 0.9
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Transparency
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// Alpha blend — smooth transparency
|
|
124
|
+
Material.setPbrMaterial(entity, {
|
|
125
|
+
albedoColor: Color4.create(1, 0, 0, 0.5), // 50% transparent red
|
|
126
|
+
transparencyMode: MaterialTransparencyMode.MTM_ALPHA_BLEND
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Alpha test — cutout (binary visible/invisible based on threshold)
|
|
130
|
+
Material.setPbrMaterial(entity, {
|
|
131
|
+
texture: Material.Texture.Common({ src: 'assets/cutout.png' }),
|
|
132
|
+
transparencyMode: MaterialTransparencyMode.MTM_ALPHA_TEST,
|
|
133
|
+
alphaTest: 0.5
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Emissive (Glow Effects)
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// Glowing material
|
|
141
|
+
Material.setPbrMaterial(entity, {
|
|
142
|
+
albedoColor: Color4.create(0, 0, 0, 1),
|
|
143
|
+
emissiveColor: Color4.create(0, 1, 0, 1), // Green glow
|
|
144
|
+
emissiveIntensity: 2.0
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
// Emissive with texture
|
|
148
|
+
Material.setPbrMaterial(entity, {
|
|
149
|
+
texture: Material.Texture.Common({ src: 'assets/diffuse.png' }),
|
|
150
|
+
emissiveTexture: Material.Texture.Common({ src: 'assets/emissive.png' }),
|
|
151
|
+
emissiveIntensity: 1.0,
|
|
152
|
+
emissiveColor: Color3.White()
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Texture Maps
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
Material.setPbrMaterial(entity, {
|
|
160
|
+
texture: Material.Texture.Common({ src: 'assets/diffuse.png' }),
|
|
161
|
+
bumpTexture: Material.Texture.Common({ src: 'assets/normal.png' }),
|
|
162
|
+
emissiveTexture: Material.Texture.Common({ src: 'assets/emissive.png' })
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## GltfNodeModifiers
|
|
167
|
+
|
|
168
|
+
Override visibility or materials on specific nodes within a GLTF model:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { engine, Transform, GltfContainer, GltfNodeModifiers, Material } from '@dcl/sdk/ecs'
|
|
172
|
+
import { Vector3, Color4 } from '@dcl/sdk/math'
|
|
173
|
+
|
|
174
|
+
const model = engine.addEntity()
|
|
175
|
+
GltfContainer.create(model, { src: 'models/myModel.glb' })
|
|
176
|
+
Transform.create(model, { position: Vector3.create(4, 0, 4) })
|
|
177
|
+
|
|
178
|
+
// Override material of the entire model (empty path = whole model)
|
|
179
|
+
GltfNodeModifiers.create(model, {
|
|
180
|
+
modifiers: [
|
|
181
|
+
{
|
|
182
|
+
path: '', // empty string = all nodes
|
|
183
|
+
materialOverride: Material.setPbrMaterial(model, {
|
|
184
|
+
albedoColor: Color4.Red()
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Use node paths to target specific parts of a model for visibility or material changes.
|
|
192
|
+
|
|
193
|
+
## VisibilityComponent
|
|
194
|
+
|
|
195
|
+
Show or hide entities without removing them:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { engine, VisibilityComponent } from '@dcl/sdk/ecs'
|
|
199
|
+
|
|
200
|
+
// Hide an entity
|
|
201
|
+
VisibilityComponent.create(entity, { visible: false })
|
|
202
|
+
|
|
203
|
+
// Toggle visibility
|
|
204
|
+
const visibility = VisibilityComponent.getMutable(entity)
|
|
205
|
+
visibility.visible = !visibility.visible
|
|
206
|
+
|
|
207
|
+
// Useful for LOD (Level of Detail)
|
|
208
|
+
function lodSystem() {
|
|
209
|
+
const playerPos = Transform.get(engine.PlayerEntity).position
|
|
210
|
+
|
|
211
|
+
for (const [entity, transform] of engine.getEntitiesWith(Transform, MeshRenderer)) {
|
|
212
|
+
const distance = Vector3.distance(playerPos, transform.position)
|
|
213
|
+
|
|
214
|
+
if (distance > 30) {
|
|
215
|
+
VisibilityComponent.createOrReplace(entity, { visible: false })
|
|
216
|
+
} else {
|
|
217
|
+
VisibilityComponent.createOrReplace(entity, { visible: true })
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
engine.addSystem(lodSystem)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Best Practices
|
|
226
|
+
|
|
227
|
+
- Use `BillboardMode.BM_Y` instead of `BM_ALL` — looks more natural and renders faster
|
|
228
|
+
- Keep `fontSize` readable (16-32 for in-world text)
|
|
229
|
+
- Add `outlineColor` and `outlineWidth` to TextShape for legibility against any background
|
|
230
|
+
- Use `emissiveColor` with a dark `albedoColor` for maximum glow visibility
|
|
231
|
+
- `MTM_ALPHA_TEST` is cheaper than `MTM_ALPHA_BLEND` — use cutout when smooth transparency isn't needed
|
|
232
|
+
- Combine Billboard + TextShape for floating name labels above NPCs or objects
|
|
233
|
+
- Use VisibilityComponent for LOD systems instead of removing/re-adding entities
|
|
234
|
+
|
|
235
|
+
For more component details, see `context/components-reference.md`.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: animations-tweens
|
|
3
|
+
description: Animate objects in Decentraland scenes using Animator (GLTF animations), Tween (move/rotate/scale over time), and TweenSequence (chain animations). Use when user wants to animate, move, rotate, spin, slide, or create motion effects.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Animations and Tweens in Decentraland
|
|
7
|
+
|
|
8
|
+
## GLTF Animations (Animator)
|
|
9
|
+
|
|
10
|
+
Play animations embedded in .glb models:
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { engine, Transform, GltfContainer, Animator } from '@dcl/sdk/ecs'
|
|
14
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
15
|
+
|
|
16
|
+
const character = engine.addEntity()
|
|
17
|
+
Transform.create(character, { position: Vector3.create(8, 0, 8) })
|
|
18
|
+
GltfContainer.create(character, { src: 'models/character.glb' })
|
|
19
|
+
|
|
20
|
+
// Set up animation states
|
|
21
|
+
Animator.create(character, {
|
|
22
|
+
states: [
|
|
23
|
+
{ clip: 'idle', playing: true, loop: true, speed: 1 },
|
|
24
|
+
{ clip: 'walk', playing: false, loop: true, speed: 1 },
|
|
25
|
+
{ clip: 'attack', playing: false, loop: false, speed: 1.5 }
|
|
26
|
+
]
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// Play a specific animation
|
|
30
|
+
Animator.playSingleAnimation(character, 'walk')
|
|
31
|
+
|
|
32
|
+
// Stop all animations
|
|
33
|
+
Animator.stopAllAnimations(character)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Switching Animations
|
|
37
|
+
```typescript
|
|
38
|
+
function playAnimation(entity: Entity, clipName: string) {
|
|
39
|
+
const animator = Animator.getMutable(entity)
|
|
40
|
+
// Stop all
|
|
41
|
+
for (const state of animator.states) {
|
|
42
|
+
state.playing = false
|
|
43
|
+
}
|
|
44
|
+
// Play the desired one
|
|
45
|
+
const state = animator.states.find(s => s.clip === clipName)
|
|
46
|
+
if (state) {
|
|
47
|
+
state.playing = true
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Tweens (Code-Based Animation)
|
|
53
|
+
|
|
54
|
+
Animate entity properties smoothly over time:
|
|
55
|
+
|
|
56
|
+
### Move
|
|
57
|
+
```typescript
|
|
58
|
+
import { engine, Transform, Tween, EasingFunction } from '@dcl/sdk/ecs'
|
|
59
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
60
|
+
|
|
61
|
+
const box = engine.addEntity()
|
|
62
|
+
Transform.create(box, { position: Vector3.create(2, 1, 8) })
|
|
63
|
+
|
|
64
|
+
Tween.create(box, {
|
|
65
|
+
mode: Tween.Mode.Move({
|
|
66
|
+
start: Vector3.create(2, 1, 8),
|
|
67
|
+
end: Vector3.create(14, 1, 8)
|
|
68
|
+
}),
|
|
69
|
+
duration: 2000, // milliseconds
|
|
70
|
+
easingFunction: EasingFunction.EF_EASEINOUTSINE
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Rotate
|
|
75
|
+
```typescript
|
|
76
|
+
Tween.create(box, {
|
|
77
|
+
mode: Tween.Mode.Rotate({
|
|
78
|
+
start: Quaternion.fromEulerDegrees(0, 0, 0),
|
|
79
|
+
end: Quaternion.fromEulerDegrees(0, 360, 0)
|
|
80
|
+
}),
|
|
81
|
+
duration: 3000,
|
|
82
|
+
easingFunction: EasingFunction.EF_LINEAR
|
|
83
|
+
})
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Scale
|
|
87
|
+
```typescript
|
|
88
|
+
Tween.create(box, {
|
|
89
|
+
mode: Tween.Mode.Scale({
|
|
90
|
+
start: Vector3.create(1, 1, 1),
|
|
91
|
+
end: Vector3.create(2, 2, 2)
|
|
92
|
+
}),
|
|
93
|
+
duration: 1000,
|
|
94
|
+
easingFunction: EasingFunction.EF_EASEOUTBOUNCE
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Tween Sequences (Chained Animations)
|
|
99
|
+
|
|
100
|
+
Chain multiple tweens to play one after another:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { TweenSequence } from '@dcl/sdk/ecs'
|
|
104
|
+
|
|
105
|
+
// First tween
|
|
106
|
+
Tween.create(box, {
|
|
107
|
+
mode: Tween.Mode.Move({
|
|
108
|
+
start: Vector3.create(2, 1, 8),
|
|
109
|
+
end: Vector3.create(14, 1, 8)
|
|
110
|
+
}),
|
|
111
|
+
duration: 2000,
|
|
112
|
+
easingFunction: EasingFunction.EF_EASEINOUTSINE
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// Chain sequence
|
|
116
|
+
TweenSequence.create(box, {
|
|
117
|
+
sequence: [
|
|
118
|
+
// Second: move back
|
|
119
|
+
{
|
|
120
|
+
mode: Tween.Mode.Move({
|
|
121
|
+
start: Vector3.create(14, 1, 8),
|
|
122
|
+
end: Vector3.create(2, 1, 8)
|
|
123
|
+
}),
|
|
124
|
+
duration: 2000,
|
|
125
|
+
easingFunction: EasingFunction.EF_EASEINOUTSINE
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
loop: TweenLoop.TL_RESTART // Loop the entire sequence
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Easing Functions
|
|
133
|
+
|
|
134
|
+
Available easing functions from `EasingFunction`:
|
|
135
|
+
- `EF_LINEAR` — Constant speed
|
|
136
|
+
- `EF_EASEINQUAD` / `EF_EASEOUTQUAD` / `EF_EASEINOUTQUAD` — Quadratic
|
|
137
|
+
- `EF_EASEINSINE` / `EF_EASEOUTSINE` / `EF_EASEINOUTSINE` — Sinusoidal (smooth)
|
|
138
|
+
- `EF_EASEINEXPO` / `EF_EASEOUTEXPO` / `EF_EASEINOUTEXPO` — Exponential
|
|
139
|
+
- `EF_EASEINELASTIC` / `EF_EASEOUTELASTIC` / `EF_EASEINOUTELASTIC` — Elastic bounce
|
|
140
|
+
- `EF_EASEOUTBOUNCE` / `EF_EASEINBOUNCE` / `EF_EASEINOUTBOUNCE` — Bounce effect
|
|
141
|
+
- `EF_EASEINBACK` / `EF_EASEOUTBACK` / `EF_EASEINOUTBACK` — Overshoot
|
|
142
|
+
|
|
143
|
+
## Custom Animation Systems
|
|
144
|
+
|
|
145
|
+
For complex animations, create a system:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// Continuous rotation system
|
|
149
|
+
function spinSystem(dt: number) {
|
|
150
|
+
for (const [entity] of engine.getEntitiesWith(Transform, Spinner)) {
|
|
151
|
+
const transform = Transform.getMutable(entity)
|
|
152
|
+
const spinner = Spinner.get(entity)
|
|
153
|
+
// Rotate around Y axis
|
|
154
|
+
const currentRotation = Quaternion.toEulerAngles(transform.rotation)
|
|
155
|
+
transform.rotation = Quaternion.fromEulerDegrees(
|
|
156
|
+
currentRotation.x,
|
|
157
|
+
currentRotation.y + spinner.speed * dt,
|
|
158
|
+
currentRotation.z
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
engine.addSystem(spinSystem)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Best Practices
|
|
167
|
+
|
|
168
|
+
- Use Tweens for simple A-to-B animations (doors, platforms, UI elements)
|
|
169
|
+
- Use Animator for character/model animations baked into GLTF files
|
|
170
|
+
- Use Systems for continuous or physics-based animations
|
|
171
|
+
- Tween durations are in **milliseconds** (1000 = 1 second)
|
|
172
|
+
- Combine move + rotate tweens by applying them to parent/child entities
|
|
173
|
+
- For looping: use `TweenSequence` with `loop: TweenLoop.TL_RESTART`
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audio-video
|
|
3
|
+
description: Add audio sources, sound effects, music, audio streaming, and video players to Decentraland scenes. Use when user wants sound, music, audio, video screens, speakers, or media playback.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Audio and Video in Decentraland
|
|
7
|
+
|
|
8
|
+
## Audio Source (Sound Effects & Music)
|
|
9
|
+
|
|
10
|
+
Play audio clips from files:
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { engine, Transform, AudioSource } from '@dcl/sdk/ecs'
|
|
14
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
15
|
+
|
|
16
|
+
const speaker = engine.addEntity()
|
|
17
|
+
Transform.create(speaker, { position: Vector3.create(8, 1, 8) })
|
|
18
|
+
|
|
19
|
+
AudioSource.create(speaker, {
|
|
20
|
+
audioClipUrl: 'sounds/music.mp3',
|
|
21
|
+
playing: true,
|
|
22
|
+
loop: true,
|
|
23
|
+
volume: 0.5, // 0 to 1
|
|
24
|
+
pitch: 1.0 // Playback speed (0.5 = half speed, 2.0 = double)
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Supported Formats
|
|
29
|
+
- `.mp3` (recommended)
|
|
30
|
+
- `.ogg`
|
|
31
|
+
- `.wav`
|
|
32
|
+
|
|
33
|
+
### File Organization
|
|
34
|
+
```
|
|
35
|
+
project/
|
|
36
|
+
├── sounds/
|
|
37
|
+
│ ├── click.mp3
|
|
38
|
+
│ ├── background-music.mp3
|
|
39
|
+
│ └── explosion.ogg
|
|
40
|
+
├── src/
|
|
41
|
+
│ └── index.ts
|
|
42
|
+
└── scene.json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Play/Stop/Toggle
|
|
46
|
+
```typescript
|
|
47
|
+
// Play
|
|
48
|
+
AudioSource.getMutable(speaker).playing = true
|
|
49
|
+
|
|
50
|
+
// Stop
|
|
51
|
+
AudioSource.getMutable(speaker).playing = false
|
|
52
|
+
|
|
53
|
+
// Toggle
|
|
54
|
+
const audio = AudioSource.getMutable(speaker)
|
|
55
|
+
audio.playing = !audio.playing
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Play on Click
|
|
59
|
+
```typescript
|
|
60
|
+
import { pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
|
|
61
|
+
|
|
62
|
+
const button = engine.addEntity()
|
|
63
|
+
// ... set up transform and mesh ...
|
|
64
|
+
|
|
65
|
+
const audioEntity = engine.addEntity()
|
|
66
|
+
Transform.create(audioEntity, { position: Vector3.create(8, 1, 8) })
|
|
67
|
+
AudioSource.create(audioEntity, {
|
|
68
|
+
audioClipUrl: 'sounds/click.mp3',
|
|
69
|
+
playing: false,
|
|
70
|
+
loop: false,
|
|
71
|
+
volume: 0.8
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
pointerEventsSystem.onPointerDown(
|
|
75
|
+
{ entity: button, opts: { button: InputAction.IA_POINTER, hoverText: 'Play sound' } },
|
|
76
|
+
() => {
|
|
77
|
+
// Reset and play
|
|
78
|
+
const audio = AudioSource.getMutable(audioEntity)
|
|
79
|
+
audio.playing = false
|
|
80
|
+
audio.playing = true
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Audio Streaming
|
|
86
|
+
|
|
87
|
+
Stream audio from a URL (radio, live streams):
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { engine, Transform, AudioStream } from '@dcl/sdk/ecs'
|
|
91
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
92
|
+
|
|
93
|
+
const radio = engine.addEntity()
|
|
94
|
+
Transform.create(radio, { position: Vector3.create(8, 1, 8) })
|
|
95
|
+
|
|
96
|
+
AudioStream.create(radio, {
|
|
97
|
+
url: 'https://example.com/stream.mp3',
|
|
98
|
+
playing: true,
|
|
99
|
+
volume: 0.3
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Video Player
|
|
104
|
+
|
|
105
|
+
Play video on a surface:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { engine, Transform, VideoPlayer, Material, MeshRenderer } from '@dcl/sdk/ecs'
|
|
109
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
110
|
+
|
|
111
|
+
// Create a screen
|
|
112
|
+
const screen = engine.addEntity()
|
|
113
|
+
Transform.create(screen, {
|
|
114
|
+
position: Vector3.create(8, 3, 15.9),
|
|
115
|
+
scale: Vector3.create(8, 4.5, 1) // 16:9 ratio
|
|
116
|
+
})
|
|
117
|
+
MeshRenderer.setPlane(screen)
|
|
118
|
+
|
|
119
|
+
// Add video player
|
|
120
|
+
VideoPlayer.create(screen, {
|
|
121
|
+
src: 'https://example.com/video.mp4',
|
|
122
|
+
playing: true,
|
|
123
|
+
loop: true,
|
|
124
|
+
volume: 0.5,
|
|
125
|
+
playbackRate: 1.0
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// Set material to show the video texture
|
|
129
|
+
Material.setPbrMaterial(screen, {
|
|
130
|
+
texture: Material.Texture.Video({ videoPlayerEntity: screen }),
|
|
131
|
+
roughness: 1,
|
|
132
|
+
emissiveColor: { r: 1, g: 1, b: 1 },
|
|
133
|
+
emissiveIntensity: 1,
|
|
134
|
+
emissiveTexture: Material.Texture.Video({ videoPlayerEntity: screen })
|
|
135
|
+
})
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Video Controls
|
|
139
|
+
```typescript
|
|
140
|
+
// Play
|
|
141
|
+
VideoPlayer.getMutable(screen).playing = true
|
|
142
|
+
|
|
143
|
+
// Pause
|
|
144
|
+
VideoPlayer.getMutable(screen).playing = false
|
|
145
|
+
|
|
146
|
+
// Change volume
|
|
147
|
+
VideoPlayer.getMutable(screen).volume = 0.8
|
|
148
|
+
|
|
149
|
+
// Change source
|
|
150
|
+
VideoPlayer.getMutable(screen).src = 'https://example.com/other.mp4'
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Spatial Audio
|
|
154
|
+
|
|
155
|
+
Audio in Decentraland is **spatial by default** — it gets louder as the player approaches the audio source entity and quieter as they move away. The position is determined by the entity's `Transform`.
|
|
156
|
+
|
|
157
|
+
To make audio non-spatial (same volume everywhere), there's no built-in flag — keep the volume low and place the audio at the scene center.
|
|
158
|
+
|
|
159
|
+
## Important Notes
|
|
160
|
+
|
|
161
|
+
- Audio files must be in the project's directory (relative paths from project root)
|
|
162
|
+
- Video requires HTTPS URLs — HTTP won't work
|
|
163
|
+
- Players must interact with the scene (click) before audio can play (browser autoplay policy)
|
|
164
|
+
- Keep audio files small — large files increase scene load time
|
|
165
|
+
- Use `.mp3` for music and `.ogg` for sound effects (smaller file sizes)
|
|
166
|
+
- Video playback requires the `ALLOW_MEDIA_HOSTNAMES` permission in scene.json for external URLs
|
|
167
|
+
- For live video streaming, use HLS (.m3u8) URLs when possible
|