@dcl/asset-packs 2.6.0 → 2.6.1-20251020110502.commit-097464e

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.
Files changed (32) hide show
  1. package/bin/index.js +837 -522
  2. package/dclcontext/npc.mdc +224 -285
  3. package/dclcontext/sdk7-complete-reference.md +39 -12
  4. package/dclcontext/sdk7-examples.mdc +43 -23
  5. package/dist/admin-toolkit-ui/Header.js +13 -4
  6. package/dist/admin-toolkit-ui/ModerationControl/AddUserInput.d.ts +9 -3
  7. package/dist/admin-toolkit-ui/ModerationControl/AddUserInput.js +75 -58
  8. package/dist/admin-toolkit-ui/ModerationControl/BanUserDescription.d.ts +6 -0
  9. package/dist/admin-toolkit-ui/ModerationControl/BanUserDescription.js +16 -0
  10. package/dist/admin-toolkit-ui/ModerationControl/RemoveAdminConfirmation.js +1 -1
  11. package/dist/admin-toolkit-ui/ModerationControl/UsersList.d.ts +16 -0
  12. package/dist/admin-toolkit-ui/ModerationControl/UsersList.js +136 -0
  13. package/dist/admin-toolkit-ui/ModerationControl/api.d.ts +24 -2
  14. package/dist/admin-toolkit-ui/ModerationControl/api.js +33 -7
  15. package/dist/admin-toolkit-ui/ModerationControl/index.d.ts +4 -1
  16. package/dist/admin-toolkit-ui/ModerationControl/index.js +21 -22
  17. package/dist/admin-toolkit-ui/ModerationControl/styles/AddUserInputStyles.d.ts +25 -0
  18. package/dist/admin-toolkit-ui/ModerationControl/styles/AddUserInputStyles.js +73 -0
  19. package/dist/admin-toolkit-ui/ModerationControl/styles/ModerationControlStyles.d.ts +6 -0
  20. package/dist/admin-toolkit-ui/ModerationControl/styles/ModerationControlStyles.js +31 -0
  21. package/dist/admin-toolkit-ui/ModerationControl/styles/UsersListStyles.d.ts +48 -0
  22. package/dist/admin-toolkit-ui/ModerationControl/styles/UsersListStyles.js +206 -0
  23. package/dist/admin-toolkit-ui/ModerationControl/utils.d.ts +11 -0
  24. package/dist/admin-toolkit-ui/ModerationControl/utils.js +41 -0
  25. package/dist/admin-toolkit-ui/SmartItemsControl.js +4 -4
  26. package/dist/admin-toolkit-ui/TextAnnouncementsControl.js +5 -3
  27. package/dist/admin-toolkit-ui/index.d.ts +2 -0
  28. package/dist/admin-toolkit-ui/index.js +31 -18
  29. package/dist/bin/index.js +837 -522
  30. package/package.json +2 -2
  31. package/dist/admin-toolkit-ui/ModerationControl/AdminList.d.ts +0 -10
  32. package/dist/admin-toolkit-ui/ModerationControl/AdminList.js +0 -192
@@ -1,21 +1,30 @@
1
- # @dcl-sdk/npc Context7 Reference
1
+ # dcl-npc-toolkit Context7 Reference
2
2
 
3
3
  ## Installation & Import
4
+
5
+ To install the NPC toolkit, run the following command:
6
+
7
+ ```bash
8
+ npm i dcl-npc-toolkit
9
+ ```
10
+
11
+ Then import the toolkit in your code:
12
+
4
13
  ```typescript
5
- // NPC functionality is built into the SDK
6
- import { AvatarShape, Transform, engine } from '@dcl/sdk/ecs'
7
- import { Vector3, Color3 } from '@dcl/sdk/math'
8
- import { getPlayer } from '@dcl/sdk/src/players'
14
+ import { createNPC, Dialog } from 'dcl-npc-toolkit'
9
15
  ```
10
16
 
17
+ Then you can use the toolkit to create NPCs.
18
+
19
+
11
20
  ## Basic NPC Creation
12
21
 
13
22
  ### Create Simple NPC
14
23
  ```typescript
15
- const npc = engine.addEntity()
24
+ const npcEntity = engine.addEntity()
16
25
 
17
26
  // Add avatar shape component
18
- AvatarShape.create(npc, {
27
+ AvatarShape.create(npcEntity, {
19
28
  id: 'npc-001', // Required: unique identifier
20
29
  name: 'Guide NPC', // Display name (optional, default: "NPC")
21
30
  bodyShape: 'urn:decentraland:off-chain:base-avatars:BaseMale', // Optional
@@ -31,7 +40,7 @@ AvatarShape.create(npc, {
31
40
  })
32
41
 
33
42
  // Position the NPC
34
- Transform.create(npc, {
43
+ Transform.create(npcEntity, {
35
44
  position: Vector3.create(8, 0.25, 8),
36
45
  rotation: { x: 0, y: 0, z: 0, w: 1 }
37
46
  })
@@ -48,6 +57,27 @@ Transform.create(randomNPC, {
48
57
  })
49
58
  ```
50
59
 
60
+ ### Create NPC with toolkit (GLB + dialog)
61
+ ```typescript
62
+ // Use the toolkit to instantiate an NPC from a GLB with a simple dialog and activation callback
63
+ const guide = createNPC(
64
+ {
65
+ position: { x: 8, y: 0, z: 8 },
66
+ rotation: { x: 0, y: 0, z: 0, w: 1 },
67
+ scale: { x: 1, y: 1, z: 1 }
68
+ },
69
+ {
70
+ type: 'custom',
71
+ model: 'models/guide.glb',
72
+ dialog: [{ text: "Welcome!", isEndOfDialog: true } as Dialog],
73
+ onActivate: () => {
74
+ // Called on click; open dialog or run logic
75
+ console.log('Guide activated')
76
+ }
77
+ }
78
+ )
79
+ ```
80
+
51
81
  ## NPC Animations & Emotes
52
82
 
53
83
  ### Play Predefined Emotes
@@ -151,128 +181,153 @@ pointerEventsSystem.onPointerDown(
151
181
  )
152
182
  ```
153
183
 
154
- ### Proximity Interaction
184
+ ### Proximity Interaction (TriggerArea)
155
185
  ```typescript
156
- import * as utils from '@dcl-sdk/utils'
157
-
158
- // Create trigger area around NPC
159
- utils.triggers.addTrigger(
160
- npc,
161
- utils.LAYER_1, // NPC layer
162
- utils.LAYER_2, // Player layer
163
- [{ type: 'sphere', radius: 3 }],
164
- (playerEntity) => {
165
- console.log('Player entered NPC area')
166
- showNPCDialogue(npc)
167
- },
168
- (playerEntity) => {
169
- console.log('Player left NPC area')
170
- hideNPCDialogue()
171
- }
172
- )
186
+ // Use SDK7 TriggerArea directly (no @dcl-sdk/utils)
187
+ import { TriggerArea, triggerAreaEventsSystem, ColliderLayer } from '@dcl/sdk/ecs'
188
+
189
+ // Add a spherical trigger around the NPC
190
+ TriggerArea.setSphere(npcEntity, ColliderLayer.CL_PLAYER)
191
+ Transform.createOrReplace(npcEntity, {
192
+ position: Transform.get(npcEntity).position,
193
+ scale: Vector3.create(3, 3, 3) // radius ~3
194
+ })
195
+
196
+ // Listen for player entering/leaving
197
+ triggerAreaEventsSystem.onTriggerEnter(npcEntity, () => {
198
+ showNPCDialogue(npcEntity)
199
+ })
200
+ triggerAreaEventsSystem.onTriggerExit(npcEntity, () => {
201
+ hideNPCDialogue()
202
+ })
173
203
  ```
174
204
 
175
205
  ## NPC Dialogue Systems
176
206
 
177
207
  ### Simple Text Dialogue
178
208
  ```typescript
179
- import { ReactEcsRenderer } from '@dcl/sdk/react-ecs'
180
-
181
- let currentDialogue: string | null = null
182
-
183
- function showNPCDialogue(npcEntity: Entity) {
184
- const npcName = AvatarShape.get(npcEntity).name
185
- currentDialogue = `Hello! I'm ${npcName}. How can I help you?`
186
-
187
- ReactEcsRenderer.setUiRenderer(() => (
188
- <UiEntity
189
- uiTransform={{
190
- width: 400,
191
- height: 100,
192
- positionType: 'absolute',
193
- position: { bottom: '20%', left: '50%' },
194
- margin: { left: -200 }
195
- }}
196
- uiBackground={{ color: Color4.create(0, 0, 0, 0.8) }}
197
- >
198
- <Label
199
- value={currentDialogue || ''}
200
- color={Color4.White()}
201
- fontSize={16}
202
- textAlign="middle-center"
203
- />
204
- </UiEntity>
205
- ))
206
- }
207
-
208
- function hideNPCDialogue() {
209
- currentDialogue = null
210
- ReactEcsRenderer.setUiRenderer(() => null)
211
- }
209
+ // Create a talking NPC using the toolkit's Dialog[]
210
+ const greeter = createNPC(
211
+ { position: { x: 8, y: 0, z: 8 }, rotation: { x: 0, y: 0, z: 0, w: 1 }, scale: { x: 1, y: 1, z: 1 } },
212
+ {
213
+ type: 'custom',
214
+ model: 'models/greeter.glb',
215
+ dialog: [
216
+ { text: "Hello! I'm your guide. How can I help you today?", isEndOfDialog: true } as Dialog
217
+ ],
218
+ onActivate: () => {
219
+ // Optional extra behavior when clicked
220
+ console.log('Greeter activated')
221
+ }
222
+ }
223
+ )
212
224
  ```
213
225
 
214
226
  ### Multi-Choice Dialogue
215
227
  ```typescript
216
- interface DialogueOption {
217
- text: string
218
- action: () => void
219
- }
228
+ // Create an NPC with a multi-choice dialog flow via toolkit Dialog[]
229
+ const questGiver = createNPC(
230
+ { position: { x: 10, y: 0, z: 10 }, rotation: { x: 0, y: 0, z: 0, w: 1 }, scale: { x: 1, y: 1, z: 1 } },
231
+ {
232
+ type: 'custom',
233
+ model: 'models/quest_giver.glb',
234
+ dialog: [
235
+ {
236
+ text: 'Welcome, traveler! What do you seek?',
237
+ isQuestion: true,
238
+ buttons: [
239
+ { label: 'Tell me about this place', goToDialog: 1 },
240
+ { label: 'Give me a quest', goToDialog: 2 },
241
+ { label: 'Goodbye', goToDialog: 3 }
242
+ ]
243
+ } as unknown as Dialog,
244
+ { text: 'These lands are rich with secrets and stories.', isEndOfDialog: true } as Dialog,
245
+ { text: 'Take this task and return victorious!', isEndOfDialog: true } as Dialog,
246
+ { text: 'Safe travels!', isEndOfDialog: true } as Dialog
247
+ ]
248
+ }
249
+ )
250
+ ```
251
+
252
+ ### Toolkit Dialog UI vs Bubble UI (SDK7)
253
+
254
+ When opening dialogs on an EXISTING entity (not created via `createNPC`), the toolkit expects internal per-NPC data to exist. Otherwise, UI helpers (e.g., `getTheme`, `displayDialog`) may crash.
255
+
256
+ - UI Dialog (React HUD)
257
+ 1) Mount the toolkit UI once in your scene UI:
258
+ ```typescript
259
+ import { NpcUtilsUi } from 'dcl-npc-toolkit'
220
260
 
221
- function showDialogueOptions(npcEntity: Entity, options: DialogueOption[]) {
222
261
  ReactEcsRenderer.setUiRenderer(() => (
223
- <UiEntity
224
- uiTransform={{
225
- width: 400,
226
- height: 200,
227
- positionType: 'absolute',
228
- position: { bottom: '20%', left: '50%' },
229
- margin: { left: -200 }
230
- }}
231
- uiBackground={{ color: Color4.create(0, 0, 0, 0.8) }}
232
- >
233
- {options.map((option, index) => (
234
- <Button
235
- key={index}
236
- value={option.text}
237
- variant="secondary"
238
- fontSize={14}
239
- onMouseDown={option.action}
240
- uiTransform={{
241
- width: 350,
242
- height: 40,
243
- margin: { top: 10 }
244
- }}
245
- />
246
- ))}
262
+ <UiEntity uiTransform={{ width: '100%', height: '100%', positionType: 'absolute' }}>
263
+ <NpcUtilsUi />
247
264
  </UiEntity>
248
265
  ))
249
- }
266
+ ```
267
+ 2) Ensure the NPC has toolkit dialog data before opening a window:
268
+ ```typescript
269
+ import { addDialog } from 'dcl-npc-toolkit/dist/dialog'
270
+ import { openDialogWindow } from 'dcl-npc-toolkit'
271
+ import { npcDataComponent } from 'dcl-npc-toolkit/dist/npc'
272
+
273
+ function ensureNpcToolkitData(entity: Entity) {
274
+ if (npcDataComponent.has(entity)) return
275
+ npcDataComponent.set(entity as any, {
276
+ introduced: false,
277
+ inCooldown: false,
278
+ coolDownDuration: 5,
279
+ faceUser: undefined,
280
+ walkingSpeed: 2,
281
+ walkingAnim: undefined,
282
+ pathData: undefined,
283
+ currentPathData: [],
284
+ manualStop: false,
285
+ pathIndex: 0,
286
+ state: 'STANDING',
287
+ idleAnim: 'Idle',
288
+ hasBubble: false,
289
+ turnSpeed: 2,
290
+ theme: 'https://decentraland.org/images/ui/light-atlas-v3.png',
291
+ bubbleXOffset: 0,
292
+ bubbleYOffset: 0,
293
+ lastPlayedAnim: 'Idle'
294
+ })
295
+ }
250
296
 
251
- // Usage
252
- showDialogueOptions(npc, [
253
- { text: "Tell me about this place", action: () => console.log("Place info") },
254
- { text: "Give me a quest", action: () => console.log("Quest given") },
255
- { text: "Goodbye", action: () => hideNPCDialogue() }
256
- ])
257
- ```
297
+ // On setup
298
+ addDialog(npcEntity) // required for dialog state
299
+ ensureNpcToolkitData(npcEntity) // required for UI helpers
300
+
301
+ // On click
302
+ openDialogWindow(npcEntity, dialogs, startIndex)
303
+ ```
304
+
305
+ - Bubble Dialog (world-space speech bubbles)
306
+ 1) Initialize a bubble instance per entity BEFORE calling `talkBubble`:
307
+ ```typescript
308
+ import { addDialog } from 'dcl-npc-toolkit/dist/dialog'
309
+ import { createDialogBubble } from 'dcl-npc-toolkit/dist/bubble'
310
+ import { talkBubble } from 'dcl-npc-toolkit'
311
+
312
+ addDialog(npcEntity)
313
+ createDialogBubble(npcEntity)
314
+ talkBubble(npcEntity, dialogs, startIndex)
315
+ ```
316
+
317
+ Common symptoms and fixes:
318
+ - Error: "Cannot set properties of undefined (setting 'script')" in bubble.js → Missing `createDialogBubble(npc)` before `talkBubble`.
319
+ - Error in `<NpcUtilsUi>` caused by `getTheme`/`displayDialog` → Missing `npcDataComponent` for that entity; call `addDialog` and ensure a minimal `npcDataComponent.set(...)` exists before `openDialogWindow`.
258
320
 
259
321
  ## NPC Movement & Behavior
260
322
 
261
323
  ### Simple Path Following
262
324
  ```typescript
263
- import * as utils from '@dcl-sdk/utils'
264
-
265
325
  function createPatrollingNPC() {
266
- const patrollingNPC = engine.addEntity()
267
-
268
- AvatarShape.create(patrollingNPC, {
269
- id: 'patrol-npc',
270
- name: 'Guard'
271
- })
272
-
273
- Transform.create(patrollingNPC, {
274
- position: Vector3.create(0, 0.25, 0)
275
- })
326
+ // Create NPC via toolkit (handles walk/idle animations)
327
+ const patrollingNPC = createNPC(
328
+ { position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: 0, z: 0, w: 1 }, scale: { x: 1, y: 1, z: 1 } },
329
+ { type: 'custom', model: 'models/guard.glb' }
330
+ )
276
331
 
277
332
  // Define patrol path
278
333
  const patrolPath = [
@@ -283,18 +338,20 @@ function createPatrollingNPC() {
283
338
  Vector3.create(0, 0.25, 0)
284
339
  ]
285
340
 
286
- // Start patrolling
287
- utils.paths.startSmoothPath(
288
- patrollingNPC,
289
- patrolPath,
290
- 20, // Duration in seconds
291
- 30, // Segments for smoothness
292
- true, // Face movement direction
293
- () => {
294
- // Restart patrol when complete
295
- utils.paths.startSmoothPath(patrollingNPC, patrolPath, 20, 30, true)
296
- }
297
- )
341
+ // Start walking animation via toolkit (handles walk/idle clips)
342
+ // import * as npc from 'dcl-npc-toolkit'
343
+ npc.playAnimation(patrollingNPC, 'Walk', true)
344
+
345
+ // Patrol using Tween helpers; keep looping
346
+ Tween.setMove(patrollingNPC, patrolPath[0], patrolPath[1], 4000, EasingFunction.EF_LINEAR)
347
+ TweenSequence.create(patrollingNPC, {
348
+ sequence: [
349
+ { duration: 4000, easingFunction: EasingFunction.EF_LINEAR, mode: Tween.Mode.Move({ start: patrolPath[1], end: patrolPath[2] }) },
350
+ { duration: 4000, easingFunction: EasingFunction.EF_LINEAR, mode: Tween.Mode.Move({ start: patrolPath[2], end: patrolPath[3] }) },
351
+ { duration: 4000, easingFunction: EasingFunction.EF_LINEAR, mode: Tween.Mode.Move({ start: patrolPath[3], end: patrolPath[4] }) }
352
+ ],
353
+ loop: TweenLoop.TL_RESTART
354
+ })
298
355
 
299
356
  return patrollingNPC
300
357
  }
@@ -303,21 +360,16 @@ function createPatrollingNPC() {
303
360
  ### NPC Following Player
304
361
  ```typescript
305
362
  function createFollowingNPC() {
306
- const followingNPC = engine.addEntity()
307
-
308
- AvatarShape.create(followingNPC, {
309
- id: 'follower-npc',
310
- name: 'Companion'
311
- })
312
-
313
- Transform.create(followingNPC, {
314
- position: Vector3.create(2, 0.25, 2)
315
- })
363
+ // Create NPC via toolkit
364
+ const followingNPC = createNPC(
365
+ { position: { x: 2, y: 0, z: 2 }, rotation: { x: 0, y: 0, z: 0, w: 1 }, scale: { x: 1, y: 1, z: 1 } },
366
+ { type: 'custom', model: 'models/companion.glb' }
367
+ )
316
368
 
317
- // System to make NPC follow player
369
+ // System to make NPC follow player (read player via engine.PlayerEntity)
318
370
  engine.addSystem(() => {
319
- const playerPos = utils.getPlayerPosition()
320
- if (!playerPos) return
371
+ if (!Transform.has(engine.PlayerEntity)) return
372
+ const playerPos = Transform.get(engine.PlayerEntity).position
321
373
 
322
374
  const npcTransform = Transform.getMutable(followingNPC)
323
375
  const currentPos = npcTransform.position
@@ -344,73 +396,38 @@ function createFollowingNPC() {
344
396
 
345
397
  ## NPC State Management
346
398
 
347
- ### NPC State Component
399
+ ### Toolkit-managed NPC state
348
400
  ```typescript
349
- // Define custom component for NPC state
350
- @Component('NPCState')
351
- export class NPCState {
352
- currentDialogue: string = ''
353
- isInteracting: boolean = false
354
- lastInteractionTime: number = 0
355
- interactionCooldown: number = 5000 // 5 seconds
356
- }
401
+ // The toolkit tracks basic interaction state and dialog progression internally.
402
+ // Prefer defining behavior via createNPC options and Dialog[] rather than custom components.
357
403
 
358
- // Create NPC with state
359
- function createStatefulNPC() {
360
- const npc = engine.addEntity()
361
-
362
- AvatarShape.create(npc, {
363
- id: 'stateful-npc',
364
- name: 'Merchant'
365
- })
366
-
367
- Transform.create(npc, {
368
- position: Vector3.create(8, 0.25, 8)
369
- })
370
-
371
- NPCState.create(npc, {
372
- currentDialogue: 'Welcome to my shop!',
373
- isInteracting: false,
374
- lastInteractionTime: 0,
375
- interactionCooldown: 3000
376
- })
377
-
378
- return npc
379
- }
404
+ const merchant = createNPC(
405
+ { position: { x: 8, y: 0, z: 8 }, rotation: { x: 0, y: 0, z: 0, w: 1 }, scale: { x: 1, y: 1, z: 1 } },
406
+ {
407
+ type: 'custom',
408
+ model: 'models/merchant.glb',
409
+ dialog: [
410
+ { text: 'Welcome to my shop!', isEndOfDialog: true } as Dialog
411
+ ],
412
+ onActivate: () => {
413
+ // Optional: extra side effects on interaction
414
+ console.log('Merchant activated')
415
+ }
416
+ }
417
+ )
380
418
  ```
381
419
 
382
- ### NPC Interaction System
420
+ ### Interaction flow
383
421
  ```typescript
384
- function createNPCInteractionSystem() {
385
- engine.addSystem(() => {
386
- for (const [entity, npcState] of engine.getEntitiesWith(NPCState)) {
387
- const currentTime = Date.now()
388
-
389
- // Reset interaction state if cooldown has passed
390
- if (npcState.isInteracting &&
391
- currentTime - npcState.lastInteractionTime > npcState.interactionCooldown) {
392
- NPCState.getMutable(entity).isInteracting = false
393
- }
394
- }
395
- })
396
- }
397
-
398
- // Handle NPC interaction with cooldown
399
- function interactWithNPC(npcEntity: Entity) {
400
- const npcState = NPCState.getMutable(npcEntity)
401
- const currentTime = Date.now()
402
-
403
- if (npcState.isInteracting) {
404
- console.log('NPC is busy, please wait...')
405
- return
422
+ // Clicks are handled via the toolkit's onActivate; use TriggerArea for proximity prompts if needed.
423
+ const helper = createNPC(
424
+ { position: { x: 6, y: 0, z: 6 }, rotation: { x: 0, y: 0, z: 0, w: 1 }, scale: { x: 1, y: 1, z: 1 } },
425
+ {
426
+ type: 'custom', model: 'models/helper.glb',
427
+ dialog: [{ text: 'Need assistance?', isEndOfDialog: true } as Dialog],
428
+ onActivate: () => console.log('Helper clicked')
406
429
  }
407
-
408
- npcState.isInteracting = true
409
- npcState.lastInteractionTime = currentTime
410
-
411
- // Show dialogue
412
- showNPCDialogue(npcEntity)
413
- }
430
+ )
414
431
  ```
415
432
 
416
433
  ## NPC Multiplayer Considerations
@@ -468,60 +485,22 @@ messageBus.on('npc-interaction', (message) => {
468
485
 
469
486
  ### NPC with Multiple Dialogue States
470
487
  ```typescript
471
- enum NPCState {
472
- IDLE = 'idle',
473
- GREETING = 'greeting',
474
- IN_DIALOGUE = 'dialogue',
475
- FAREWELL = 'farewell'
476
- }
477
-
478
488
  function createAdvancedNPC() {
479
- const npc = engine.addEntity()
480
-
481
- AvatarShape.create(npc, {
482
- id: 'advanced-npc',
483
- name: 'Quest Giver'
484
- })
485
-
486
- Transform.create(npc, {
487
- position: Vector3.create(8, 0.25, 8)
488
- })
489
-
490
- // Add click interaction
491
- pointerEventsSystem.onPointerDown(
489
+ // Multi-step dialog with branching via toolkit only
490
+ return createNPC(
491
+ { position: { x: 8, y: 0, z: 8 }, rotation: { x: 0, y: 0, z: 0, w: 1 }, scale: { x: 1, y: 1, z: 1 } },
492
492
  {
493
- entity: npc,
494
- opts: { button: InputAction.IA_POINTER, hoverText: 'Talk to Quest Giver' }
495
- },
496
- () => handleNPCClick(npc)
493
+ type: 'custom', model: 'models/quest_giver.glb',
494
+ dialog: [
495
+ { text: 'Hello traveler! I have a quest for you.', isQuestion: true, buttons: [
496
+ { label: 'Tell me about the quest', goToDialog: 1 },
497
+ { label: 'I\'ll pass for now', goToDialog: 2 }
498
+ ] } as unknown as Dialog,
499
+ { text: 'Retrieve the ancient token from the ruins to the east.', isEndOfDialog: true } as Dialog,
500
+ { text: 'Come back if you change your mind!', isEndOfDialog: true } as Dialog
501
+ ]
502
+ }
497
503
  )
498
-
499
- return npc
500
- }
501
-
502
- function handleNPCClick(npcEntity: Entity) {
503
- const currentState = getNPCState(npcEntity) // Custom function to get state
504
-
505
- switch (currentState) {
506
- case NPCState.IDLE:
507
- setNPCState(npcEntity, NPCState.GREETING)
508
- showDialogue(npcEntity, "Hello traveler! I have a quest for you.")
509
- break
510
- case NPCState.GREETING:
511
- setNPCState(npcEntity, NPCState.IN_DIALOGUE)
512
- showDialogueOptions(npcEntity, [
513
- { text: "Tell me about the quest", action: () => showQuestDetails(npcEntity) },
514
- { text: "I'll pass for now", action: () => setNPCState(npcEntity, NPCState.FAREWELL) }
515
- ])
516
- break
517
- case NPCState.IN_DIALOGUE:
518
- // Already in dialogue, do nothing
519
- break
520
- case NPCState.FAREWELL:
521
- setNPCState(npcEntity, NPCState.IDLE)
522
- showDialogue(npcEntity, "Come back if you change your mind!")
523
- break
524
- }
525
504
  }
526
505
  ```
527
506
 
@@ -673,46 +652,6 @@ const npc1 = npcPool.createNPC(Vector3.create(4, 0.25, 4))
673
652
  const npc2 = npcPool.createNPC(Vector3.create(8, 0.25, 8))
674
653
  ```
675
654
 
676
- ### NPC LOD (Level of Detail)
677
- ```typescript
678
- function createLODNPC() {
679
- const npc = engine.addEntity()
680
-
681
- AvatarShape.create(npc, {
682
- id: 'lod-npc',
683
- name: 'LOD NPC'
684
- })
685
-
686
- Transform.create(npc, {
687
- position: Vector3.create(8, 0.25, 8)
688
- })
689
-
690
- // System to adjust NPC detail based on distance
691
- engine.addSystem(() => {
692
- const playerPos = utils.getPlayerPosition()
693
- if (!playerPos) return
694
-
695
- const npcPos = Transform.get(npc).position
696
- const distance = Vector3.distance(playerPos, npcPos)
697
-
698
- const avatarShape = AvatarShape.getMutable(npc)
699
-
700
- if (distance > 20) {
701
- // Far distance: hide name and disable interactions
702
- avatarShape.name = ''
703
- } else if (distance > 10) {
704
- // Medium distance: show name, no interactions
705
- avatarShape.name = 'LOD NPC'
706
- } else {
707
- // Close distance: full functionality
708
- avatarShape.name = 'LOD NPC (Click to interact)'
709
- }
710
- })
711
-
712
- return npc
713
- }
714
- ```
715
-
716
655
  ## Best Practices
717
656
 
718
657
  ### NPC Design Guidelines
@@ -500,20 +500,14 @@ Material.setPbrMaterial(entity, {
500
500
  #### Texture Animation
501
501
  ```typescript
502
502
  // Animate texture offset
503
- Tween.create(entity, {
504
- mode: Tween.Mode.TextureMove({
505
- start: Vector2.create(0, 0),
506
- end: Vector2.create(1, 0)
507
- }),
508
- duration: 2000,
509
- easingFunction: EasingFunction.EF_LINEAR
510
- })
503
+ Tween.setTextureMove(entity,
504
+ Vector2.create(0, 0),
505
+ Vector2.create(1, 0),
506
+ 2000
507
+ )
511
508
 
512
509
  // Loop texture animation
513
- TweenSequence.create(entity, {
514
- sequence: [],
515
- loop: TweenLoop.TL_RESTART
516
- })
510
+ TweenSequence.create(entity, { sequence: [], loop: TweenLoop.TL_RESTART })
517
511
  ```
518
512
 
519
513
  #### Transparency
@@ -561,6 +555,39 @@ Tip: set `path` to a specific mesh node to target only that part; use `Material.
561
555
 
562
556
  ### Move Entities
563
557
 
558
+ #### Tween helpers (concise syntax)
559
+ ```typescript
560
+ // Move between two points
561
+ Tween.setMove(entity,
562
+ Vector3.create(4, 1, 4),
563
+ Vector3.create(8, 1, 8),
564
+ 2000,
565
+ { faceDirection: false, easingFunction: EasingFunction.EF_LINEAR }
566
+ )
567
+
568
+ // Rotate between two rotations
569
+ Tween.setRotate(entity,
570
+ Quaternion.fromEulerDegrees(0, 0, 0),
571
+ Quaternion.fromEulerDegrees(0, 180, 0),
572
+ 700,
573
+ EasingFunction.EF_EASEOUTBOUNCE
574
+ )
575
+
576
+ // Scale between sizes
577
+ Tween.setScale(entity,
578
+ Vector3.create(1, 1, 1),
579
+ Vector3.create(4, 4, 4),
580
+ 2000,
581
+ EasingFunction.EF_LINEAR
582
+ )
583
+
584
+ // Continuous movement (meters/second)
585
+ Tween.setMoveContinuous(entity, Vector3.create(0, 0, 1), 0.7)
586
+
587
+ // Continuous rotation (degrees/second)
588
+ Tween.setRotateContinuous(entity, Quaternion.fromEulerDegrees(0, -1, 0), 700)
589
+ ```
590
+
564
591
  #### Tween System
565
592
  ```typescript
566
593
  // Move between points