@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,239 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: player-avatar
|
|
3
|
+
description: Work with player avatars in Decentraland scenes. Read player position and profile data, customize appearance with AvatarBase, trigger emotes with triggerEmote/triggerSceneEmote, read equipped wearables via AvatarEquippedData, attach objects to players with AvatarAttach, create NPC avatars with AvatarShape, and modify avatars in areas. Use when user wants player data, emotes, wearables, avatar attachments, or NPCs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Player and Avatar System in Decentraland
|
|
7
|
+
|
|
8
|
+
## Player Position and Movement
|
|
9
|
+
|
|
10
|
+
Access the player's position via the reserved `engine.PlayerEntity`:
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { engine, Transform } from '@dcl/sdk/ecs'
|
|
14
|
+
|
|
15
|
+
function trackPlayer() {
|
|
16
|
+
if (!Transform.has(engine.PlayerEntity)) return
|
|
17
|
+
|
|
18
|
+
const playerTransform = Transform.get(engine.PlayerEntity)
|
|
19
|
+
console.log('Player position:', playerTransform.position)
|
|
20
|
+
console.log('Player rotation:', playerTransform.rotation)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
engine.addSystem(trackPlayer)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Distance-Based Logic
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
30
|
+
|
|
31
|
+
function proximityCheck() {
|
|
32
|
+
const playerPos = Transform.get(engine.PlayerEntity).position
|
|
33
|
+
const npcPos = Transform.get(npcEntity).position
|
|
34
|
+
const distance = Vector3.distance(playerPos, npcPos)
|
|
35
|
+
|
|
36
|
+
if (distance < 5) {
|
|
37
|
+
console.log('Player is near the NPC')
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
engine.addSystem(proximityCheck)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Player Profile Data
|
|
45
|
+
|
|
46
|
+
Get the player's name, wallet address, and guest status:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { getPlayer } from '@dcl/sdk/src/players'
|
|
50
|
+
|
|
51
|
+
function main() {
|
|
52
|
+
const player = getPlayer()
|
|
53
|
+
if (player) {
|
|
54
|
+
console.log('Name:', player.name)
|
|
55
|
+
console.log('User ID:', player.userId)
|
|
56
|
+
console.log('Is guest:', player.isGuest)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `userId` — the player's Ethereum wallet address (or guest ID)
|
|
62
|
+
- `isGuest` — `true` if the player hasn't connected a wallet
|
|
63
|
+
|
|
64
|
+
## Avatar Attachments
|
|
65
|
+
|
|
66
|
+
Attach 3D objects to a player's avatar:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { engine, Transform, GltfContainer, AvatarAttach, AvatarAnchorPointType } from '@dcl/sdk/ecs'
|
|
70
|
+
|
|
71
|
+
const hat = engine.addEntity()
|
|
72
|
+
GltfContainer.create(hat, { src: 'models/hat.glb' })
|
|
73
|
+
|
|
74
|
+
// Attach to the local player's avatar
|
|
75
|
+
AvatarAttach.create(hat, {
|
|
76
|
+
anchorPointId: AvatarAnchorPointType.AAPT_NAME_TAG
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Anchor Points
|
|
81
|
+
|
|
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
|
+
```
|
|
88
|
+
|
|
89
|
+
### Attach to a Specific Player
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
AvatarAttach.create(hat, {
|
|
93
|
+
avatarId: '0x123...abc', // Target player's wallet address
|
|
94
|
+
anchorPointId: AvatarAnchorPointType.AAPT_RIGHT_HAND
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Triggering Emotes
|
|
99
|
+
|
|
100
|
+
### Default Emotes
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { triggerEmote } from '~system/RestrictedActions'
|
|
104
|
+
|
|
105
|
+
// Play a built-in emote
|
|
106
|
+
triggerEmote({ predefinedEmote: 'robot' })
|
|
107
|
+
triggerEmote({ predefinedEmote: 'wave' })
|
|
108
|
+
triggerEmote({ predefinedEmote: 'clap' })
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Custom Scene Emotes
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { triggerSceneEmote } from '~system/RestrictedActions'
|
|
115
|
+
|
|
116
|
+
// Play a custom emote animation (file must end with _emote.glb)
|
|
117
|
+
triggerSceneEmote({
|
|
118
|
+
src: 'animations/Snowball_Throw_emote.glb',
|
|
119
|
+
loop: false
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Notes:**
|
|
124
|
+
- Emotes play only while the player is standing still — walking or jumping interrupts them
|
|
125
|
+
- Custom emote files must have the `_emote.glb` suffix
|
|
126
|
+
|
|
127
|
+
## NPC Avatars
|
|
128
|
+
|
|
129
|
+
Create avatar-shaped NPCs using `AvatarShape`:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { engine, Transform, AvatarShape } from '@dcl/sdk/ecs'
|
|
133
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
134
|
+
|
|
135
|
+
const npc = engine.addEntity()
|
|
136
|
+
Transform.create(npc, { position: Vector3.create(8, 0, 8) })
|
|
137
|
+
|
|
138
|
+
AvatarShape.create(npc, {
|
|
139
|
+
id: 'npc-1',
|
|
140
|
+
name: 'Guard',
|
|
141
|
+
wearables: [
|
|
142
|
+
'urn:decentraland:off-chain:base-avatars:f_eyes_01',
|
|
143
|
+
'urn:decentraland:off-chain:base-avatars:f_eyebrows_01',
|
|
144
|
+
'urn:decentraland:off-chain:base-avatars:f_mouth_01',
|
|
145
|
+
'urn:decentraland:off-chain:base-avatars:sport_jacket',
|
|
146
|
+
'urn:decentraland:off-chain:base-avatars:oxford_pants'
|
|
147
|
+
]
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
NPC avatars are static — they display the avatar model but don't move or animate on their own. Combine with Animator or Tween for movement.
|
|
152
|
+
|
|
153
|
+
## Avatar Modifier Areas
|
|
154
|
+
|
|
155
|
+
Modify how avatars appear or behave in a region:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { engine, Transform, AvatarModifierArea, AvatarModifierType } from '@dcl/sdk/ecs'
|
|
159
|
+
import { Vector3 } from '@dcl/sdk/math'
|
|
160
|
+
|
|
161
|
+
const modifierArea = engine.addEntity()
|
|
162
|
+
Transform.create(modifierArea, {
|
|
163
|
+
position: Vector3.create(8, 1.5, 8),
|
|
164
|
+
scale: Vector3.create(4, 3, 4)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
AvatarModifierArea.create(modifierArea, {
|
|
168
|
+
area: { box: Vector3.create(4, 3, 4) },
|
|
169
|
+
modifiers: [AvatarModifierType.AMT_HIDE_AVATARS],
|
|
170
|
+
excludeIds: ['0x123...abc'] // Optional: exclude specific players
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Available Modifiers
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
AvatarModifierType.AMT_HIDE_AVATARS // Hide all avatars in the area
|
|
178
|
+
AvatarModifierType.AMT_DISABLE_PASSPORTS // Disable clicking on avatars to see profiles
|
|
179
|
+
AvatarModifierType.AMT_DISABLE_JUMPING // Prevent jumping in the area
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Movement Constraints
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// Prevent jumping in a specific area
|
|
186
|
+
const constraintArea = engine.addEntity()
|
|
187
|
+
Transform.create(constraintArea, {
|
|
188
|
+
position: Vector3.create(8, 5, 8),
|
|
189
|
+
scale: Vector3.create(6, 10, 6)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
AvatarModifierArea.create(constraintArea, {
|
|
193
|
+
area: { box: Vector3.create(6, 10, 6) },
|
|
194
|
+
modifiers: [AvatarModifierType.AMT_DISABLE_JUMPING]
|
|
195
|
+
})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Teleporting the Player
|
|
199
|
+
|
|
200
|
+
Move the player to a specific position:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// Move player position
|
|
204
|
+
const playerTransform = Transform.getMutable(engine.PlayerEntity)
|
|
205
|
+
playerTransform.position = Vector3.create(8, 0, 8)
|
|
206
|
+
|
|
207
|
+
// Move player with rotation
|
|
208
|
+
playerTransform.position = Vector3.create(8, 0, 8)
|
|
209
|
+
playerTransform.rotation = Quaternion.fromEulerDegrees(0, 180, 0)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Keeping Player in Bounds
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
function boundarySystem() {
|
|
216
|
+
const playerTransform = Transform.getMutable(engine.PlayerEntity)
|
|
217
|
+
const pos = playerTransform.position
|
|
218
|
+
|
|
219
|
+
// Clamp to scene bounds
|
|
220
|
+
if (pos.x < 0) playerTransform.position.x = 0
|
|
221
|
+
if (pos.x > 16) playerTransform.position.x = 16
|
|
222
|
+
if (pos.z < 0) playerTransform.position.z = 0
|
|
223
|
+
if (pos.z > 16) playerTransform.position.z = 16
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
engine.addSystem(boundarySystem)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Best Practices
|
|
230
|
+
|
|
231
|
+
- Always check `Transform.has(engine.PlayerEntity)` before reading player data — it may not be ready on the first frame
|
|
232
|
+
- Use `getPlayer()` to check `isGuest` before attempting wallet-dependent features
|
|
233
|
+
- `AvatarAttach` requires the target player to be in the same scene — attachments disappear when the player leaves
|
|
234
|
+
- Custom emote files must use the `_emote.glb` naming convention
|
|
235
|
+
- Use `AvatarModifierArea` with `AMT_HIDE_AVATARS` for private rooms or puzzle areas
|
|
236
|
+
- Add `excludeIds` to modifier areas when you want specific players (like the scene owner) to remain visible
|
|
237
|
+
- Teleporting the player works within scene bounds — respect parcel limits
|
|
238
|
+
|
|
239
|
+
For component field details, see `context/components-reference.md`.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: smart-items
|
|
3
|
+
description: Use Decentraland asset pack smart items with pre-built behaviors like doors, buttons, platforms, teleporters, and interactive objects. Use when user wants pre-built interactive items, asset packs, doors, elevators, buttons, teleporters, or drag-and-drop style items.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Smart Items in Decentraland
|
|
7
|
+
|
|
8
|
+
Smart Items are pre-built interactive objects from Decentraland's asset packs. They come with built-in behaviors and can be combined using actions and triggers.
|
|
9
|
+
|
|
10
|
+
## Available Asset Packs
|
|
11
|
+
|
|
12
|
+
| Pack | Contents |
|
|
13
|
+
|------|----------|
|
|
14
|
+
| **Smart Items** | Doors, buttons, platforms, teleporters, spawn points |
|
|
15
|
+
| **Cyberpunk** | Neon signs, holograms, futuristic furniture, vehicles |
|
|
16
|
+
| **Fantasy** | Medieval buildings, treasure chests, torches, trees |
|
|
17
|
+
| **Genesis City** | Urban buildings, street furniture, signs |
|
|
18
|
+
| **Sci-Fi** | Space station parts, control panels, airlocks |
|
|
19
|
+
| **Gallery** | Art frames, pedestals, display cases |
|
|
20
|
+
| **Steampunk** | Gears, pipes, Victorian furniture |
|
|
21
|
+
| **Pirates** | Ships, barrels, treasure, cannons |
|
|
22
|
+
| **Western** | Saloon, cacti, wagons, buildings |
|
|
23
|
+
|
|
24
|
+
## Using Smart Items via Code
|
|
25
|
+
|
|
26
|
+
Smart items from the Creator Hub use a component-based system. When coding manually, you can recreate their behavior:
|
|
27
|
+
|
|
28
|
+
### Door (Open/Close on Click)
|
|
29
|
+
```typescript
|
|
30
|
+
import { engine, Transform, GltfContainer, Animator, pointerEventsSystem, InputAction } from '@dcl/sdk/ecs'
|
|
31
|
+
import { Vector3, Quaternion } from '@dcl/sdk/math'
|
|
32
|
+
|
|
33
|
+
const door = engine.addEntity()
|
|
34
|
+
Transform.create(door, { position: Vector3.create(8, 0, 8) })
|
|
35
|
+
GltfContainer.create(door, { src: 'models/door.glb' })
|
|
36
|
+
|
|
37
|
+
let isOpen = false
|
|
38
|
+
|
|
39
|
+
// If the model has animations
|
|
40
|
+
Animator.create(door, {
|
|
41
|
+
states: [
|
|
42
|
+
{ clip: 'open', playing: false, loop: false },
|
|
43
|
+
{ clip: 'close', playing: false, loop: false }
|
|
44
|
+
]
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
pointerEventsSystem.onPointerDown(
|
|
48
|
+
{ entity: door, opts: { button: InputAction.IA_POINTER, hoverText: 'Open/Close' } },
|
|
49
|
+
() => {
|
|
50
|
+
isOpen = !isOpen
|
|
51
|
+
if (isOpen) {
|
|
52
|
+
Animator.playSingleAnimation(door, 'open')
|
|
53
|
+
} else {
|
|
54
|
+
Animator.playSingleAnimation(door, 'close')
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Button (Trigger Action)
|
|
61
|
+
```typescript
|
|
62
|
+
const button = engine.addEntity()
|
|
63
|
+
Transform.create(button, { position: Vector3.create(6, 1, 8) })
|
|
64
|
+
GltfContainer.create(button, { src: 'models/button.glb' })
|
|
65
|
+
|
|
66
|
+
Animator.create(button, {
|
|
67
|
+
states: [{ clip: 'press', playing: false, loop: false }]
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
pointerEventsSystem.onPointerDown(
|
|
71
|
+
{ entity: button, opts: { button: InputAction.IA_POINTER, hoverText: 'Press' } },
|
|
72
|
+
() => {
|
|
73
|
+
Animator.playSingleAnimation(button, 'press')
|
|
74
|
+
// Trigger linked action
|
|
75
|
+
onButtonPressed()
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
function onButtonPressed() {
|
|
80
|
+
// Do something — open a door, show UI, play sound, etc.
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Platform (Moving Platform)
|
|
85
|
+
```typescript
|
|
86
|
+
import { Tween, EasingFunction } from '@dcl/sdk/ecs'
|
|
87
|
+
import { TweenSequence, TweenLoop } from '@dcl/sdk/ecs'
|
|
88
|
+
|
|
89
|
+
const platform = engine.addEntity()
|
|
90
|
+
Transform.create(platform, { position: Vector3.create(8, 0, 8) })
|
|
91
|
+
GltfContainer.create(platform, { src: 'models/platform.glb' })
|
|
92
|
+
|
|
93
|
+
// Move up and down
|
|
94
|
+
Tween.create(platform, {
|
|
95
|
+
mode: Tween.Mode.Move({
|
|
96
|
+
start: Vector3.create(8, 0, 8),
|
|
97
|
+
end: Vector3.create(8, 5, 8)
|
|
98
|
+
}),
|
|
99
|
+
duration: 3000,
|
|
100
|
+
easingFunction: EasingFunction.EF_EASEINOUTSINE
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
TweenSequence.create(platform, {
|
|
104
|
+
sequence: [{
|
|
105
|
+
mode: Tween.Mode.Move({
|
|
106
|
+
start: Vector3.create(8, 5, 8),
|
|
107
|
+
end: Vector3.create(8, 0, 8)
|
|
108
|
+
}),
|
|
109
|
+
duration: 3000,
|
|
110
|
+
easingFunction: EasingFunction.EF_EASEINOUTSINE
|
|
111
|
+
}],
|
|
112
|
+
loop: TweenLoop.TL_RESTART
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Teleporter
|
|
117
|
+
```typescript
|
|
118
|
+
import { movePlayerTo } from '~system/RestrictedActions'
|
|
119
|
+
|
|
120
|
+
const teleporter = engine.addEntity()
|
|
121
|
+
Transform.create(teleporter, { position: Vector3.create(4, 0, 4) })
|
|
122
|
+
GltfContainer.create(teleporter, { src: 'models/teleporter.glb' })
|
|
123
|
+
|
|
124
|
+
pointerEventsSystem.onPointerDown(
|
|
125
|
+
{ entity: teleporter, opts: { button: InputAction.IA_POINTER, hoverText: 'Teleport' } },
|
|
126
|
+
() => {
|
|
127
|
+
// Move player to target position
|
|
128
|
+
void movePlayerTo({
|
|
129
|
+
newRelativePosition: Vector3.create(12, 0, 12),
|
|
130
|
+
cameraTarget: Vector3.create(12, 1, 13)
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Note**: `movePlayerTo` requires the `ALLOW_TO_MOVE_PLAYER_INSIDE_SCENE` permission in scene.json.
|
|
137
|
+
|
|
138
|
+
## Action/Trigger Pattern
|
|
139
|
+
|
|
140
|
+
Smart items in Creator Hub use an action/trigger pattern. You can replicate this:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// Action registry
|
|
144
|
+
type Action = () => void
|
|
145
|
+
const actions: Map<string, Action> = new Map()
|
|
146
|
+
|
|
147
|
+
function registerAction(name: string, action: Action) {
|
|
148
|
+
actions.set(name, action)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function triggerAction(name: string) {
|
|
152
|
+
const action = actions.get(name)
|
|
153
|
+
if (action) action()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Register actions
|
|
157
|
+
registerAction('openDoor', () => {
|
|
158
|
+
Animator.playSingleAnimation(door, 'open')
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
registerAction('playSound', () => {
|
|
162
|
+
AudioSource.getMutable(speaker).playing = true
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// Trigger from button
|
|
166
|
+
pointerEventsSystem.onPointerDown(
|
|
167
|
+
{ entity: button, opts: { button: InputAction.IA_POINTER, hoverText: 'Activate' } },
|
|
168
|
+
() => {
|
|
169
|
+
triggerAction('openDoor')
|
|
170
|
+
triggerAction('playSound')
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Tips
|
|
176
|
+
|
|
177
|
+
- Smart items from Creator Hub export as code — you can inspect and modify the generated code
|
|
178
|
+
- For complex item interactions, use an event bus pattern (register/trigger actions)
|
|
179
|
+
- Models for smart items are in the asset-packs repository
|
|
180
|
+
- Keep smart item models lightweight (under 1,000 triangles each)
|
|
181
|
+
- Combine multiple simple smart items rather than creating one complex one
|