dcl-npc-toolkit 1.0.6-20230515144908.commit-f43b9c1 β 1.0.7-20230516200558.commit-a07eb6f
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 +901 -255
- package/dist/bubble.d.ts +10 -0
- package/dist/bubble.js +367 -0
- package/dist/components.d.ts +1 -0
- package/dist/components.js +2 -1
- package/dist/dialog.d.ts +1 -0
- package/dist/dialog.js +4 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -3
- package/dist/npc.d.ts +2 -1
- package/dist/npc.js +20 -10
- package/dist/systems.d.ts +1 -0
- package/dist/systems.js +34 -3
- package/dist/ui.js +59 -58
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,622 +1,1268 @@
|
|
|
1
1
|
|
|
2
|
+
|
|
3
|
+
|
|
2
4
|
# NPC-library
|
|
3
5
|
|
|
6
|
+
|
|
7
|
+
|
|
4
8
|
A collection of tools for creating Non-Player-Characters (NPCs). These are capable of having conversations with the player, and play different animations.
|
|
5
9
|
|
|
10
|
+
|
|
11
|
+
|
|
6
12
|
Capabilities of the NPCs in this library:
|
|
7
13
|
|
|
14
|
+
|
|
15
|
+
|
|
8
16
|
- Start a conversation when clicked or when walking near
|
|
17
|
+
|
|
9
18
|
- Trigger any action when clicked or when walking near
|
|
19
|
+
|
|
10
20
|
- Trigger any action when the player walks away
|
|
21
|
+
|
|
11
22
|
- Turn around slowly to always face the player
|
|
23
|
+
|
|
12
24
|
- Play an animation in the NPC 3d model, optionally returning to loop the idle animation afterwards
|
|
13
25
|
|
|
26
|
+
|
|
27
|
+
|
|
14
28
|
The dialog messages can also require that the player chooses options, and any action can be triggered when the player picks an option or advances past a message.
|
|
29
|
+
|
|
15
30
|
## Using the UI utils library
|
|
16
31
|
|
|
32
|
+
|
|
33
|
+
|
|
17
34
|
To use any of the helpers provided by the utils library you must install it in your Decentrland project.
|
|
18
35
|
|
|
36
|
+
|
|
37
|
+
|
|
19
38
|
### Via the Decentraland Editor
|
|
20
39
|
|
|
40
|
+
|
|
41
|
+
|
|
21
42
|
Make sure you've [installed the Decentraland editor](https://docs.decentraland.org/creator/development-guide/sdk7/installation-guide/#the-decentraland-editor).
|
|
22
43
|
|
|
23
|
-
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
1) Open your scene's folder using Visual Studio Code.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
> **π Note**: The Visual Studio window must be at the root folder of the scene project.
|
|
24
51
|
|
|
25
|
-
> **π Note**: The Visual Studio window must be at the root folder of the scene project.
|
|
26
52
|
2) Open the Decentraland Editor tab on Visual Studio. Note that the bottom section lists all of your project's currently installed dependencies.
|
|
27
53
|
|
|
54
|
+
|
|
55
|
+
|
|
28
56
|
3) Click the `+` icon on the header of the **Dependencies** view.
|
|
29
57
|
|
|
58
|
+
|
|
59
|
+
|
|
30
60
|
4) Visual Studio opens an input box at the top of the screen. Write `@dcl-sdk/npc-utils` and hit enter. The dependency is then installed to your scene.
|
|
31
61
|
|
|
62
|
+
|
|
63
|
+
|
|
32
64
|
|
|
33
65
|
5. Import the library into the scene's script. Add this line at the start of your `index.ts` file, or any other TypeScript files that require it:
|
|
34
66
|
|
|
67
|
+
|
|
68
|
+
|
|
35
69
|
```ts
|
|
36
|
-
|
|
70
|
+
|
|
71
|
+
import * as npc from '@dcl-sdk/npc-utils'
|
|
72
|
+
|
|
37
73
|
```
|
|
38
74
|
|
|
75
|
+
|
|
76
|
+
|
|
39
77
|
6. In your TypeScript file, call the `create` function passing it a `TransformType` and a `NPCData` object. The `NPCData` object requires a minimum of a `NPCType` and a function to trigger when the NPC is activated:
|
|
40
78
|
|
|
79
|
+
|
|
80
|
+
|
|
41
81
|
```ts
|
|
42
|
-
|
|
82
|
+
|
|
83
|
+
export let myNPC = npc.create({position: Vector3.create(8,0,8),rotation:Quaternion.Zero(), scale: Vector3.create(1,1,1)},
|
|
84
|
+
|
|
43
85
|
//NPC Data Object
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
86
|
+
|
|
87
|
+
{
|
|
88
|
+
|
|
89
|
+
type: npc.NPCType.CUSTOM,
|
|
90
|
+
|
|
91
|
+
model: 'models/npc.glb',
|
|
92
|
+
|
|
93
|
+
onActivate:()=>{console.log('npc activated');}
|
|
94
|
+
|
|
48
95
|
}
|
|
96
|
+
|
|
49
97
|
)
|
|
98
|
+
|
|
50
99
|
```
|
|
51
100
|
|
|
101
|
+
|
|
102
|
+
|
|
52
103
|
7. Write a dialog script for your character, preferably on a separate file, making it of type `Dialog[]`.
|
|
53
104
|
|
|
105
|
+
|
|
106
|
+
|
|
54
107
|
```ts
|
|
55
|
-
import { Dialog } from '@dcl-sdk/npc-utils'
|
|
56
108
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
109
|
+
import { Dialog } from '@dcl-sdk/npc-utils'
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
export let ILoveCats: Dialog[] = [
|
|
114
|
+
|
|
115
|
+
{
|
|
116
|
+
|
|
117
|
+
text: `I really lo-ove cats`,
|
|
118
|
+
|
|
119
|
+
isEndOfDialog: true
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
62
123
|
]
|
|
124
|
+
|
|
63
125
|
```
|
|
64
126
|
|
|
127
|
+
|
|
128
|
+
|
|
65
129
|
|
|
66
130
|
### Via the CLI
|
|
67
131
|
|
|
132
|
+
|
|
133
|
+
|
|
68
134
|
1. Install the library as an npm bundle. Run this command in your scene's project folder:
|
|
69
135
|
|
|
136
|
+
|
|
137
|
+
|
|
70
138
|
```
|
|
139
|
+
|
|
71
140
|
npm i @dcl-sdk/npc-utils
|
|
141
|
+
|
|
72
142
|
```
|
|
73
143
|
|
|
144
|
+
|
|
145
|
+
|
|
74
146
|
2. Run `npm run start` or `npm run build` so the dependencies are correctly installed.
|
|
75
147
|
|
|
148
|
+
|
|
149
|
+
|
|
76
150
|
3. Import the library into the scene's script. Add this line at the start of your `index.ts` file, or any other TypeScript files that require it:
|
|
77
151
|
|
|
152
|
+
|
|
153
|
+
|
|
78
154
|
```ts
|
|
79
|
-
|
|
155
|
+
|
|
156
|
+
import * as npc from '@dcl-sdk/npc-utils'
|
|
157
|
+
|
|
80
158
|
```
|
|
81
159
|
|
|
160
|
+
|
|
161
|
+
|
|
82
162
|
4. In your TypeScript file, call the `create` function passing it a `TransformType` and a `NPCData` object. The `NPCData` object requires a minimum of a `NPCType` and a function to trigger when the NPC is activated:
|
|
83
163
|
|
|
164
|
+
|
|
165
|
+
|
|
84
166
|
```ts
|
|
85
|
-
|
|
167
|
+
|
|
168
|
+
export let myNPC = npc.create({position: Vector3.create(8,0,8),rotation:Quaternion.Zero(), scale: Vector3.create(1,1,1)},
|
|
169
|
+
|
|
86
170
|
//NPC Data Object
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
171
|
+
|
|
172
|
+
{
|
|
173
|
+
|
|
174
|
+
type: npc.NPCType.CUSTOM,
|
|
175
|
+
|
|
176
|
+
model: 'models/npc.glb',
|
|
177
|
+
|
|
178
|
+
onActivate:()=>{console.log('npc activated');}
|
|
179
|
+
|
|
91
180
|
}
|
|
181
|
+
|
|
92
182
|
)
|
|
183
|
+
|
|
93
184
|
```
|
|
94
185
|
|
|
186
|
+
|
|
187
|
+
|
|
95
188
|
5. Write a dialog script for your character, preferably on a separate file, making it of type `Dialog[]`.
|
|
96
189
|
|
|
190
|
+
|
|
191
|
+
|
|
97
192
|
```ts
|
|
98
|
-
import { Dialog } from '@dcl-sdk/npc-utils'
|
|
99
193
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
194
|
+
import { Dialog } from '@dcl-sdk/npc-utils'
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
export let ILoveCats: Dialog[] = [
|
|
199
|
+
|
|
200
|
+
{
|
|
201
|
+
|
|
202
|
+
text: `I really lo-ove cats`,
|
|
203
|
+
|
|
204
|
+
isEndOfDialog: true
|
|
205
|
+
|
|
206
|
+
}
|
|
207
|
+
|
|
105
208
|
]
|
|
209
|
+
|
|
106
210
|
```
|
|
107
211
|
|
|
212
|
+
|
|
213
|
+
|
|
108
214
|
## NPC Default Behavior
|
|
109
215
|
|
|
216
|
+
|
|
217
|
+
|
|
110
218
|
NPCs at the very least must have:
|
|
111
219
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
- `position`: (_TransformType_) Must include position, rotation and scale.
|
|
223
|
+
|
|
224
|
+
- `NPCData`: (_Data Object_) with a minimum of two variables
|
|
225
|
+
|
|
226
|
+
- `type`: (_NPCType_) you have the choice to use a custom GLB object or an `AvatarShape` for your npc
|
|
227
|
+
|
|
228
|
+
- `NPCType.CUSTOM`
|
|
229
|
+
|
|
230
|
+
- `NPCType.AVATAR`
|
|
231
|
+
|
|
232
|
+
- `onActivate()`: (_()=> void_) A function to call when the NPC is activated.
|
|
233
|
+
|
|
234
|
+
|
|
118
235
|
|
|
119
236
|
*if you decide to use a `NPCType.CUSTOM` GLB model for your avatar, you must pass in a model object inside the `NPCData`*
|
|
120
|
-
|
|
237
|
+
|
|
238
|
+
- `model`: (_string_) The path to a 3D model
|
|
239
|
+
|
|
240
|
+
|
|
121
241
|
|
|
122
242
|
```ts
|
|
123
|
-
|
|
243
|
+
|
|
244
|
+
export let myNPC = npc.create({position: Vector3.create(8,0,8),rotation:Quaternion.Zero(), scale: Vector3.create(1,1,1)},
|
|
245
|
+
|
|
124
246
|
//NPC Data Object
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
247
|
+
|
|
248
|
+
{
|
|
249
|
+
|
|
250
|
+
type: npc.NPCType.CUSTOM,
|
|
251
|
+
|
|
252
|
+
model: 'models/npc.glb',
|
|
253
|
+
|
|
254
|
+
onActivate:()=>{console.log('npc activated');}
|
|
255
|
+
|
|
129
256
|
}
|
|
257
|
+
|
|
130
258
|
)
|
|
259
|
+
|
|
131
260
|
```
|
|
132
261
|
|
|
262
|
+
|
|
263
|
+
|
|
133
264
|
With this default configuration, the NPC behaves in the following way:
|
|
134
265
|
|
|
266
|
+
|
|
267
|
+
|
|
135
268
|
- The `onActivate()` function is called when pressing E on the NPC, and when the player walks near at a distance of 6 meters.
|
|
269
|
+
|
|
136
270
|
- Once activated, there's a cooldown period of 5 seconds, that prevents the NPC to be activated again.
|
|
271
|
+
|
|
137
272
|
- After walking away from the NPC, if its dialog window was open it will be closed, and if the NPC was rotating to follow the player it will stop.
|
|
273
|
+
|
|
138
274
|
- If the NPC already has an open dialog window, clicking on the NPC won't do anything, to prevent accidentally clicking on it while flipping through the conversation.
|
|
275
|
+
|
|
139
276
|
- If the NPC has an animation named 'Idle', it will play it in a loop. If other non-looping animations are played, it will return to looping the 'Idle' animation after the indicated duration.
|
|
140
277
|
|
|
278
|
+
|
|
279
|
+
|
|
141
280
|
Many of these behaviors can be overridden or tweaked with the exposed properties.
|
|
142
281
|
|
|
282
|
+
|
|
283
|
+
|
|
143
284
|
## NPC Additional Properties
|
|
144
285
|
|
|
286
|
+
|
|
287
|
+
|
|
145
288
|
To configure other properties of an NPC, add a fourth argument as an `NPCData` object. This object can have the following optional properties:
|
|
146
289
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
|
|
151
|
-
-
|
|
152
|
-
|
|
153
|
-
-
|
|
154
|
-
|
|
155
|
-
-
|
|
156
|
-
|
|
157
|
-
-
|
|
158
|
-
|
|
159
|
-
-
|
|
160
|
-
|
|
161
|
-
-
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
- `idleAnim`: _(string)_ Name of the idle animation in the model. This animation is always looped. After playing a non-looping animation it returns to looping this one.
|
|
293
|
+
|
|
294
|
+
- `faceUser`: _(boolean)_ Set if the NPC rotates to face the user while active.
|
|
295
|
+
|
|
296
|
+
- `dialogSound`: _(string)_ Path to sound file to play once for every entry shown on the UI. If the dialog entry being shown has an `audio` field, the NPC will play the file referenced by the `audio` field instead.
|
|
297
|
+
|
|
298
|
+
- `coolDownDuration`: _(number)_ Change the cooldown period for activating the NPC again. The number is in seconds.
|
|
299
|
+
|
|
300
|
+
- `hoverText`: _(string)_ Set the UI hover feedback when pointing the cursor at the NPC. _TALK_ by default.
|
|
301
|
+
|
|
302
|
+
- `onlyClickTrigger`: _(boolean)_ If true, the NPC can't be activated by walking near. Just by clicking on it or calling its `activate()` function.
|
|
303
|
+
|
|
304
|
+
- `onlyETrigger`: _(boolean)_ If true, the NPC can't be activated by walking near. Just by pressing the E key on it or calling its `activate()` function.
|
|
305
|
+
|
|
306
|
+
- `onlyExternalTrigger`: _(boolean)_ If true, the NPC can't be activated by clicking, pressing E, or walking near. Just by calling its `activate()` function.
|
|
307
|
+
|
|
308
|
+
- `reactDistance`: _(number)_ Radius in meters for the player to activate the NPC or trigger the `onWalkAway()` function when leaving the radius.
|
|
309
|
+
|
|
310
|
+
- `continueOnWalkAway`: _(boolean)_ If true,when the player walks out of the `reactDistance` radius, the dialog window stays open and the NPC keeps turning to face the player (if applicable). It doesn't affect the triggering of the `onWalkAway()` function.
|
|
311
|
+
|
|
312
|
+
- `onWalkAway`: (_()=> void_) Function to call every time the player walks out of the `reactDistance` radius.
|
|
313
|
+
|
|
314
|
+
- `walkingAnim`: _(string)_ Name of the walking animation on the model. This animation is looped when calling the `followPath()` function.
|
|
315
|
+
|
|
316
|
+
- `walkingSpeed`: _(number)_ Speed of the NPC when walking. By default _2_.
|
|
317
|
+
|
|
318
|
+
- `path`: _(Vector3)_ Default path to walk. If a value is provided for this field on NPC initialization, the NPC will walk over this path in loop from the start.
|
|
319
|
+
- `bubbleHeight`: _(number)_ The height at which to display the speech bubble above the head of the NPC.
|
|
320
|
+
- `textBubble`: _(boolean)_ If true, NPC starts with a speech bubble object ready to be accessed from the start. Otherwise, they text bubble is only built on the first call to `talkBubble()` on the NPC.
|
|
321
|
+
|
|
322
|
+
- `noUI`: _(boolean)_ If true, no UI object is built for UI dialogs for this NPC. This may help optimize the scene if this feature is not used.
|
|
323
|
+
|
|
324
|
+
|
|
162
325
|
|
|
163
326
|
```ts
|
|
164
|
-
|
|
327
|
+
|
|
328
|
+
export let myNPC = npc.create({position: Vector3.create(8,0,8),rotation:Quaternion.Zero(), scale: Vector3.create(1,1,1)},
|
|
329
|
+
|
|
165
330
|
//NPC Data Object
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
331
|
+
|
|
332
|
+
{
|
|
333
|
+
|
|
334
|
+
type: npc.NPCType.CUSTOM,
|
|
335
|
+
|
|
336
|
+
model: 'models/npc.glb',
|
|
337
|
+
|
|
338
|
+
onActivate: ()=>{console.log('npc activated');},
|
|
339
|
+
|
|
340
|
+
onWalkAway: ()=>{console.log('test on walk away function')},
|
|
341
|
+
|
|
342
|
+
faceUser: true,
|
|
343
|
+
|
|
344
|
+
reactDistance: 3,
|
|
345
|
+
|
|
346
|
+
idleAnim: 'idle1',
|
|
347
|
+
|
|
348
|
+
walkingAnim: 'walk1',
|
|
349
|
+
|
|
350
|
+
hoverText: 'Activate',
|
|
351
|
+
|
|
352
|
+
continueOnWalkAway: true,
|
|
353
|
+
|
|
354
|
+
onlyClickTrigger: false,
|
|
355
|
+
|
|
356
|
+
onlyExternalTrigger: false
|
|
357
|
+
|
|
179
358
|
}
|
|
359
|
+
|
|
180
360
|
)
|
|
361
|
+
|
|
181
362
|
```
|
|
182
363
|
|
|
364
|
+
|
|
365
|
+
|
|
183
366
|
## Get NPC Data
|
|
184
367
|
|
|
368
|
+
|
|
369
|
+
|
|
185
370
|
```ts
|
|
371
|
+
|
|
186
372
|
npc.getData(myNPC)
|
|
373
|
+
|
|
187
374
|
```
|
|
188
375
|
|
|
376
|
+
|
|
377
|
+
|
|
189
378
|
There are several properties you can check on an NPC to know what its current state is:
|
|
190
379
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
-
|
|
194
|
-
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
- `.state`: An enum value of type `NPCState`. Supported values are `NPCState.STANDING` (default), `NPCState.TALKING`, and `NPCState.FOLLOWPATH`. `TALKING` is applied when the dialog window is opened, and set back to `STANDING` when the window is closed. `FOLLOWPATH` is applied when the NPC starts walking, and set back to `STANDING` when the NPC finishes its path or is stopped.
|
|
383
|
+
|
|
384
|
+
- `.introduced`: Boolean, false by default. Set to true if the NPC has spoken to the player at least once in this session.
|
|
385
|
+
|
|
386
|
+
- `.visible`: Returns a Boolean, false by default. True if the dialog window for this NPC is currently open.
|
|
387
|
+
|
|
388
|
+
- `.inCooldown`: Boolean, false by default. True if the NPC was recently activated and it's now in cooldown. The NPC won't respond to being activated till `inCooldown` is false.
|
|
389
|
+
|
|
390
|
+
|
|
195
391
|
|
|
196
392
|
> TIP: If you want to force an activation of the NPC in spite of the `inCooldown` value, you can force this value to true before activating.
|
|
197
393
|
|
|
394
|
+
|
|
395
|
+
|
|
198
396
|
## NPC Callable Actions
|
|
199
397
|
|
|
398
|
+
|
|
399
|
+
|
|
200
400
|
An NPC object has several callable functions that come with the class:
|
|
201
401
|
|
|
402
|
+
|
|
403
|
+
|
|
202
404
|
### Talk
|
|
203
405
|
|
|
406
|
+
|
|
407
|
+
|
|
204
408
|
To start a conversation with the NPC using the dialog UI, call the `talk()` function. The function takes the following **required** parameter:
|
|
205
409
|
|
|
206
|
-
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
- `script`: _(Dialog[])_ This array contains the information to manage the conversation, including events that may be triggered, options to choose, etc.
|
|
413
|
+
|
|
414
|
+
|
|
207
415
|
|
|
208
416
|
It can also take the following optional parameters:
|
|
209
417
|
|
|
210
|
-
|
|
211
|
-
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
- `startIndex`: _(number | string)_ The _Dialog_ object from the `script` array to open first. By default this is _0_, the first element of the array. Pass a number to open the entry on a given array position, or pass a string to open the entry with a `name` property matching that string.
|
|
421
|
+
|
|
422
|
+
- `duration`: _(number)_ Number of seconds to wait before closing the dialog window. If no value is set, the window is kept open till the player reaches the end of the conversation or something else closes it.
|
|
423
|
+
|
|
424
|
+
|
|
212
425
|
|
|
213
426
|
```ts
|
|
427
|
+
|
|
214
428
|
npc.talk(myNPC,myScript, 0)
|
|
429
|
+
|
|
215
430
|
```
|
|
216
431
|
|
|
432
|
+
|
|
433
|
+
|
|
217
434
|
Learn how to build a script object for NPCs in a section below.
|
|
218
435
|
|
|
436
|
+
### Speech Bubbles
|
|
437
|
+
|
|
438
|
+
Besides the UI dialog window, NPCs can show speech bubbles over their heads. This alternative is less invasive to the player, but also non-interactive. Players can't alter the pace of the conversation or provide answers to questions.
|
|
439
|
+
|
|
440
|
+
For an NPC to talk with bubbles:
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
npc.talkBubble(myNPC, myScript, 0)
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
The function takes the following **required** parameter:
|
|
447
|
+
- `npc`: _(Entity)_ The NPC you would like to talk.
|
|
448
|
+
|
|
449
|
+
- `script`: _(Dialog[])_ This array contains the information to manage the conversation, including events that may be triggered, options to choose, etc.
|
|
450
|
+
|
|
451
|
+
It can also take the following optional parameters:
|
|
452
|
+
|
|
453
|
+
- `startIndex`: _(number | string)_ The _Dialog_ object from the `script` array to open first. By default this is _0_, the first element of the array. Pass a number to open the entry on a given array position, or pass a string to open the entry with a `name` property matching that string.
|
|
454
|
+
|
|
455
|
+
To interrupt the flow of an NPC's dialog windows, you can either:
|
|
456
|
+
|
|
457
|
+
- Run `npc.endInteraction(myNPC)` on the NPC
|
|
458
|
+
- Run `npc.closeBubble(myNPC)` on the NPC's `bubble` object
|
|
459
|
+
- Run `npc.closeBubbleEndAll(myNPC)` on the NPC's `bubble` object
|
|
460
|
+
|
|
461
|
+
The first two options keep running any `triggeredByNext()` functions associated to the dialogs being shown on the bubble, the third option prevents running these.
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
|
|
219
465
|
### Play Animations
|
|
220
466
|
|
|
467
|
+
|
|
468
|
+
|
|
221
469
|
By default, the NPC will loop an animation named 'Idle', or with a name passed in the `idleAnim` parameter.
|
|
222
470
|
|
|
471
|
+
|
|
472
|
+
|
|
223
473
|
Make the NPC play another animation by calling the `playAnimation()` function. The function takes the following **required** parameter:
|
|
224
474
|
|
|
225
|
-
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
- `animationName`: _(string)_ The name of the animation to play.
|
|
478
|
+
|
|
479
|
+
|
|
226
480
|
|
|
227
481
|
It can also take the following optional parameters:
|
|
228
482
|
|
|
229
|
-
|
|
230
|
-
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
- `noLoop`: _(boolean)_ If true, plays the animation just once. Otherwise, the animation is looped.
|
|
486
|
+
|
|
487
|
+
- `duration`: _(number)_ Specifies the duration in seconds of the animation. When finished, it returns to playing the idle animation.
|
|
488
|
+
|
|
489
|
+
|
|
231
490
|
|
|
232
491
|
> Note: If `noLoop` is true but no `duration` is set, the model will stay still after playing the animation instead of returning to the idle animation.
|
|
233
492
|
|
|
493
|
+
|
|
494
|
+
|
|
234
495
|
```ts
|
|
496
|
+
|
|
235
497
|
npc.playAnimation(myNPC, `Head_Yes`, true, 2.63)
|
|
498
|
+
|
|
236
499
|
```
|
|
237
500
|
|
|
501
|
+
|
|
502
|
+
|
|
238
503
|
### Change idle animation
|
|
239
504
|
|
|
505
|
+
|
|
506
|
+
|
|
240
507
|
The NPC's idle animation is looped by default whenever the NPC is not playing any other animations. In some cases you may want to have different idle animations depending on the circumstances, like while in a conversation, or if the NPC changes its general attitude after some event.
|
|
241
508
|
|
|
509
|
+
|
|
510
|
+
|
|
242
511
|
You set the NPC's idle animation when creating the NPC, using the `idleAnim` field. To change this animation at some later time, use `changeIdleAnim()`.
|
|
243
512
|
|
|
513
|
+
|
|
514
|
+
|
|
244
515
|
The `changeIdleAnim()` function takes two arguments:
|
|
245
516
|
|
|
246
|
-
|
|
247
|
-
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
- `animation`: The name of the new animation to set as the idle animation
|
|
520
|
+
|
|
521
|
+
- `play`: Optionally pass this value as _true_ if you want this new animation to start playing right away.
|
|
522
|
+
|
|
523
|
+
|
|
248
524
|
|
|
249
525
|
```ts
|
|
526
|
+
|
|
250
527
|
npc.changeIdleAnim(myNPC,`AngryIdle`, true)
|
|
528
|
+
|
|
251
529
|
```
|
|
252
530
|
|
|
531
|
+
|
|
532
|
+
|
|
253
533
|
### Activate
|
|
254
534
|
|
|
535
|
+
|
|
536
|
+
|
|
255
537
|
The `activate()` function can be used to trigger the `onActivate()` function, as an alternative to pressing E or walking near.
|
|
256
538
|
|
|
539
|
+
|
|
540
|
+
|
|
257
541
|
```ts
|
|
542
|
+
|
|
258
543
|
npc.activate(myNPC)
|
|
544
|
+
|
|
259
545
|
```
|
|
260
546
|
|
|
547
|
+
|
|
548
|
+
|
|
261
549
|
The `activate()` function is callable even when in cool down period, and it doesn't start a new cool down period.
|
|
262
550
|
|
|
551
|
+
|
|
552
|
+
|
|
263
553
|
### Stop Walking
|
|
264
554
|
|
|
555
|
+
|
|
556
|
+
|
|
265
557
|
If the NPC is currently walking, call `stopWalking()` to stop it moving and return to playing its idle animation.
|
|
266
558
|
|
|
559
|
+
|
|
560
|
+
|
|
267
561
|
```ts
|
|
562
|
+
|
|
268
563
|
npc.stopWalking(myNPC)
|
|
564
|
+
|
|
269
565
|
```
|
|
270
566
|
|
|
567
|
+
|
|
568
|
+
|
|
271
569
|
`stopWalking()` can be called with no parameters, or it can also be called with:
|
|
272
570
|
|
|
273
|
-
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
- `duration`: Seconds to wait before starting to walk again. If not provided, the NPC will stop walking indefinitely.
|
|
574
|
+
|
|
575
|
+
|
|
274
576
|
|
|
275
577
|
> Note: If the NPC is has its dialog window open when the timer for the `duration` ends, the NPC will not return to walking.
|
|
276
578
|
|
|
579
|
+
|
|
580
|
+
|
|
277
581
|
To make the NPC play a different animation from idle when paused, call `playAnimation()` after `stopWalking()`.
|
|
278
582
|
|
|
583
|
+
|
|
584
|
+
|
|
279
585
|
### Follow Path
|
|
280
586
|
|
|
587
|
+
|
|
588
|
+
|
|
281
589
|
Make an NPC walk following a path of `Vector3` points by calling `followPath()`. While walking, the NPC will play the `walkingAnim` if one was set when defining the NPC. The path can be taken once or on a loop.
|
|
282
590
|
|
|
591
|
+
|
|
592
|
+
|
|
283
593
|
`followPath()` can be called with no parameters if a `path` was already provided in the NPC's initialization or in a previous calling of `followPath()`. If the NPC was previously in the middle of walking a path and was interrupted, calling `followPath()` again with no arguments will return the NPC to that path.
|
|
284
594
|
|
|
595
|
+
|
|
596
|
+
|
|
285
597
|
```ts
|
|
598
|
+
|
|
286
599
|
npc.followPath(myNPC)
|
|
600
|
+
|
|
287
601
|
```
|
|
288
602
|
|
|
603
|
+
|
|
604
|
+
|
|
289
605
|
> Note: If the NPC is initialized with a `path` value, it will start out walking that path in a loop, no need to run `followPath()`.
|
|
290
606
|
|
|
607
|
+
|
|
608
|
+
|
|
291
609
|
`followPath()` has a single optional parameter of type `FollowPathData`. This object may have the following optional fields:
|
|
292
610
|
|
|
611
|
+
|
|
612
|
+
|
|
293
613
|
- path: Array of `Vector3` positions to walk over.
|
|
614
|
+
|
|
294
615
|
- speed: Speed to move at while walking this path. If no `speed` or `totalDuration` is provided, it uses the NPC's `walkingSpeed`, which is _2_ by default.
|
|
616
|
+
|
|
295
617
|
- totalDuration: The duration in _seconds_ that the whole path should take. The NPC will move at the constant speed required to finish in that time. This value overrides that of the _speed_.
|
|
618
|
+
|
|
296
619
|
- loop: _boolean_ If true, the NPC walks in circles over the provided set of points in the path. _false_ by default, unless the NPC is initiated with a `path`, in which case it starts as _true_.
|
|
620
|
+
|
|
297
621
|
- curve: _boolean_ If true, the path is traced a single smooth curve that passes over each of the indicated points. The curve is made out of straight-line segments, the path is stored with 4 times as many points as originally defined. _false_ by default.
|
|
622
|
+
|
|
298
623
|
- startingPoint: Index position for what point to start from on the path. _0_ by default.
|
|
624
|
+
|
|
299
625
|
- onFinishCallback: Function to call when the NPC finished walking over all the points on the path. This is only called when `loop` is _false_.
|
|
626
|
+
|
|
300
627
|
- onReachedPointCallback: Function to call once every time the NPC reaches a point in the path.
|
|
301
628
|
|
|
629
|
+
|
|
630
|
+
|
|
302
631
|
```ts
|
|
303
|
-
|
|
632
|
+
|
|
633
|
+
export let myNPC = npc.create({position: Vector3.create(8,0,8),rotation:Quaternion.Zero(), scale: Vector3.create(1,1,1)},
|
|
634
|
+
|
|
304
635
|
//NPC Data Object
|
|
305
|
-
{
|
|
306
|
-
type: npc.NPCType.CUSTOM,
|
|
307
|
-
model: 'models/npc.glb',
|
|
308
|
-
onActivate: ()=>{console.log('npc activated');},
|
|
309
|
-
onWalkAway: ()=>{console.log('test on walk away function')},
|
|
310
|
-
faceUser: true,
|
|
311
|
-
reactDistance: 3,
|
|
312
|
-
idleAnim: 'idle1',
|
|
313
|
-
walkingAnim: 'walk1',
|
|
314
|
-
hoverText: "Activate"
|
|
315
|
-
}
|
|
316
|
-
)
|
|
317
636
|
|
|
318
|
-
npc.followPath(myNPC,
|
|
319
637
|
{
|
|
320
|
-
path:path,
|
|
321
|
-
loop:true,
|
|
322
|
-
pathType: npc.NPCPathType.RIGID_PATH,
|
|
323
|
-
onFinishCallback:()=>{console.log('path is done')},
|
|
324
|
-
onReachedPointCallback:()=>{console.log('ending oint')},
|
|
325
|
-
totalDuration: 20
|
|
326
|
-
}
|
|
327
|
-
)
|
|
328
638
|
|
|
329
|
-
|
|
639
|
+
type: npc.NPCType.CUSTOM,
|
|
330
640
|
|
|
331
|
-
|
|
641
|
+
model: 'models/npc.glb',
|
|
332
642
|
|
|
333
|
-
|
|
643
|
+
onActivate: ()=>{console.log('npc activated');},
|
|
334
644
|
|
|
335
|
-
|
|
336
|
-
- `speed` parameter set when calling `followPath()`
|
|
337
|
-
- `walkingSpeed` parameter set when initializing NPC
|
|
338
|
-
- Default value _2_.
|
|
645
|
+
onWalkAway: ()=>{console.log('test on walk away function')},
|
|
339
646
|
|
|
340
|
-
|
|
647
|
+
faceUser: true,
|
|
341
648
|
|
|
342
|
-
|
|
649
|
+
reactDistance: 3,
|
|
343
650
|
|
|
344
|
-
|
|
651
|
+
idleAnim: 'idle1',
|
|
345
652
|
|
|
346
|
-
|
|
653
|
+
walkingAnim: 'walk1',
|
|
347
654
|
|
|
348
|
-
|
|
655
|
+
hoverText: "Activate"
|
|
349
656
|
|
|
350
|
-
```ts
|
|
351
|
-
export let myNPC = npc.create({position: Vector3.create(10,0,10),rotation:Quaternion.Zero(), scale: Vector3.create(1,1,1)},
|
|
352
|
-
//NPC Data Object
|
|
353
|
-
{
|
|
354
|
-
type: npc.NPCType.CUSTOM,
|
|
355
|
-
model: 'models/npc.glb',
|
|
356
|
-
onActivate: ()=>{console.log('npc activated');},
|
|
357
657
|
}
|
|
658
|
+
|
|
358
659
|
)
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
|
|
359
663
|
npc.followPath(myNPC,
|
|
664
|
+
|
|
360
665
|
{
|
|
361
|
-
|
|
666
|
+
|
|
667
|
+
path:path,
|
|
668
|
+
|
|
669
|
+
loop:true,
|
|
670
|
+
|
|
671
|
+
pathType: npc.NPCPathType.RIGID_PATH,
|
|
672
|
+
|
|
673
|
+
onFinishCallback:()=>{console.log('path is done')},
|
|
674
|
+
|
|
675
|
+
onReachedPointCallback:()=>{console.log('ending oint')},
|
|
676
|
+
|
|
677
|
+
totalDuration: 20
|
|
678
|
+
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
#### NPC Walking Speed
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
The following list of factors are used to determine speed in hierarchical order:
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
- `totalDuration` parameter set when calling `followPath()` is used over the total distance travelled over the path.
|
|
698
|
+
|
|
699
|
+
- `speed` parameter set when calling `followPath()`
|
|
700
|
+
|
|
701
|
+
- `walkingSpeed` parameter set when initializing NPC
|
|
702
|
+
|
|
703
|
+
- Default value _2_.
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
#### Joining the path
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
If the NPC's current position when calling `followPath()` doesn't match the first position in the `path` array (or the one that matches the `startingPoint` value), the current position is added to the `path` array. The NPC will start by walking from its current position to the first point provided in the path.
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
The `path` can be a single point, and the NPC will then walk a from its current position to that point.
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
> Note: If the speed of the NPC is determined by a `totalDuration` value, the segment that the NPC walks to join into the path is counted as part of the full path. If this segment is long, it will increase the NPC walking speed so that the full path lasts as what's indicated by the `totalDuration`.
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
In this example the NPC is far away from the start of the path. It will first walk from _10, 0, 10_ to _2, 0, 2_ and then continue the path.
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
```ts
|
|
728
|
+
|
|
729
|
+
export let myNPC = npc.create({position: Vector3.create(10,0,10),rotation:Quaternion.Zero(), scale: Vector3.create(1,1,1)},
|
|
730
|
+
|
|
731
|
+
//NPC Data Object
|
|
732
|
+
|
|
733
|
+
{
|
|
734
|
+
|
|
735
|
+
type: npc.NPCType.CUSTOM,
|
|
736
|
+
|
|
737
|
+
model: 'models/npc.glb',
|
|
738
|
+
|
|
739
|
+
onActivate: ()=>{console.log('npc activated');},
|
|
740
|
+
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
npc.followPath(myNPC,
|
|
746
|
+
|
|
747
|
+
{
|
|
748
|
+
|
|
749
|
+
path: [new Vector3(2, 0, 2), new Vector3(4, 0, 4), new Vector3(6, 0, 6)]
|
|
750
|
+
|
|
362
751
|
})
|
|
752
|
+
|
|
363
753
|
```
|
|
364
754
|
|
|
755
|
+
|
|
756
|
+
|
|
365
757
|
#### Example Interrupting the NPC
|
|
366
758
|
|
|
759
|
+
|
|
760
|
+
|
|
367
761
|
In the following example, an NPC starts roaming walking over a path, pausing on every point to call out for its lost kitten. If the player activates the NPC (by pressing E on it or walking near it) the NPC stops, and turns to face the player and talk. When the conversation is over, the NPC returns to walking its path from where it left off.
|
|
368
762
|
|
|
763
|
+
|
|
764
|
+
|
|
369
765
|
```ts
|
|
370
|
-
|
|
766
|
+
|
|
767
|
+
export let myNPC = npc.create({position: Vector3.create(10,0,10),rotation:Quaternion.Zero(), scale: Vector3.create(1,1,1)},
|
|
768
|
+
|
|
371
769
|
//NPC Data Object
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
770
|
+
|
|
771
|
+
{
|
|
772
|
+
|
|
773
|
+
type: npc.NPCType.CUSTOM,
|
|
774
|
+
|
|
775
|
+
model: 'models/npc.glb',
|
|
776
|
+
|
|
777
|
+
onActivate: ()=>{
|
|
778
|
+
|
|
779
|
+
npc.stopWalking(myNPC);
|
|
780
|
+
|
|
781
|
+
npc.talk(myNPC, lostCat, 0)
|
|
782
|
+
|
|
783
|
+
console.log('npc activated');
|
|
784
|
+
|
|
785
|
+
},
|
|
786
|
+
|
|
787
|
+
walkingAnim: 'walk1',
|
|
788
|
+
|
|
789
|
+
faceUser:true
|
|
790
|
+
|
|
382
791
|
}
|
|
792
|
+
|
|
383
793
|
)
|
|
384
794
|
|
|
795
|
+
|
|
796
|
+
|
|
385
797
|
npc.followPath(myNPC,
|
|
798
|
+
|
|
386
799
|
{
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
800
|
+
|
|
801
|
+
path: [new Vector3(4, 0, 30), new Vector3(6, 0, 29), new Vector3(15, 0, 25)],
|
|
802
|
+
|
|
803
|
+
loop: true,
|
|
804
|
+
|
|
805
|
+
onReachedPointCallback: () => {
|
|
806
|
+
|
|
807
|
+
npc.stopWalking(myNPC, 3)
|
|
808
|
+
|
|
809
|
+
npc.playAnimation(myNPC, `Cocky`, true, 2.93)
|
|
810
|
+
|
|
811
|
+
}
|
|
812
|
+
|
|
393
813
|
})
|
|
394
814
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
export let lostCat: Dialog[] = [
|
|
818
|
+
|
|
819
|
+
{
|
|
820
|
+
|
|
821
|
+
text: `I lost my cat, I'm going crazy here`
|
|
822
|
+
|
|
823
|
+
},
|
|
824
|
+
|
|
825
|
+
{
|
|
826
|
+
|
|
827
|
+
text: `Have you seen it anywhere?`
|
|
828
|
+
|
|
829
|
+
},
|
|
830
|
+
|
|
831
|
+
{
|
|
832
|
+
|
|
833
|
+
text: `Ok, I'm gonna go back to looking for it`,
|
|
834
|
+
|
|
835
|
+
triggeredByNext: () => {
|
|
836
|
+
|
|
837
|
+
npc.followPath(myNPC)
|
|
838
|
+
|
|
839
|
+
},
|
|
840
|
+
|
|
841
|
+
isEndOfDialog: true
|
|
842
|
+
|
|
843
|
+
}
|
|
844
|
+
|
|
409
845
|
]
|
|
846
|
+
|
|
410
847
|
```
|
|
411
848
|
|
|
849
|
+
|
|
850
|
+
|
|
412
851
|
### End interaction
|
|
413
852
|
|
|
853
|
+
|
|
854
|
+
|
|
414
855
|
The `endInteraction()` function can be used to abruptly end interactions with the NPC.
|
|
415
856
|
|
|
857
|
+
|
|
858
|
+
|
|
416
859
|
If applicable, it closes the dialog UI, hides speech bubbles, and makes the NPC stop rotating to face the player.
|
|
417
860
|
|
|
861
|
+
|
|
862
|
+
|
|
418
863
|
```ts
|
|
864
|
+
|
|
419
865
|
npc.endInteraction(myNPC)
|
|
866
|
+
|
|
420
867
|
```
|
|
421
868
|
|
|
869
|
+
|
|
870
|
+
|
|
422
871
|
As an alternative, you can call the `handleWalkAway()` function, which has the same effects (as long as `continueOnWalkAway` isn't set to true), but also triggers the `onWalkAway()` function.
|
|
423
872
|
|
|
873
|
+
|
|
874
|
+
|
|
424
875
|
## NPC Dialog Window
|
|
425
876
|
|
|
877
|
+
|
|
878
|
+
|
|
426
879
|
You can display an interactive dialog window to simulate a conversation with a non-player character (NPC).
|
|
427
880
|
|
|
881
|
+
|
|
882
|
+
|
|
428
883
|
The conversation is based on a script in JSON format. The script can include questions that can take you forward or backward, or end the conversation.
|
|
429
884
|
|
|
430
|
-
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
<img src="screenshots/NPC1.png" width="500">
|
|
888
|
+
|
|
889
|
+
|
|
431
890
|
|
|
432
891
|
### The NPC script
|
|
433
892
|
|
|
893
|
+
|
|
894
|
+
|
|
434
895
|
Each entry on the script must include at least a `text` field, but can include several more fields to further customize it.
|
|
435
896
|
|
|
897
|
+
|
|
898
|
+
|
|
436
899
|
Below is a minimal dialog.
|
|
437
900
|
|
|
901
|
+
|
|
902
|
+
|
|
438
903
|
```ts
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
904
|
+
|
|
905
|
+
export let NPCTalk: Dialog[] = [
|
|
906
|
+
|
|
907
|
+
{
|
|
908
|
+
|
|
909
|
+
text: 'Hi there'
|
|
910
|
+
|
|
911
|
+
},
|
|
912
|
+
|
|
913
|
+
{
|
|
914
|
+
|
|
915
|
+
text: 'It sure is nice talking to you'
|
|
916
|
+
|
|
917
|
+
},
|
|
918
|
+
|
|
919
|
+
{
|
|
920
|
+
|
|
921
|
+
text: 'I must go, my planet needs me',
|
|
922
|
+
|
|
923
|
+
isEndOfDialog: true
|
|
924
|
+
|
|
925
|
+
}
|
|
926
|
+
|
|
450
927
|
]
|
|
928
|
+
|
|
451
929
|
```
|
|
452
930
|
|
|
931
|
+
|
|
932
|
+
|
|
453
933
|
The player advances through each entry by clicking the mouse button. Once the last is reached, clicking again closes the window, as it's marked as `isEndOfDialog`.
|
|
454
934
|
|
|
935
|
+
|
|
936
|
+
|
|
455
937
|
The script must adhere to the following schema:
|
|
456
938
|
|
|
939
|
+
|
|
940
|
+
|
|
457
941
|
```ts
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
942
|
+
|
|
943
|
+
class Dialog {
|
|
944
|
+
|
|
945
|
+
text: string
|
|
946
|
+
|
|
947
|
+
fontSize?: number
|
|
948
|
+
|
|
949
|
+
typeSpeed?: number
|
|
950
|
+
|
|
951
|
+
isEndOfDialog?: boolean
|
|
952
|
+
|
|
953
|
+
isQuestion?:boolean
|
|
954
|
+
|
|
955
|
+
buttons?: ButtonData[]
|
|
956
|
+
|
|
957
|
+
audio?: string
|
|
958
|
+
|
|
959
|
+
triggeredByNext?: () => void
|
|
960
|
+
|
|
467
961
|
}
|
|
962
|
+
|
|
468
963
|
```
|
|
469
964
|
|
|
470
|
-
|
|
965
|
+
|
|
966
|
+
|
|
967
|
+
> Note: A `Dialog` object can be used as an input both for the `talk()` function (that is displayed in the UI), and the `talkBubble()` function (that is displayed in a floating bubble over the NPC). Properties marked with `*` are only applicable to UI dialogs.
|
|
471
968
|
|
|
969
|
+
|
|
970
|
+
|
|
472
971
|
|
|
473
972
|
You can set the following fields to change the appearance of a dialog:
|
|
474
973
|
|
|
475
|
-
|
|
476
|
-
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
- `text`: The dialog text
|
|
977
|
+
|
|
978
|
+
- `fontSize`: Size of the text
|
|
979
|
+
|
|
980
|
+
|
|
477
981
|
|
|
478
982
|
Other fields:
|
|
479
|
-
|
|
480
|
-
-
|
|
481
|
-
|
|
983
|
+
|
|
984
|
+
- `buttons *`: An array of buttons to use in a question entry, covered in the next section.
|
|
985
|
+
|
|
986
|
+
- `audio`: String with the path to an audio file to play once when this dialog is shown on the UI.
|
|
987
|
+
|
|
988
|
+
- `typeSpeed`: The text appears one character at a time, simulating typing. Players can click to skip the animation. Tune the speed of this typing (30 by default) to go slower or faster. Set to _-1_ to skip the animation.
|
|
989
|
+
|
|
990
|
+
|
|
482
991
|
|
|
483
992
|
#### Questions and conversation trees
|
|
484
993
|
|
|
994
|
+
|
|
995
|
+
|
|
485
996
|
The script can include questions that prompt the player to pick between two or up to four options. These questions can branch the conversation out and trigger other actions in the scene.
|
|
486
997
|
|
|
487
|
-
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
<img src="screenshots/NPC2.png" width="500">
|
|
1001
|
+
|
|
1002
|
+
|
|
488
1003
|
|
|
489
1004
|
> Note: Questions are only used by UI dialogs. If used in a speech bubble, questions will be displayed as regular entries with no buttons or options.
|
|
490
1005
|
|
|
1006
|
+
|
|
1007
|
+
|
|
491
1008
|
To make an entry a question, set the `isQuestion` field to _true_. This displays a set of buttons rather than the click icon. It also disables the click to advance to the next entry.
|
|
492
1009
|
|
|
1010
|
+
|
|
1011
|
+
|
|
493
1012
|
The `buttons` property of an entry contains an array of `ButtonData` objects, each one of these defines one button.
|
|
494
1013
|
|
|
1014
|
+
|
|
1015
|
+
|
|
495
1016
|
When on a question entry, you must provide at least the following for each button:
|
|
496
1017
|
|
|
497
|
-
|
|
498
|
-
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
- `label`: _(string)_ The label to show on the button.
|
|
1021
|
+
|
|
1022
|
+
- `goToDialog`: _(number | string)_ The index or name of the next dialog entry to display when activated.
|
|
1023
|
+
|
|
1024
|
+
|
|
499
1025
|
|
|
500
1026
|
> TIP: It's always better to refer to an entry by name, since the array index might shift if you add more entries and it can get hard to keep track of these references.
|
|
501
1027
|
|
|
1028
|
+
|
|
1029
|
+
|
|
502
1030
|
You can also set the following:
|
|
503
1031
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
-
|
|
507
|
-
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
- `triggeredActions`: _( () => void )_ An additional function to run whenever the button is activated
|
|
1035
|
+
|
|
1036
|
+
- `fontSize`: _(number)_ Font size of the text
|
|
1037
|
+
|
|
1038
|
+
- `offsetX`: _(number)_ Offset of the label on the X axis, relative to its normal position.
|
|
1039
|
+
|
|
1040
|
+
- `offsetY`: _(number)_ Offset of the label on the Y axis, relative to its normal position.
|
|
1041
|
+
|
|
1042
|
+
|
|
508
1043
|
|
|
509
1044
|
All buttons can be clicked to activate them. Additionally, the first button in the array can be activated by pressing the _E_ key. The second button in the array can be activated by pressing the _F_ key,
|
|
510
1045
|
|
|
511
|
-
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
<img src="screenshots/NPC3.png" width="500">
|
|
1049
|
+
|
|
1050
|
+
|
|
512
1051
|
|
|
513
1052
|
```ts
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
1053
|
+
|
|
1054
|
+
export let GemsMission: Dialog[] = [
|
|
1055
|
+
|
|
1056
|
+
{
|
|
1057
|
+
|
|
1058
|
+
text: `Hello stranger`
|
|
1059
|
+
|
|
1060
|
+
},
|
|
1061
|
+
|
|
1062
|
+
{
|
|
1063
|
+
|
|
1064
|
+
text: `Can you help me finding my missing gems?`,
|
|
1065
|
+
|
|
1066
|
+
isQuestion: true,
|
|
1067
|
+
|
|
1068
|
+
buttons: [
|
|
1069
|
+
|
|
1070
|
+
{ label: `Yes!`, goToDialog: 2 },
|
|
1071
|
+
|
|
1072
|
+
{ label: `I'm busy`, goToDialog: 4 }
|
|
1073
|
+
|
|
1074
|
+
]
|
|
1075
|
+
|
|
1076
|
+
},
|
|
1077
|
+
|
|
1078
|
+
{
|
|
1079
|
+
|
|
1080
|
+
text: `Ok, awesome, thanks!`
|
|
1081
|
+
|
|
1082
|
+
},
|
|
1083
|
+
|
|
1084
|
+
{
|
|
1085
|
+
|
|
1086
|
+
text: `I need you to find 10 gems scattered around this scene, go find them!`,
|
|
1087
|
+
|
|
1088
|
+
isEndOfDialog: true
|
|
1089
|
+
|
|
1090
|
+
},
|
|
1091
|
+
|
|
1092
|
+
{
|
|
1093
|
+
|
|
1094
|
+
text: `Ok, come back soon`,
|
|
1095
|
+
|
|
1096
|
+
isEndOfDialog: true
|
|
1097
|
+
|
|
1098
|
+
}
|
|
1099
|
+
|
|
537
1100
|
]
|
|
1101
|
+
|
|
538
1102
|
```
|
|
539
1103
|
|
|
1104
|
+
|
|
1105
|
+
|
|
540
1106
|
#### Triggering functions from the dialog
|
|
541
1107
|
|
|
1108
|
+
|
|
1109
|
+
|
|
542
1110
|
You can run functions that may affect any other part of your scene. These functions get triggered when the player interacts with the dialog window, or when the NPC displays speech bubbles.
|
|
543
1111
|
|
|
544
|
-
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
- `triggeredByNext`: Is executed when the player advances to the next dialog on a non-question dialog. The function also gets called if the dialog is the end of the conversation. It also gets called when a speech bubble advances to the next entry.
|
|
1115
|
+
|
|
1116
|
+
|
|
545
1117
|
|
|
546
|
-
-
|
|
1118
|
+
- `triggeredActions`: This property is associated to a button and is executed on a question dialog if the player activates the corresponding button. You can have up to 4 different buttons per entry, each with its own actions.
|
|
1119
|
+
|
|
1120
|
+
|
|
547
1121
|
|
|
548
1122
|
```ts
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
1123
|
+
|
|
1124
|
+
export let GemsMission: Dialog[] = [
|
|
1125
|
+
|
|
1126
|
+
{
|
|
1127
|
+
|
|
1128
|
+
text: `Hello stranger`,
|
|
1129
|
+
|
|
1130
|
+
triggeredByNext: () => {
|
|
1131
|
+
|
|
1132
|
+
// NPC plays animation to show a gem
|
|
1133
|
+
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
},
|
|
1137
|
+
|
|
1138
|
+
{
|
|
1139
|
+
|
|
1140
|
+
text: `Can you help me finding my missing gems?`,
|
|
1141
|
+
|
|
1142
|
+
isQuestion: true,
|
|
1143
|
+
|
|
1144
|
+
buttons: [
|
|
1145
|
+
|
|
1146
|
+
{
|
|
1147
|
+
|
|
1148
|
+
label: `Yes!`,
|
|
1149
|
+
|
|
1150
|
+
goToDialog: 2,
|
|
1151
|
+
|
|
1152
|
+
triggeredActions: () => {
|
|
1153
|
+
|
|
1154
|
+
// NPC plays an animation to celebrate
|
|
1155
|
+
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
},
|
|
1159
|
+
|
|
1160
|
+
{
|
|
1161
|
+
|
|
1162
|
+
label: `I'm busy`,
|
|
1163
|
+
|
|
1164
|
+
goToDialog: 4
|
|
1165
|
+
|
|
1166
|
+
triggeredActions: () => {
|
|
1167
|
+
|
|
1168
|
+
// NPC waves goodbye
|
|
1169
|
+
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
},
|
|
1173
|
+
|
|
590
1174
|
]
|
|
1175
|
+
|
|
1176
|
+
},
|
|
1177
|
+
|
|
1178
|
+
{
|
|
1179
|
+
|
|
1180
|
+
text: `Ok, awesome, thanks!`,
|
|
1181
|
+
|
|
1182
|
+
},
|
|
1183
|
+
|
|
1184
|
+
{
|
|
1185
|
+
|
|
1186
|
+
text: `I need you to find 10 gems scattered around this scene, go find them!`,
|
|
1187
|
+
|
|
1188
|
+
isEndOfDialog: true
|
|
1189
|
+
|
|
1190
|
+
triggeredByNext: () => {
|
|
1191
|
+
|
|
1192
|
+
// Gems are rendered all around the scene
|
|
1193
|
+
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
},
|
|
1197
|
+
|
|
1198
|
+
{
|
|
1199
|
+
|
|
1200
|
+
text: `Ok, come back soon`,
|
|
1201
|
+
|
|
1202
|
+
isEndOfDialog: true
|
|
1203
|
+
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
]
|
|
1207
|
+
|
|
591
1208
|
```
|
|
592
1209
|
|
|
1210
|
+
|
|
1211
|
+
|
|
593
1212
|
|
|
594
1213
|
## Contribute
|
|
595
1214
|
|
|
1215
|
+
|
|
1216
|
+
|
|
596
1217
|
In order to test changes made to this repository in active scenes, do the following:
|
|
597
1218
|
|
|
1219
|
+
|
|
1220
|
+
|
|
598
1221
|
1. Run `npm run build` for the internal files of the library to be generated
|
|
1222
|
+
|
|
599
1223
|
2. Run `npm run link` on this repository
|
|
1224
|
+
|
|
600
1225
|
3. On a new Decentraland scene, import this library as you normally would and include the tests you need
|
|
1226
|
+
|
|
601
1227
|
4. On the scene directory, run `npm link @dcl-sdk/npc-utils`
|
|
602
1228
|
|
|
1229
|
+
|
|
1230
|
+
|
|
603
1231
|
> Note: When done testing, run `npm unlink` on both folders, so that the scene stops using the local version of the library.
|
|
604
1232
|
|
|
1233
|
+
|
|
1234
|
+
|
|
605
1235
|
|
|
606
1236
|
## CI/CD
|
|
607
1237
|
|
|
1238
|
+
|
|
1239
|
+
|
|
608
1240
|
This repository uses `semantic-release` to automatically release new versions of the package to NPM.
|
|
609
1241
|
|
|
1242
|
+
|
|
1243
|
+
|
|
610
1244
|
Use the following convention for commit names:
|
|
611
1245
|
|
|
1246
|
+
|
|
1247
|
+
|
|
612
1248
|
`feat: something`: Minor release, every time you add a feature or enhancement that doesnβt break the api.
|
|
613
1249
|
|
|
1250
|
+
|
|
1251
|
+
|
|
614
1252
|
`fix: something`: Bug fixing / patch
|
|
615
1253
|
|
|
1254
|
+
|
|
1255
|
+
|
|
616
1256
|
`chore: something`: Anything that doesn't require a release to npm, like changing the readme. Updating a dependency is **not** a chore if it fixes a bug or a vulnerability, that's a `fix`.
|
|
617
1257
|
|
|
1258
|
+
|
|
1259
|
+
|
|
618
1260
|
If you break the API of the library, you need to do a major release, and that's done a different way. You need to add a second comment that starts with `BREAKING CHANGE`, like:
|
|
619
1261
|
|
|
1262
|
+
|
|
1263
|
+
|
|
620
1264
|
```
|
|
1265
|
+
|
|
621
1266
|
commit -m "feat: changed the signature of a method" -m "BREAKING CHANGE: this commit breaks the API, changing foo(arg1) to foo(arg1, arg2)"
|
|
622
|
-
|
|
1267
|
+
|
|
1268
|
+
```
|