emeraldengine 2.2.1 → 3.0.0
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 +2359 -968
- package/dist/types/index.d.ts +72 -32
- package/dist/types/src/Animator.d.ts +50 -0
- package/dist/types/{BitmapText.d.ts → src/BitmapText.d.ts} +19 -21
- package/dist/types/src/Camera.d.ts +122 -0
- package/dist/types/src/CameraController.d.ts +107 -0
- package/dist/types/src/CanvasText.d.ts +91 -0
- package/dist/types/src/CollisionLayers.d.ts +58 -0
- package/dist/types/{Color.d.ts → src/Color.d.ts} +5 -6
- package/dist/types/src/Coroutine.d.ts +111 -0
- package/dist/types/src/DebugOverlay.d.ts +86 -0
- package/dist/types/src/Drawable.d.ts +271 -0
- package/dist/types/src/Easing.d.ts +22 -0
- package/dist/types/src/Emerald.d.ts +420 -0
- package/dist/types/src/EmeraldDB.d.ts +159 -0
- package/dist/types/{FPSCounter.d.ts → src/FPSCounter.d.ts} +1 -3
- package/dist/types/src/GLUtils.d.ts +4 -0
- package/dist/types/{Instance.d.ts → src/Instance.d.ts} +37 -15
- package/dist/types/{InstancedTexture.d.ts → src/InstancedTexture.d.ts} +60 -23
- package/dist/types/src/Interpolator.d.ts +66 -0
- package/dist/types/src/Material.d.ts +87 -0
- package/dist/types/src/MathUtils.d.ts +80 -0
- package/dist/types/src/ParticleEmitter.d.ts +131 -0
- package/dist/types/{Physics.d.ts → src/Physics.d.ts} +88 -20
- package/dist/types/src/Pool.d.ts +53 -0
- package/dist/types/src/PostEffects.d.ts +68 -0
- package/dist/types/src/PostProcessor.d.ts +124 -0
- package/dist/types/src/RenderTarget.d.ts +56 -0
- package/dist/types/src/Scene.d.ts +62 -0
- package/dist/types/src/ScreenEffects.d.ts +111 -0
- package/dist/types/src/Serializer.d.ts +86 -0
- package/dist/types/src/Shaders.d.ts +2 -0
- package/dist/types/{Shapes.d.ts → src/Shapes.d.ts} +4 -6
- package/dist/types/src/SpatialGrid.d.ts +50 -0
- package/dist/types/src/SpriteBatch.d.ts +89 -0
- package/dist/types/src/StateMachine.d.ts +59 -0
- package/dist/types/src/Storage.d.ts +89 -0
- package/dist/types/{Texture.d.ts → src/Texture.d.ts} +3 -4
- package/dist/types/src/TextureAtlas.d.ts +55 -0
- package/dist/types/src/Tilemap.d.ts +91 -0
- package/dist/types/src/Time.d.ts +53 -0
- package/dist/types/src/Timer.d.ts +54 -0
- package/dist/types/src/Transform.d.ts +94 -0
- package/dist/types/src/Tween.d.ts +81 -0
- package/dist/types/src/UI.d.ts +121 -0
- package/dist/types/src/components/Behaviour.d.ts +71 -0
- package/dist/types/{components → src/components}/BoxCollider.d.ts +14 -14
- package/dist/types/src/components/BoxColliderDebug.d.ts +15 -0
- package/dist/types/{components → src/components}/CircleCollider.d.ts +14 -12
- package/dist/types/src/components/CircleColliderDebug.d.ts +15 -0
- package/dist/types/src/components/Collider.d.ts +96 -0
- package/dist/types/src/components/GameObject.d.ts +135 -0
- package/dist/types/src/components/RigidBody.d.ts +183 -0
- package/dist/types/src/importers/Aseprite.d.ts +79 -0
- package/dist/types/src/importers/TiledMap.d.ts +62 -0
- package/dist/types/{lights → src/lights}/DirectionalLight.d.ts +7 -9
- package/dist/types/{lights → src/lights}/PointLight.d.ts +6 -9
- package/dist/types/src/managers/AssetManager.d.ts +116 -0
- package/dist/types/src/managers/AudioManager.d.ts +259 -0
- package/dist/types/{managers → src/managers}/CameraManager.d.ts +8 -8
- package/dist/types/{managers → src/managers}/EventManager.d.ts +46 -32
- package/dist/types/src/managers/GLManager.d.ts +84 -0
- package/dist/types/src/managers/GLState.d.ts +37 -0
- package/dist/types/src/managers/IDManager.d.ts +31 -0
- package/dist/types/src/managers/InputManager.d.ts +290 -0
- package/dist/types/src/managers/NetworkManager.d.ts +93 -0
- package/dist/types/src/managers/RenderStats.d.ts +34 -0
- package/dist/types/src/managers/SceneManager.d.ts +44 -0
- package/dist/types/src/managers/ShaderManager.d.ts +55 -0
- package/dist/types/src/managers/TextureManager.d.ts +102 -0
- package/dist/types/src/particlesystem/Particle.d.ts +64 -0
- package/dist/types/{particlesystem → src/particlesystem}/ParticleSettings.d.ts +32 -24
- package/dist/types/{particlesystem → src/particlesystem}/Particles.d.ts +20 -12
- package/index.js +72 -0
- package/package.json +74 -60
- package/src/Animator.js +95 -0
- package/src/BitmapText.js +6 -5
- package/src/Camera.js +183 -0
- package/src/CameraController.js +192 -0
- package/src/CanvasText.js +281 -0
- package/src/CollisionLayers.js +86 -0
- package/src/Color.js +18 -18
- package/src/Coroutine.js +259 -0
- package/src/DebugOverlay.js +246 -0
- package/src/Drawable.js +842 -582
- package/src/Easing.js +57 -0
- package/src/Emerald.js +1150 -459
- package/src/EmeraldDB.js +328 -0
- package/src/FPSCounter.js +43 -43
- package/src/GLUtils.js +60 -67
- package/src/Instance.js +41 -5
- package/src/InstancedTexture.js +251 -118
- package/src/Interpolator.js +124 -0
- package/src/Material.js +202 -0
- package/src/MathUtils.js +133 -0
- package/src/ParticleEmitter.js +284 -0
- package/src/Physics.js +186 -5
- package/src/Pool.js +85 -0
- package/src/PostEffects.js +296 -0
- package/src/PostProcessor.js +304 -0
- package/src/RenderTarget.js +134 -0
- package/src/Scene.js +115 -83
- package/src/ScreenEffects.js +266 -0
- package/src/Serializer.js +131 -0
- package/src/Shaders.js +150 -165
- package/src/Shapes.js +118 -129
- package/src/SpatialGrid.js +111 -0
- package/src/SpriteBatch.js +299 -0
- package/src/StateMachine.js +82 -0
- package/src/Storage.js +175 -47
- package/src/Texture.js +58 -67
- package/src/TextureAtlas.js +96 -0
- package/src/Tilemap.js +274 -0
- package/src/Time.js +51 -6
- package/src/Timer.js +99 -0
- package/src/Transform.js +100 -7
- package/src/Tween.js +160 -0
- package/src/UI.js +394 -0
- package/src/components/Behaviour.js +90 -0
- package/src/components/BoxCollider.js +22 -3
- package/src/components/CircleCollider.js +19 -3
- package/src/components/CircleColliderDebug.js +24 -24
- package/src/components/Collider.js +140 -34
- package/src/components/GameObject.js +130 -21
- package/src/components/RigidBody.js +123 -2
- package/src/importers/Aseprite.js +142 -0
- package/src/importers/TiledMap.js +158 -0
- package/src/lights/DirectionalLight.js +6 -15
- package/src/lights/PointLight.js +4 -4
- package/src/managers/AssetManager.js +239 -0
- package/src/managers/AudioManager.js +565 -146
- package/src/managers/EventManager.js +488 -477
- package/src/managers/GLManager.js +57 -0
- package/src/managers/GLState.js +70 -0
- package/src/managers/IDManager.js +24 -2
- package/src/managers/InputManager.js +779 -0
- package/src/managers/NetworkManager.js +178 -0
- package/src/managers/RenderStats.js +34 -0
- package/src/managers/SceneManager.js +30 -0
- package/src/managers/ShaderManager.js +0 -2
- package/src/managers/TextureManager.js +218 -0
- package/src/particlesystem/Particle.js +82 -7
- package/src/particlesystem/ParticleSettings.js +21 -3
- package/src/particlesystem/Particles.js +80 -31
- package/dist/types/Drawable.d.ts +0 -157
- package/dist/types/Emerald.d.ts +0 -73
- package/dist/types/GLUtils.d.ts +0 -6
- package/dist/types/Scene.d.ts +0 -39
- package/dist/types/Shaders.d.ts +0 -4
- package/dist/types/Storage.d.ts +0 -46
- package/dist/types/Time.d.ts +0 -22
- package/dist/types/Transform.d.ts +0 -41
- package/dist/types/components/BoxColliderDebug.d.ts +0 -19
- package/dist/types/components/CircleColliderDebug.d.ts +0 -19
- package/dist/types/components/Collider.d.ts +0 -53
- package/dist/types/components/GameObject.d.ts +0 -72
- package/dist/types/components/RigidBody.d.ts +0 -104
- package/dist/types/managers/AudioManager.d.ts +0 -60
- package/dist/types/managers/GLManager.d.ts +0 -47
- package/dist/types/managers/IDManager.d.ts +0 -21
- package/dist/types/managers/SceneManager.d.ts +0 -22
- package/dist/types/particlesystem/Particle.d.ts +0 -42
package/README.md
CHANGED
|
@@ -1,968 +1,2359 @@
|
|
|
1
|
-
# Emerald
|
|
2
|
-
|
|
3
|
-
Emerald is a comprehensive 2D graphics engine that can help you create games easier than ever.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- [
|
|
35
|
-
- [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
- [
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- [
|
|
45
|
-
|
|
46
|
-
- [
|
|
47
|
-
- [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- [
|
|
51
|
-
- [
|
|
52
|
-
- [
|
|
53
|
-
- [
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
- [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
- [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
//
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
//
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
###
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
//
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
```javascript
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
//
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
//
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
//
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
```javascript
|
|
686
|
-
import
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
}
|
|
821
|
-
```
|
|
822
|
-
|
|
823
|
-
###
|
|
824
|
-
|
|
825
|
-
```javascript
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
)
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
```javascript
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
//
|
|
949
|
-
const
|
|
950
|
-
|
|
951
|
-
//
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1
|
+
# Emerald
|
|
2
|
+
|
|
3
|
+
Emerald is a comprehensive 2D graphics engine that can help you create games easier than ever.
|
|
4
|
+
|
|
5
|
+
**New to the engine?** Start with the [Getting Started guide](docs/getting-started.md) —
|
|
6
|
+
it walks from an empty page to a playable sprite with input, tiles, audio,
|
|
7
|
+
saves, and a debug overlay.
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Emerald](#emerald)
|
|
12
|
+
- [Table of Contents](#table-of-contents)
|
|
13
|
+
- [Getting Started](#getting-started)
|
|
14
|
+
- [Core Systems](#core-systems)
|
|
15
|
+
- [Sprites: flipping, pivot & anchor](#sprites-flipping-pivot--anchor)
|
|
16
|
+
- [Rendering Pipeline (post-processing, materials, batching)](#rendering-pipeline-post-processing-materials-batching)
|
|
17
|
+
- [Post-processing](#post-processing)
|
|
18
|
+
- [PostEffects (built-in)](#posteffects-built-in)
|
|
19
|
+
- [RenderTarget](#rendertarget)
|
|
20
|
+
- [Material (custom shaders)](#material-custom-shaders)
|
|
21
|
+
- [SpriteBatch](#spritebatch)
|
|
22
|
+
- [In-Engine UI](#in-engine-ui)
|
|
23
|
+
- [Game Loop & Scene Transitions](#game-loop--scene-transitions)
|
|
24
|
+
- [ScreenEffects (transitions)](#screeneffects-transitions)
|
|
25
|
+
- [Coroutines](#coroutines)
|
|
26
|
+
- [Gamepads & Controllers](#gamepads--controllers)
|
|
27
|
+
- [Per-instance color & tinting](#per-instance-color--tinting)
|
|
28
|
+
- [Physics: collision layers & continuous detection](#physics-collision-layers--continuous-detection)
|
|
29
|
+
- [RigidBody velocity helpers](#rigidbody-velocity-helpers)
|
|
30
|
+
- [Tilemap colliders & auto-tiling](#tilemap-colliders--auto-tiling)
|
|
31
|
+
- [Camera follow deadzone](#camera-follow-deadzone)
|
|
32
|
+
- [ParticleEmitter (pooled sprites)](#particleemitter-pooled-sprites)
|
|
33
|
+
- [Advanced Particles](#advanced-particles)
|
|
34
|
+
- [Positional (spatial) Audio](#positional-spatial-audio)
|
|
35
|
+
- [Audio buses & fades](#audio-buses--fades)
|
|
36
|
+
- [AssetManager](#assetmanager-loading)
|
|
37
|
+
- [Asset importers (Tiled & Aseprite)](#asset-importers-tiled--aseprite)
|
|
38
|
+
- [Networking (NetworkManager + Interpolator)](#networking-networkmanager--interpolator)
|
|
39
|
+
- [DebugOverlay (upgraded)](#debugoverlay-upgraded)
|
|
40
|
+
- [Storage (versioned saves)](#storage-versioned-saves)
|
|
41
|
+
- [EmeraldDB (IndexedDB saves)](#emeralddb-indexeddb-saves)
|
|
42
|
+
- [Resolution independence](#resolution-independence-design-resolution)
|
|
43
|
+
- [Auto-pause & lifecycle](#auto-pause--lifecycle)
|
|
44
|
+
- [Production hardening](#production-hardening-resource-lifecycle-context-loss-render-stats)
|
|
45
|
+
- [Usage](#usage)
|
|
46
|
+
- [Basic Setup](#basic-setup)
|
|
47
|
+
- [Set the background color for the engine](#set-the-background-color-for-the-engine)
|
|
48
|
+
- [Drawing the scene](#drawing-the-scene)
|
|
49
|
+
- [Scene](#scene)
|
|
50
|
+
- [Adding and removing items from the scene](#adding-and-removing-items-from-the-scene)
|
|
51
|
+
- [Set Active Scene](#set-active-scene)
|
|
52
|
+
- [Game Objects](#game-objects)
|
|
53
|
+
- [Creating a new GameObject](#creating-a-new-gameobject)
|
|
54
|
+
- [Components](#components)
|
|
55
|
+
- [Texture](#texture)
|
|
56
|
+
- [InstancedTexture](#instancedtexture)
|
|
57
|
+
- [Square2D](#square2d)
|
|
58
|
+
- [Triangle2D](#triangle2d)
|
|
59
|
+
- [Circle2D](#circle2d)
|
|
60
|
+
- [RigidBody](#rigidbody)
|
|
61
|
+
- [BoxCollider](#boxcollider)
|
|
62
|
+
- [CircleCollider](#circlecollider)
|
|
63
|
+
- [Object methods](#object-methods)
|
|
64
|
+
- [Position](#position)
|
|
65
|
+
- [Rotation](#rotation)
|
|
66
|
+
- [Scale](#scale)
|
|
67
|
+
- [Change color](#change-color)
|
|
68
|
+
- [Set texture frame](#set-texture-frame)
|
|
69
|
+
- [Animations](#animations)
|
|
70
|
+
- [Instance System](#instance-system)
|
|
71
|
+
- [Creating Instances](#creating-instances)
|
|
72
|
+
- [Instance Management](#instance-management)
|
|
73
|
+
- [Instance Events](#instance-events)
|
|
74
|
+
- [Physics Engine](#physics-engine)
|
|
75
|
+
- [Setting up Physics](#setting-up-physics)
|
|
76
|
+
- [Physics Bodies](#physics-bodies)
|
|
77
|
+
- [Collision Detection](#collision-detection)
|
|
78
|
+
- [Collision Events](#collision-events)
|
|
79
|
+
- [Particle System](#particle-system)
|
|
80
|
+
- [Particle Settings](#particle-settings)
|
|
81
|
+
- [Creating Particle Systems](#creating-particle-systems)
|
|
82
|
+
- [Particle System Methods](#particle-system-methods)
|
|
83
|
+
- [Lighting System](#lighting-system)
|
|
84
|
+
- [Ambient Light](#ambient-light)
|
|
85
|
+
- [Point Light](#point-light)
|
|
86
|
+
- [Directional Light](#directional-light)
|
|
87
|
+
- [Text Rendering](#text-rendering)
|
|
88
|
+
- [BitmapText](#bitmaptext)
|
|
89
|
+
- [EventManager](#eventmanager)
|
|
90
|
+
- [Keyboard Events](#keyboard-events)
|
|
91
|
+
- [Mouse Events](#mouse-events)
|
|
92
|
+
- [Object Events](#object-events)
|
|
93
|
+
- [Event Cleanup](#event-cleanup)
|
|
94
|
+
- [AudioManager](#audiomanager)
|
|
95
|
+
- [Adding Audio](#adding-audio)
|
|
96
|
+
- [Playing Audio](#playing-audio)
|
|
97
|
+
- [Audio Control](#audio-control)
|
|
98
|
+
- [Camera](#camera)
|
|
99
|
+
- [FPSCounter](#fpscounter)
|
|
100
|
+
- [Scene Management](#scene-management)
|
|
101
|
+
- [Time Management](#time-management)
|
|
102
|
+
- [Advanced Features](#advanced-features)
|
|
103
|
+
- [Resize Handling](#resize-handling)
|
|
104
|
+
|
|
105
|
+
## Getting Started
|
|
106
|
+
|
|
107
|
+
To get started with Emerald, you need to have a canvas element in your HTML and import the necessary classes.
|
|
108
|
+
|
|
109
|
+
## Core Systems
|
|
110
|
+
|
|
111
|
+
Everything below ships from the package root (`import { ... } from "emeraldengine"`).
|
|
112
|
+
|
|
113
|
+
### Game loop & Time
|
|
114
|
+
|
|
115
|
+
`Emerald.drawScene(scene, dt)` renders the scene and also advances global `Tween`/`Timer` updates using the time-scaled delta. `Time` is updated for you each call.
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
import { Time } from "emeraldengine";
|
|
119
|
+
Time.getDeltaTime(); // time-scaled seconds since last frame
|
|
120
|
+
Time.getUnscaledDeltaTime(); // raw delta, ignores timeScale
|
|
121
|
+
Time.getElapsedTime(); // total accumulated time
|
|
122
|
+
Time.setTimeScale(0.5); // 0 = paused, 1 = normal, 2 = double speed
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Behaviour (component lifecycle)
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
import { Behaviour } from "emeraldengine";
|
|
129
|
+
|
|
130
|
+
class Spinner extends Behaviour {
|
|
131
|
+
start() {
|
|
132
|
+
this.speed = 2;
|
|
133
|
+
} // once, before first update
|
|
134
|
+
update(dt) {
|
|
135
|
+
this.gameObject.transform.rotation += this.speed * dt;
|
|
136
|
+
}
|
|
137
|
+
onCollisionEnter(other, contact) {} // requires physics ticking
|
|
138
|
+
onCollisionExit(other, contact) {}
|
|
139
|
+
onDestroy() {}
|
|
140
|
+
}
|
|
141
|
+
obj.addComponent(new Spinner());
|
|
142
|
+
scene.update(dt); // ticks all Behaviours on active objects
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Transform hierarchy
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
parent.addChild(child); // or child.setParent(parent)
|
|
149
|
+
child.setParent(null); // detach
|
|
150
|
+
// child.transform composes on top of the parent's position/rotation/scale
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Cameras & split-screen
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
import { Camera, CameraController } from "emeraldengine";
|
|
157
|
+
|
|
158
|
+
// Multiple cameras, each with a normalized viewport (origin bottom-left)
|
|
159
|
+
const top = new Camera({ viewport: { x: 0, y: 0.5, width: 1, height: 0.5 } });
|
|
160
|
+
const bottom = new Camera({ viewport: { x: 0, y: 0, width: 1, height: 0.5 } });
|
|
161
|
+
emerald.setCameras([top, bottom]); // or emerald.addCamera(cam) / removeCamera(cam)
|
|
162
|
+
|
|
163
|
+
camera.setZoom(1.5);
|
|
164
|
+
camera.setPosition(x, y);
|
|
165
|
+
camera.clearColor = new Color(10, 14, 20); // optional per-viewport clear
|
|
166
|
+
|
|
167
|
+
// Per-camera layer visibility (e.g. translucent opponent in 2-player)
|
|
168
|
+
top.setIgnoreLayers([11, 20]); // skip these object layers in this camera
|
|
169
|
+
|
|
170
|
+
// Smooth follow / bounds / shake
|
|
171
|
+
const cam = new CameraController(emerald.camera);
|
|
172
|
+
cam.follow(player, 0.12).setBounds(-1000, -1000, 1000, 1000);
|
|
173
|
+
cam.shake(10, 0.3);
|
|
174
|
+
// each frame: cam.update(dt);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Input
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
import { InputManager } from "emeraldengine";
|
|
181
|
+
const input = new InputManager();
|
|
182
|
+
input.mapAction("jump", ["Space", " ", "pad:0:south"]); // keyboard + gamepad
|
|
183
|
+
input.mapAction("left", ["a", "ArrowLeft", "pad:0:dpadLeft"]);
|
|
184
|
+
// in the loop:
|
|
185
|
+
if (input.justPressed("jump")) player.jump();
|
|
186
|
+
const move = input.getAxis("left", "right"); // -1 / 0 / +1
|
|
187
|
+
input.update(); // call once per frame (edge detection + gamepad polling)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Gamepad tokens (`pad:0:south`, sticks, triggers, rumble, mapping for non-standard
|
|
191
|
+
pads) are documented in [Gamepads & Controllers](#gamepads--controllers).
|
|
192
|
+
|
|
193
|
+
`EventManager` handles per-object click/hover; it now hits only the **topmost** object and `screenToWorld(clientX, clientY)` accounts for camera zoom, DPR, and viewport.
|
|
194
|
+
|
|
195
|
+
### Tween & Easing
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
import { Tween, Easing } from "emeraldengine";
|
|
199
|
+
Tween.to(sprite.transform.position, { x: 200, y: -50 }, 0.6, {
|
|
200
|
+
easing: Easing.outBack, // linear, inOutQuad, outCubic, outBounce, outElastic, ...
|
|
201
|
+
delay: 0,
|
|
202
|
+
loop: false,
|
|
203
|
+
yoyo: false,
|
|
204
|
+
onUpdate: (t) => {},
|
|
205
|
+
onComplete: () => {},
|
|
206
|
+
}).then(() => console.log("done"));
|
|
207
|
+
Tween.killOf(target);
|
|
208
|
+
Tween.killAll();
|
|
209
|
+
// driven automatically by drawScene
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Timer
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
import { Timer } from "emeraldengine";
|
|
216
|
+
Timer.after(2, () => spawnEnemy()); // once
|
|
217
|
+
const h = Timer.every(0.5, () => tick(), 10); // 10 times (omit count = forever)
|
|
218
|
+
Timer.clear(h);
|
|
219
|
+
Timer.clearAll();
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### StateMachine
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
import { StateMachine } from "emeraldengine";
|
|
226
|
+
const fsm = new StateMachine();
|
|
227
|
+
fsm.add("idle", {
|
|
228
|
+
update: (dt, sm) => {
|
|
229
|
+
if (seen) sm.set("chase");
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
fsm.add("chase", { enter: () => roar(), update: (dt) => move(dt) });
|
|
233
|
+
fsm.set("idle");
|
|
234
|
+
// in update(dt): fsm.update(dt); -> fsm.is("chase")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### SpatialGrid
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
import { SpatialGrid } from "emeraldengine";
|
|
241
|
+
const grid = new SpatialGrid(64);
|
|
242
|
+
grid.clear();
|
|
243
|
+
for (const e of enemies)
|
|
244
|
+
grid.insert(e, e.transform.position.x, e.transform.position.y);
|
|
245
|
+
const near = grid.queryRadius(px, py, 100); // or grid.queryRect(...)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Pool
|
|
249
|
+
|
|
250
|
+
```javascript
|
|
251
|
+
import { Pool } from "emeraldengine";
|
|
252
|
+
const bullets = new Pool(
|
|
253
|
+
() => new Bullet(),
|
|
254
|
+
(b, x, y) => b.spawn(x, y),
|
|
255
|
+
50
|
|
256
|
+
);
|
|
257
|
+
const b = bullets.acquire(px, py);
|
|
258
|
+
bullets.release(b); // bullets.releaseAll()
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### MathUtils
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
import { MathUtils } from "emeraldengine";
|
|
265
|
+
MathUtils.clamp(v, 0, 1);
|
|
266
|
+
MathUtils.lerp(a, b, t);
|
|
267
|
+
MathUtils.map(v, 0, 10, 0, 100);
|
|
268
|
+
MathUtils.degToRad(90);
|
|
269
|
+
MathUtils.randomRange(0, 5);
|
|
270
|
+
MathUtils.randomInt(1, 6);
|
|
271
|
+
MathUtils.distance(a, b);
|
|
272
|
+
MathUtils.normalize(v);
|
|
273
|
+
MathUtils.angleBetween(a, b);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Physics: fixed timestep, per-object collisions, raycast
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
physics.setFixedTimeStep(1 / 60); // stable, frame-rate independent
|
|
280
|
+
physics.process(dt); // accumulator-based stepping
|
|
281
|
+
|
|
282
|
+
// Per-object events fire on Behaviour components automatically (see Behaviour).
|
|
283
|
+
const hit = physics.raycast({ x, y }, { x: 1, y: 0 }, 500);
|
|
284
|
+
// -> { object, rigidBody, point, normal, fraction } | null
|
|
285
|
+
const objects = physics.queryPoint({ x, y }); // owners whose collider contains the point
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Rendering extras
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
// Layers (sorted before z) and screen-space HUD
|
|
292
|
+
obj.setLayer(10);
|
|
293
|
+
hudObj.setScreenSpace(true); // ignores camera; position is pixels from viewport center
|
|
294
|
+
|
|
295
|
+
// Blend modes (per drawable)
|
|
296
|
+
drawable.setBlendMode("additive"); // "normal" | "additive" | "multiply"
|
|
297
|
+
|
|
298
|
+
// Off-screen culling (on by default): objects outside the view are skipped.
|
|
299
|
+
emerald.setCullingEnabled(true);
|
|
300
|
+
particles.alwaysVisible = true; // opt an object out (e.g. emitters with spread)
|
|
301
|
+
|
|
302
|
+
// Texture atlas
|
|
303
|
+
import { TextureAtlas } from "emeraldengine";
|
|
304
|
+
const atlas = await TextureAtlas.load("sheet.png", sheetJson, true);
|
|
305
|
+
atlas.applyTo(sprite, "player_idle_0"); // sets a UV sub-rect on the Drawable
|
|
306
|
+
|
|
307
|
+
// Dynamic system-font text
|
|
308
|
+
import { CanvasText } from "emeraldengine";
|
|
309
|
+
const label = CanvasText.create("Score: 0", {
|
|
310
|
+
font: "bold 28px monospace",
|
|
311
|
+
color: "#6ee7b7",
|
|
312
|
+
screenSpace: true,
|
|
313
|
+
});
|
|
314
|
+
scene.add(label);
|
|
315
|
+
label.getComponent(CanvasText).setText("Score: 120");
|
|
316
|
+
|
|
317
|
+
// Shared/cached GL textures
|
|
318
|
+
import { TextureManager } from "emeraldengine";
|
|
319
|
+
await TextureManager.preload(["a.png", "b.png"], true);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Animator, Tilemap, Particles
|
|
323
|
+
|
|
324
|
+
```javascript
|
|
325
|
+
import { Animator, Tilemap } from "emeraldengine";
|
|
326
|
+
|
|
327
|
+
const anim = new Animator();
|
|
328
|
+
anim
|
|
329
|
+
.addClip("run", [0, 1, 2, 3], { speed: 100 })
|
|
330
|
+
.addClip("jump", [8, 9], { loop: false });
|
|
331
|
+
obj.addComponent(texture);
|
|
332
|
+
obj.addComponent(anim);
|
|
333
|
+
anim.play("run");
|
|
334
|
+
|
|
335
|
+
const map = new Tilemap("level", "tiles.png", {
|
|
336
|
+
tileSize: 32,
|
|
337
|
+
frameWidth: 16,
|
|
338
|
+
frameHeight: 16,
|
|
339
|
+
framesPerRow: 4,
|
|
340
|
+
totalFrames: 16,
|
|
341
|
+
});
|
|
342
|
+
map.setMap([
|
|
343
|
+
[0, 0, 0],
|
|
344
|
+
[1, -1, 1],
|
|
345
|
+
]); // -1 = empty; one instanced draw call
|
|
346
|
+
scene.add(map.gameObject);
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### AudioManager
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
audio.add("jump.wav", "jump", { volume: 0.8, loop: false });
|
|
353
|
+
audio.play("jump"); // restart from 0
|
|
354
|
+
audio.playOverlap("jump"); // overlapping copies for rapid SFX
|
|
355
|
+
audio.setMasterVolume(0.5);
|
|
356
|
+
audio.setBusVolume("music", 0.6); // mix buses: master * bus * sound
|
|
357
|
+
audio.crossfade("theme", "boss", 2.0); // timed fades: fadeIn/fadeOut/fadeTo too
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Full mixing details: [Audio buses & fades](#audio-buses--fades).
|
|
361
|
+
|
|
362
|
+
### Serializer (save/load scenes)
|
|
363
|
+
|
|
364
|
+
```javascript
|
|
365
|
+
import { Serializer } from "emeraldengine";
|
|
366
|
+
Serializer.register("coin", (data) => makeCoin(data.value));
|
|
367
|
+
coin.prefabType = "coin";
|
|
368
|
+
coin.serialize = () => ({ value: 5 });
|
|
369
|
+
const json = Serializer.toJSON(scene); // save
|
|
370
|
+
Serializer.fromJSON(json, new Scene()); // load
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### DebugOverlay
|
|
374
|
+
|
|
375
|
+
```javascript
|
|
376
|
+
import { DebugOverlay } from "emeraldengine";
|
|
377
|
+
const debug = new DebugOverlay();
|
|
378
|
+
// after drawScene: debug.update(emerald, scene); // FPS / objects / cameras
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### NPM scripts
|
|
382
|
+
|
|
383
|
+
| Script | Purpose |
|
|
384
|
+
| ----------------- | -------------------------------------------- |
|
|
385
|
+
| `npm test` | Node test suite (`node --test test/`) |
|
|
386
|
+
| `npm run types` | Regenerate `dist/types` from JSDoc via `tsc` |
|
|
387
|
+
| `npm run format` | Prettier |
|
|
388
|
+
|
|
389
|
+
## Sprites: flipping, pivot & anchor
|
|
390
|
+
|
|
391
|
+
Any `Texture` (or other `Drawable`) can be mirrored and re-pivoted without
|
|
392
|
+
touching the GameObject's scale — handy for characters that face left/right and
|
|
393
|
+
for putting a sprite's origin at its feet.
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
const tex = gameObject.getComponent(Texture);
|
|
397
|
+
|
|
398
|
+
tex.setFlipX(facing < 0); // mirror horizontally (e.g. face left)
|
|
399
|
+
tex.setFlipY(true); // mirror vertically
|
|
400
|
+
|
|
401
|
+
// Pivot: which local point sits on the GameObject's position and acts as the
|
|
402
|
+
// rotation/scale center. (0,0) = center (default); x in [-1,1] left..right,
|
|
403
|
+
// y in [-1,1] bottom..top.
|
|
404
|
+
tex.setPivot(0, -1); // bottom-center — feet on the ground
|
|
405
|
+
|
|
406
|
+
// Anchor: the same thing in 0..1 with a top-left origin (CSS-style).
|
|
407
|
+
tex.setAnchor(0.5, 1); // bottom-center
|
|
408
|
+
tex.setAnchor(0.5, 0.5); // back to center
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Rendering Pipeline (post-processing, materials, batching)
|
|
412
|
+
|
|
413
|
+
Everything in this section ships from the package root: `import { ... } from "emeraldengine"`.
|
|
414
|
+
|
|
415
|
+
### Post-processing
|
|
416
|
+
|
|
417
|
+
When post-processing is enabled, Emerald renders the whole scene into an offscreen
|
|
418
|
+
texture and then runs a chain of full-screen shader passes (ping-ponging between two
|
|
419
|
+
render targets) before drawing the final image to the canvas. You manage it entirely
|
|
420
|
+
through the `Emerald` instance:
|
|
421
|
+
|
|
422
|
+
```javascript
|
|
423
|
+
emerald.enablePostProcessing(); // allocate the scene render target + processor
|
|
424
|
+
emerald.disablePostProcessing(); // turn it back off
|
|
425
|
+
|
|
426
|
+
const bloom = emerald.addPostEffect(PostEffects.bloom()); // returns the effect
|
|
427
|
+
emerald.removePostEffect(bloom);
|
|
428
|
+
|
|
429
|
+
// Effects run in the order they were added. Toggle one without removing it:
|
|
430
|
+
bloom.enabled = false;
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
`drawScene` automatically routes through the processor while any **enabled** effect
|
|
434
|
+
exists; if none do, it draws straight to the screen with zero overhead.
|
|
435
|
+
|
|
436
|
+
Write a custom pass by constructing a `PostEffect`. Your fragment shader gets
|
|
437
|
+
`vUV` (0–1 screen UV), `uScene` (the previous pass), `uResolution`, and `uTime`
|
|
438
|
+
for free — declare any extra uniforms and set them in `setUniforms`:
|
|
439
|
+
|
|
440
|
+
```javascript
|
|
441
|
+
import { PostEffect } from "emeraldengine";
|
|
442
|
+
|
|
443
|
+
const tint = new PostEffect(
|
|
444
|
+
"tint",
|
|
445
|
+
`
|
|
446
|
+
uniform vec3 uTint;
|
|
447
|
+
void main() {
|
|
448
|
+
gl_FragColor = texture2D(uScene, vUV) * vec4(uTint, 1.0);
|
|
449
|
+
}`,
|
|
450
|
+
{
|
|
451
|
+
setUniforms: (gl, loc) => gl.uniform3f(loc("uTint"), 1.0, 0.85, 0.7),
|
|
452
|
+
enabled: true,
|
|
453
|
+
}
|
|
454
|
+
);
|
|
455
|
+
emerald.addPostEffect(tint);
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Keep UI crisp by rendering it on a camera **excluded from post-processing** —
|
|
459
|
+
it draws straight to the screen after the effect chain, so bloom never blows
|
|
460
|
+
out your buttons and text:
|
|
461
|
+
|
|
462
|
+
```javascript
|
|
463
|
+
const uiCam = new Camera({ excludeFromPost: true }); // or uiCam.setExcludeFromPost(true)
|
|
464
|
+
emerald.addCamera(uiCam);
|
|
465
|
+
hudObject.setLayer(100); // and restrict cameras via setOnlyLayers/ignoreLayers
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### PostEffects (built-in)
|
|
469
|
+
|
|
470
|
+
Factory functions on the `PostEffects` namespace return a ready `PostEffect`:
|
|
471
|
+
|
|
472
|
+
```javascript
|
|
473
|
+
import { PostEffects } from "emeraldengine";
|
|
474
|
+
|
|
475
|
+
emerald.enablePostProcessing();
|
|
476
|
+
emerald.addPostEffect(
|
|
477
|
+
PostEffects.bloom({ threshold: 0.6, intensity: 1.2, spread: 1.1 })
|
|
478
|
+
);
|
|
479
|
+
emerald.addPostEffect(
|
|
480
|
+
PostEffects.vignette({ intensity: 0.5, radius: 0.75, softness: 0.45 })
|
|
481
|
+
);
|
|
482
|
+
emerald.addPostEffect(
|
|
483
|
+
PostEffects.colorGrade({ brightness: 0.02, contrast: 1.08, saturation: 1.15 })
|
|
484
|
+
);
|
|
485
|
+
emerald.addPostEffect(PostEffects.chromaticAberration({ amount: 0.003 }));
|
|
486
|
+
emerald.addPostEffect(PostEffects.scanlines({ intensity: 0.15, count: 480 }));
|
|
487
|
+
emerald.addPostEffect(
|
|
488
|
+
PostEffects.crt({ curvature: 4.0, scanlineIntensity: 0.2, vignette: 0.3 })
|
|
489
|
+
);
|
|
490
|
+
emerald.addPostEffect(PostEffects.grayscale());
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
| Effect | Options (defaults) |
|
|
494
|
+
| --------------------- | ----------------------------------------------------------- |
|
|
495
|
+
| `bloom` | `threshold 0.7`, `intensity 1.0`, `spread 1.0` (multi-pass) |
|
|
496
|
+
| `vignette` | `intensity 0.5`, `radius 0.75`, `softness 0.45` |
|
|
497
|
+
| `colorGrade` | `brightness 0`, `contrast 1`, `saturation 1` |
|
|
498
|
+
| `chromaticAberration` | `amount 0.003` |
|
|
499
|
+
| `scanlines` | `intensity 0.15`, `count 480` |
|
|
500
|
+
| `crt` | `curvature 4.0`, `scanlineIntensity 0.2`, `vignette 0.3` |
|
|
501
|
+
| `grayscale` | — |
|
|
502
|
+
|
|
503
|
+
`bloom` is exported as a class too (`BloomEffect`) if you want to subclass it.
|
|
504
|
+
|
|
505
|
+
### RenderTarget
|
|
506
|
+
|
|
507
|
+
An offscreen framebuffer backed by a color texture (and an optional depth buffer).
|
|
508
|
+
Used internally by the post-processor, but useful on its own for minimaps, mirrors,
|
|
509
|
+
or picture-in-picture.
|
|
510
|
+
|
|
511
|
+
```javascript
|
|
512
|
+
import { RenderTarget } from "emeraldengine";
|
|
513
|
+
|
|
514
|
+
const rt = new RenderTarget(512, 512, { depth: false, pixelart: false });
|
|
515
|
+
rt.bind(); // binds the FBO and sets the viewport to its size
|
|
516
|
+
// ...draw...
|
|
517
|
+
rt.unbind(); // restore the canvas framebuffer
|
|
518
|
+
// rt.texture now holds the rendered image (a WebGLTexture)
|
|
519
|
+
rt.resize(1024, 1024); // reallocates only if the size changed
|
|
520
|
+
rt.dispose(); // free GL resources
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Material (custom shaders)
|
|
524
|
+
|
|
525
|
+
A `Material` replaces a Drawable's fragment shader while reusing the engine's
|
|
526
|
+
standard vertex shader, so transforms, the camera, and instancing keep working.
|
|
527
|
+
Your fragment program automatically has `vTexCoord`, `vFragPos`, `vInstanceColor`,
|
|
528
|
+
`uSampler`, `uColor`, `uOpacity`, and `uTime` — **don't redeclare them**; declare
|
|
529
|
+
any extra uniforms and push values with `set(name, value)`.
|
|
530
|
+
|
|
531
|
+
```javascript
|
|
532
|
+
import { Material, Square2D } from "emeraldengine";
|
|
533
|
+
|
|
534
|
+
const dissolve = new Material(
|
|
535
|
+
`
|
|
536
|
+
uniform float uAmount;
|
|
537
|
+
void main() {
|
|
538
|
+
vec4 c = texture2D(uSampler, vTexCoord);
|
|
539
|
+
if (c.a < uAmount) discard;
|
|
540
|
+
gl_FragColor = c * uColor * uOpacity;
|
|
541
|
+
}
|
|
542
|
+
`,
|
|
543
|
+
{ uniforms: { uAmount: 0.0 } }
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
const shape = new Square2D();
|
|
547
|
+
shape.setMaterial(dissolve); // any Drawable: Texture, Square2D, Circle2D…
|
|
548
|
+
gameObject.addComponent(shape);
|
|
549
|
+
|
|
550
|
+
// Animate a uniform (numbers, vec2/3/4 arrays, or functions are accepted):
|
|
551
|
+
dissolve.set("uAmount", 0.5);
|
|
552
|
+
dissolve.set("uPulse", () => 0.5 + 0.5 * Math.sin(performance.now() / 300));
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
A pulsing glow material (as used by the race example's finish line):
|
|
556
|
+
|
|
557
|
+
```javascript
|
|
558
|
+
const glow = new Material(`
|
|
559
|
+
void main() {
|
|
560
|
+
float pulse = 0.5 + 0.5 * sin(uTime * 4.0 + vTexCoord.y * 6.2831);
|
|
561
|
+
vec3 col = mix(vec3(0.40, 1.0, 0.70), vec3(1.0, 0.9, 0.4), pulse);
|
|
562
|
+
gl_FragColor = vec4(col, 0.5 * pulse);
|
|
563
|
+
}
|
|
564
|
+
`);
|
|
565
|
+
square.setMaterial(glow);
|
|
566
|
+
square.setBlendMode("additive");
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### SpriteBatch
|
|
570
|
+
|
|
571
|
+
A dynamic batched renderer with its own minimal shader. Instead of one draw call
|
|
572
|
+
per sprite, it accumulates sprites that share a texture into a single interleaved
|
|
573
|
+
buffer and submits them in one `drawElements` call — ideal for many same-atlas
|
|
574
|
+
quads (bullets, tiles, text glyphs).
|
|
575
|
+
|
|
576
|
+
```javascript
|
|
577
|
+
import { SpriteBatch } from "emeraldengine";
|
|
578
|
+
|
|
579
|
+
const batch = new SpriteBatch({ maxQuads: 2000 });
|
|
580
|
+
batch.begin(projectionMatrix, viewMatrix); // gl-matrix mat4 / Float32Array(16)
|
|
581
|
+
for (const e of entities) {
|
|
582
|
+
batch.draw({
|
|
583
|
+
texture: atlasTexture, // a WebGLTexture; changing it flushes the batch
|
|
584
|
+
x: e.x,
|
|
585
|
+
y: e.y,
|
|
586
|
+
w: 32,
|
|
587
|
+
h: 32,
|
|
588
|
+
rotation: e.angle,
|
|
589
|
+
originX: 0.5,
|
|
590
|
+
originY: 0.5,
|
|
591
|
+
u0: e.u0,
|
|
592
|
+
v0: e.v0,
|
|
593
|
+
u1: e.u1,
|
|
594
|
+
v1: e.v1, // UV sub-rect (defaults 0..1)
|
|
595
|
+
r: 1,
|
|
596
|
+
g: 1,
|
|
597
|
+
b: 1,
|
|
598
|
+
a: 1, // per-vertex tint
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
batch.end(); // flushes remaining sprites
|
|
602
|
+
console.log(batch.drawCalls); // GL draw calls emitted this frame
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
## In-Engine UI
|
|
606
|
+
|
|
607
|
+
`UI` is a retained-mode toolkit drawn entirely by the engine — no DOM/HTML overlay.
|
|
608
|
+
Elements are screen-space objects on a dedicated high layer with their own pointer
|
|
609
|
+
and keyboard hit-testing. Positions are **pixels from the viewport center (y up)**,
|
|
610
|
+
either a literal `{x, y}` or a responsive `(viewW, viewH) => ({x, y})` function;
|
|
611
|
+
call `relayout()` after a resize.
|
|
612
|
+
|
|
613
|
+
Pair it with a UI camera so the UI draws over the game and the game cameras skip the
|
|
614
|
+
UI layer:
|
|
615
|
+
|
|
616
|
+
```javascript
|
|
617
|
+
import { UI } from "emeraldengine";
|
|
618
|
+
|
|
619
|
+
const uiCam = UI.createCamera(); // a full-screen camera that renders ONLY UI.LAYER
|
|
620
|
+
gameCamera.ignoreLayer(UI.LAYER); // keep the UI out of the game viewport(s)
|
|
621
|
+
emerald.setCameras([gameCamera, uiCam]); // add the UI camera last
|
|
622
|
+
|
|
623
|
+
const ui = new UI(scene, canvas, { accent: [120, 200, 255] });
|
|
624
|
+
|
|
625
|
+
// Labels (return a handle with setText)
|
|
626
|
+
const score = ui.label(() => ({ x: 0, y: 200 }), "Score: 0", {
|
|
627
|
+
font: "700 30px system-ui, sans-serif",
|
|
628
|
+
color: "#eaf2ff",
|
|
629
|
+
});
|
|
630
|
+
score.setText("Score: 120");
|
|
631
|
+
|
|
632
|
+
// Buttons (panel + centered label, hover highlight, click handler)
|
|
633
|
+
ui.button(() => ({ x: 0, y: 0 }), "START", 240, 56, {
|
|
634
|
+
accent: [120, 220, 160],
|
|
635
|
+
onClick: () => startGame(),
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// Panels and a modal dimmer behind a dialog
|
|
639
|
+
ui.dim(0.6); // full-screen backdrop
|
|
640
|
+
ui.panel(() => ({ x: 0, y: 0 }), 480, 320, { opacity: 0.94 });
|
|
641
|
+
|
|
642
|
+
// Editable single-line text field
|
|
643
|
+
const name = ui.textField(() => ({ x: 0, y: -80 }), 280, 44, {
|
|
644
|
+
placeholder: "Your name",
|
|
645
|
+
maxLength: 16,
|
|
646
|
+
onChange: (v) => console.log(v),
|
|
647
|
+
});
|
|
648
|
+
name.getValue();
|
|
649
|
+
name.setValue("P1");
|
|
650
|
+
|
|
651
|
+
ui.relayout(); // after creating/anchoring or on window resize
|
|
652
|
+
ui.isOver(clientX, clientY); // true if an interactive element is under the pointer
|
|
653
|
+
ui.destroy(); // remove all UI objects + detach listeners
|
|
654
|
+
|
|
655
|
+
UI.LAYER; // 100000 — the default UI render layer
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
## Game Loop & Scene Transitions
|
|
659
|
+
|
|
660
|
+
`emerald.run(update, options)` owns the `requestAnimationFrame` loop for you: it
|
|
661
|
+
computes a clamped delta time, optionally advances a fixed-timestep simulation,
|
|
662
|
+
and calls your `update(dt, alpha)` each frame. `alpha` is the 0..1 interpolation
|
|
663
|
+
factor between fixed steps (1 when no fixed step is configured), so you can render
|
|
664
|
+
smoothly between simulation ticks.
|
|
665
|
+
|
|
666
|
+
```javascript
|
|
667
|
+
const stop = emerald.run(
|
|
668
|
+
(dt, alpha) => {
|
|
669
|
+
world.update(dt); // variable-step game logic
|
|
670
|
+
emerald.drawScene(scene, dt);
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
maxDelta: 0.25, // clamp dt after a tab-switch stall
|
|
674
|
+
fixedStep: 1 / 60, // optional fixed simulation step (0 = off)
|
|
675
|
+
fixedUpdate: (step) => physics.process(step),
|
|
676
|
+
maxSubSteps: 5,
|
|
677
|
+
}
|
|
678
|
+
);
|
|
679
|
+
// later: stop(); // or emerald.stop();
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
Switch scenes behind a fade with `SceneManager.transitionTo` (wired to
|
|
683
|
+
`ScreenEffects`), or drive the fade directly with `ScreenEffects.transition`:
|
|
684
|
+
|
|
685
|
+
```javascript
|
|
686
|
+
import { SceneManager, ScreenEffects, Color } from "emeraldengine";
|
|
687
|
+
|
|
688
|
+
const fx = new ScreenEffects(overlayScene); // update()'d each frame by your loop
|
|
689
|
+
|
|
690
|
+
await SceneManager.transitionTo(nextScene, {
|
|
691
|
+
screenEffects: fx,
|
|
692
|
+
duration: 0.4,
|
|
693
|
+
color: new Color(0, 0, 0, 255),
|
|
694
|
+
onSwap: (scene) => buildLevel(scene), // runs while the screen is covered
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// Or lower-level: fade out -> swap -> fade in
|
|
698
|
+
await fx.transition(() => swapScenes(), { duration: 0.4 });
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
## ScreenEffects (transitions)
|
|
702
|
+
|
|
703
|
+
Full-screen camera transitions drawn with the engine's own screen-space quads
|
|
704
|
+
(no CSS overlay), so they survive resolution changes, post-processing and
|
|
705
|
+
split-screen. `fadeOut`/`fadeIn`/`flash` return promises. Call `update(dt)` each
|
|
706
|
+
frame before `drawScene`.
|
|
707
|
+
|
|
708
|
+
```javascript
|
|
709
|
+
import { ScreenEffects, Color } from "emeraldengine";
|
|
710
|
+
|
|
711
|
+
const fx = new ScreenEffects(scene, { layer: 100000, size: 5000 });
|
|
712
|
+
|
|
713
|
+
await fx.fadeOut(0.4, new Color(0, 0, 0, 255)); // fade to black
|
|
714
|
+
loadNextLevel();
|
|
715
|
+
await fx.fadeIn(0.4); // fade back in
|
|
716
|
+
|
|
717
|
+
fx.flash(new Color(255, 255, 255, 255), 0.25); // quick screen flash
|
|
718
|
+
fx.setLetterbox(80); // animate cinematic bars to 80px; pass 0 to retract
|
|
719
|
+
|
|
720
|
+
// in the loop:
|
|
721
|
+
fx.update(dt);
|
|
722
|
+
// when leaving the scene:
|
|
723
|
+
fx.destroy();
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
## Coroutines
|
|
727
|
+
|
|
728
|
+
Generator-based sequencing layered on the same per-frame delta the rest of the
|
|
729
|
+
engine uses. It's driven automatically from `Emerald.drawScene` (so coroutines
|
|
730
|
+
honor pause/slow-mo via `Time.timeScale`).
|
|
731
|
+
|
|
732
|
+
```javascript
|
|
733
|
+
import { Coroutine } from "emeraldengine";
|
|
734
|
+
|
|
735
|
+
const handle = Coroutine.start(function* () {
|
|
736
|
+
big.setText("3");
|
|
737
|
+
audio.beep();
|
|
738
|
+
yield 0.7; // wait 0.7 seconds
|
|
739
|
+
big.setText("2");
|
|
740
|
+
audio.beep();
|
|
741
|
+
yield 0.7;
|
|
742
|
+
yield Coroutine.waitFrames(3); // wait 3 frames
|
|
743
|
+
yield Coroutine.waitUntil(() => player.ready); // block until predicate is truthy
|
|
744
|
+
yield Coroutine.waitWhile(() => paused); // block while predicate is truthy
|
|
745
|
+
yield fetch("/level.json"); // await any promise
|
|
746
|
+
yield Coroutine.tween(0, 1, 0.5, (v) => (door.openAmount = v)); // drive a value
|
|
747
|
+
yield otherCoroutineHandle; // wait for a nested coroutine
|
|
748
|
+
start();
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
handle.cancel(); // stop it early
|
|
752
|
+
handle.isRunning(); // boolean
|
|
753
|
+
await handle.promise; // resolves when the coroutine finishes or is cancelled
|
|
754
|
+
|
|
755
|
+
Coroutine.count(); // number of running coroutines
|
|
756
|
+
Coroutine.clearAll(); // cancel + remove every coroutine (e.g. on scene exit)
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
## Gamepads & Controllers
|
|
760
|
+
|
|
761
|
+
Full controller support is built into `InputManager`: analog sticks/triggers,
|
|
762
|
+
**semantic button names** that resolve through each pad's mapping, rumble,
|
|
763
|
+
connect/disconnect events, and a registry for non-standard controllers. Gamepad
|
|
764
|
+
input flows through the same `isDown` / `justPressed` / `getAxis` machinery as the
|
|
765
|
+
keyboard, so a token like `"pad:0:south"` works anywhere a key token does
|
|
766
|
+
(including `justPressed` edge detection).
|
|
767
|
+
|
|
768
|
+
### Button tokens & names
|
|
769
|
+
|
|
770
|
+
`pad:<i>:<name>` targets pad index `<i>`. Names resolve through the pad's mapping,
|
|
771
|
+
so `south` is always the bottom face button whether the pad reports Xbox or
|
|
772
|
+
PlayStation ordering:
|
|
773
|
+
|
|
774
|
+
| Tokens | Buttons |
|
|
775
|
+
| ------------------------------------------------------------------------------------- | -------------------------------------- |
|
|
776
|
+
| `south`/`a`/`cross`, `east`/`b`/`circle`, `west`/`x`/`square`, `north`/`y`/`triangle` | face buttons |
|
|
777
|
+
| `l1`/`lb`, `r1`/`rb`, `l2`/`lt`, `r2`/`rt` | shoulders / triggers |
|
|
778
|
+
| `select`/`back`/`view`/`share`, `start`/`menu`/`options`, `guide`/`home` | center |
|
|
779
|
+
| `l3`/`leftStick`, `r3`/`rightStick` | stick clicks |
|
|
780
|
+
| `dpadUp`/`up`, `dpadDown`/`down`, `dpadLeft`/`left`, `dpadRight`/`right` | d-pad |
|
|
781
|
+
| `pad:<i>:<n>` | raw button index (mapping-independent) |
|
|
782
|
+
| `pad:<i>:axis<n>+` / `axis<n>-` | analog axis past the deadzone |
|
|
783
|
+
| `pad:<i>:leftStickUp/Down/Left/Right`, `rightStick...` | analog stick as a d-pad |
|
|
784
|
+
|
|
785
|
+
### Analog feel: radial deadzone, rescaling, response curve
|
|
786
|
+
|
|
787
|
+
`getGamepadStick` applies a **radial** deadzone (on the stick's distance from
|
|
788
|
+
center, not per axis), so diagonals aren't clipped square and direction is
|
|
789
|
+
preserved exactly. Values are **rescaled** — 0 at the deadzone edge, 1 at full
|
|
790
|
+
deflection — so there's no jump at the threshold and the full range stays
|
|
791
|
+
reachable. An optional response curve shapes the middle:
|
|
792
|
+
|
|
793
|
+
```javascript
|
|
794
|
+
input.setGamepadDeadzone(0.25);
|
|
795
|
+
input.setGamepadCurve(2); // finer control near center (great for camera sticks)
|
|
796
|
+
const { x, y, magnitude, angle } = input.getGamepadStick("left", 0, { invertY: true });
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
Semantic stick-direction tokens (`pad:0:leftStickUp`...) fire once the rescaled
|
|
800
|
+
value passes `stickPressThreshold` (default 0.5, configurable), so drift never
|
|
801
|
+
triggers menus. Common **DirectInput** pads — Logitech Dual Action, generic
|
|
802
|
+
Twin-USB PS2 adapters, 8BitDo in D-input mode — are recognized out of the box;
|
|
803
|
+
`InputManager.registerGamepadMapping()` overrides always win.
|
|
804
|
+
|
|
805
|
+
```javascript
|
|
806
|
+
input.mapAction("jump", ["Space", "pad:0:south"]);
|
|
807
|
+
input.mapAction("dash", ["Shift", "pad:0:west", "pad:0:r2"]); // X or right trigger
|
|
808
|
+
if (input.justPressed("jump")) player.jump();
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
### Analog sticks, triggers & rumble
|
|
812
|
+
|
|
813
|
+
```javascript
|
|
814
|
+
const { x, y } = input.getGamepadStick("left"); // deadzoned -1..1
|
|
815
|
+
const aim = input.getGamepadStick("right");
|
|
816
|
+
const t = input.getGamepadTrigger("right"); // 0..1
|
|
817
|
+
input.getGamepadButton("south").pressed; // also .value, .index
|
|
818
|
+
input.setGamepadDeadzone(0.25);
|
|
819
|
+
|
|
820
|
+
input.rumble(0, { duration: 120, strong: 0.6, weak: 0.4 }); // where supported
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
### Connection events & diagnostics
|
|
824
|
+
|
|
825
|
+
```javascript
|
|
826
|
+
input.onGamepadConnected((info) =>
|
|
827
|
+
console.log(info.id, info.mapping, info.buttonCount, info.axesCount)
|
|
828
|
+
);
|
|
829
|
+
input.onGamepadDisconnected((info) => pauseFor(info.index));
|
|
830
|
+
|
|
831
|
+
input.isGamepadConnected(0);
|
|
832
|
+
input.getGamepadInfo(0); // { id, mapping, buttonCount, axesCount, standard }
|
|
833
|
+
input.getPressedButtons(0); // raw indices currently pressed (layout discovery)
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
### Non-standard controllers
|
|
837
|
+
|
|
838
|
+
Most pads (and anything via XInput / Steam Input) report `mapping === "standard"`
|
|
839
|
+
and work out of the box. For a controller that reports a non-standard mapping (its
|
|
840
|
+
raw button indices differ), register a mapping once — it only applies to pads
|
|
841
|
+
whose id matches **and** that aren't already standard, so a correctly-reporting
|
|
842
|
+
pad is never remapped:
|
|
843
|
+
|
|
844
|
+
```javascript
|
|
845
|
+
InputManager.registerGamepadMapping("my-controller-id", {
|
|
846
|
+
buttons: { south: 1, east: 2, west: 0, north: 3, start: 9 },
|
|
847
|
+
});
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
D-pads reported as a hat axis (instead of buttons 12–15) are decoded into the
|
|
851
|
+
`dpad*` tokens automatically.
|
|
852
|
+
|
|
853
|
+
## Per-instance color & tinting
|
|
854
|
+
|
|
855
|
+
`InstancedTexture` now supports an independent RGBA tint per instance (white =
|
|
856
|
+
unchanged, so existing scenes render identically). The tint multiplies the
|
|
857
|
+
texture in the shader.
|
|
858
|
+
|
|
859
|
+
```javascript
|
|
860
|
+
import { Instance, Color } from "emeraldengine";
|
|
861
|
+
|
|
862
|
+
const inst = new Instance("coin", position, scale, rotation, frame);
|
|
863
|
+
instancedTexture.addInstance(inst);
|
|
864
|
+
|
|
865
|
+
// On the Instance directly (Color uses 0..255 channels; raw form is 0..1):
|
|
866
|
+
inst.setColor(new Color(255, 120, 60)); // warm tint
|
|
867
|
+
inst.setColor(1.0, 0.4, 0.2, 1.0); // same, as raw 0..1 RGBA
|
|
868
|
+
|
|
869
|
+
// Or drive it through the InstancedTexture by index:
|
|
870
|
+
instancedTexture.updateInstanceColor(0); // re-read instance 0's tint
|
|
871
|
+
instancedTexture.updateAllInstanceColors(); // re-read every instance's tint
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
Tip: for additive sparkle/coin glows, set the instanced texture's blend mode:
|
|
875
|
+
`instancedTexture.setBlendMode("additive")`.
|
|
876
|
+
|
|
877
|
+
### Per-instance atlas regions (setTexCoords)
|
|
878
|
+
|
|
879
|
+
Normally every instance samples the shared frame grid (`instance.frame`). With
|
|
880
|
+
`setTexCoords` an instance carries its **own UV quad**, so a single
|
|
881
|
+
InstancedTexture — one draw call — can batch tiles from an atlas with margins
|
|
882
|
+
and spacing, apply per-instance flips, or mix arbitrary sprite regions:
|
|
883
|
+
|
|
884
|
+
```javascript
|
|
885
|
+
const inst = new InstancedTexture("atlas.png", count, 0, 0, 1, 1, 0, false, true, false);
|
|
886
|
+
const tile = new Instance("tile", new Vector3(x, y, 0), new Vector2(32, 32), rotation);
|
|
887
|
+
// 8 floats, one vec2 per corner in getFrameTexCoords order: (R,B) (L,B) (R,T) (L,T)
|
|
888
|
+
tile.setTexCoords([right, bottom, left, bottom, right, top, left, top]);
|
|
889
|
+
inst.addInstance(tile);
|
|
890
|
+
inst.setStatic(true); // non-moving batch: matrices upload once
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
This is exactly how the Tile Forge level loader renders a whole layer of
|
|
894
|
+
sliced, rotated, flipped tiles as one draw call. Pass `null` to return an
|
|
895
|
+
instance to the frame grid.
|
|
896
|
+
|
|
897
|
+
## Physics: collision layers & continuous detection
|
|
898
|
+
|
|
899
|
+
### Collision filtering (layers)
|
|
900
|
+
|
|
901
|
+
`CollisionLayers` maps human-readable layer names to the category bits planck uses
|
|
902
|
+
for filtering, so you can express "players collide with ground and enemies, but
|
|
903
|
+
not each other" without juggling bitmasks. Two fixtures collide only when each
|
|
904
|
+
one's category is in the other's mask (symmetric by construction; up to 16
|
|
905
|
+
layers).
|
|
906
|
+
|
|
907
|
+
```javascript
|
|
908
|
+
import { CollisionLayers } from "emeraldengine";
|
|
909
|
+
|
|
910
|
+
CollisionLayers.define("ground", "player", "enemy", "pickup");
|
|
911
|
+
|
|
912
|
+
playerCollider
|
|
913
|
+
.setCategory("player")
|
|
914
|
+
.setCollidesWith(["ground", "enemy", "pickup"]);
|
|
915
|
+
enemyCollider.setCategory("enemy").setCollidesWith(["ground", "player"]); // ignore each other
|
|
916
|
+
|
|
917
|
+
// Or up front, in the collider constructor (8th arg):
|
|
918
|
+
new BoxCollider(body, size, 1, 0.2, 0, false, gameObject, {
|
|
919
|
+
category: "pickup",
|
|
920
|
+
collidesWith: ["player"],
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
// Raw control if you prefer bits:
|
|
924
|
+
collider.setFilter({ category: 0x0004, mask: 0xffff, group: 0 });
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
### Continuous collision detection (CCD)
|
|
928
|
+
|
|
929
|
+
Fast bodies (a dash, a projectile, a hard fall) can move far enough in one physics
|
|
930
|
+
step to tunnel through thin walls. Mark them continuous so planck solves the swept
|
|
931
|
+
path against static geometry instead:
|
|
932
|
+
|
|
933
|
+
```javascript
|
|
934
|
+
projectile.setContinuous(true); // bullet-mode CCD
|
|
935
|
+
projectile.isContinuous(); // boolean
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
Reserve it for the handful of bodies that actually move fast — it costs more per
|
|
939
|
+
step.
|
|
940
|
+
|
|
941
|
+
## RigidBody velocity helpers
|
|
942
|
+
|
|
943
|
+
`RigidBody` gained direct velocity/impulse control in **world (pixel) units** —
|
|
944
|
+
the engine converts to/from physics units internally, so you never touch the
|
|
945
|
+
scale factor.
|
|
946
|
+
|
|
947
|
+
```javascript
|
|
948
|
+
rigidBody.setLinearVelocity(380, 0); // world units per second
|
|
949
|
+
const v = rigidBody.getLinearVelocity(); // { x, y } in world units/sec
|
|
950
|
+
rigidBody.applyImpulse(0, 900); // impulse at the body's center
|
|
951
|
+
rigidBody.setAwake(true); // wake (or sleep) the body
|
|
952
|
+
|
|
953
|
+
// Live world-space position/rotation (kept in sync with the simulation):
|
|
954
|
+
rigidBody.getPosition(); // Vector2 — current position (not the spawn point)
|
|
955
|
+
rigidBody.getInitialPosition(); // Vector2 — the position it was created at
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
These make velocity-driven movement (player controllers, knockback, dashes)
|
|
959
|
+
straightforward without reaching into the underlying planck body.
|
|
960
|
+
|
|
961
|
+
## Tilemap colliders & auto-tiling
|
|
962
|
+
|
|
963
|
+
A `Tilemap` can now generate physics colliders from its map and auto-pick tile
|
|
964
|
+
frames from a solidity grid.
|
|
965
|
+
|
|
966
|
+
```javascript
|
|
967
|
+
// 1) Build solid colliders from the current map.
|
|
968
|
+
// Solid cells are merged greedily into horizontal runs, so a row of N tiles
|
|
969
|
+
// becomes ONE static box collider instead of N.
|
|
970
|
+
map.setMap(grid, { originX: 0, originY: 0, flipY: true });
|
|
971
|
+
map.buildColliders(physics, {
|
|
972
|
+
isSolid: (frame) => frame != null && frame >= 0, // default
|
|
973
|
+
friction: 0.2,
|
|
974
|
+
restitution: 0,
|
|
975
|
+
density: 0,
|
|
976
|
+
ownerObject: map.gameObject, // collision callbacks resolve back to this
|
|
977
|
+
});
|
|
978
|
+
map.clearColliders(); // destroy the generated bodies (e.g. before a rebuild)
|
|
979
|
+
|
|
980
|
+
// 2) Auto-tiling: turn a boolean solidity grid into frame indices using a
|
|
981
|
+
// 4-bit edge bitmask (up|right|down|left = bits 1,2,4,8). Empty cells -> -1.
|
|
982
|
+
const frames = Tilemap.computeAutoTile(solidGrid, {
|
|
983
|
+
frames: lookup16, // optional length-16 mask -> frame map matching your sheet
|
|
984
|
+
base: 0, // added to every solid frame when no lookup is given
|
|
985
|
+
edgesSolid: true, // treat out-of-bounds as solid
|
|
986
|
+
});
|
|
987
|
+
map.setAutoTiledMap(solidGrid, { base: 0, originX: 0, originY: 0 }); // compute + setMap
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
## Camera follow deadzone
|
|
991
|
+
|
|
992
|
+
`CameraController` now supports a centered follow deadzone: the camera only scrolls
|
|
993
|
+
once the target leaves a box around the current focus, so small movements don't jitter
|
|
994
|
+
the view. Chainable with the existing follow/bounds/shake API.
|
|
995
|
+
|
|
996
|
+
```javascript
|
|
997
|
+
const ctrl = new CameraController(camera);
|
|
998
|
+
ctrl
|
|
999
|
+
.follow(player.gameObject, 0.12)
|
|
1000
|
+
.setDeadzone(90, 60) // half-extents in world units; 0/0 or null disables
|
|
1001
|
+
.setBounds(280, cy, finishX + 200, cy);
|
|
1002
|
+
// each frame: ctrl.update(dt);
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
## ParticleEmitter (pooled sprites)
|
|
1006
|
+
|
|
1007
|
+
`ParticleEmitter` is a reliable, allocation-free particle system built from a
|
|
1008
|
+
fixed pool of ordinary textured GameObjects. Every live particle's transform, tint
|
|
1009
|
+
and opacity are driven by hand each frame — there's no instanced-draw buffer
|
|
1010
|
+
lifecycle to desync, so it keeps rendering for the whole session. Spawn with
|
|
1011
|
+
`burst(n, cfg)` / `emit(cfg)` and advance with `update(dt)`; every `cfg` field is
|
|
1012
|
+
optional.
|
|
1013
|
+
|
|
1014
|
+
```javascript
|
|
1015
|
+
import { ParticleEmitter } from "emeraldengine";
|
|
1016
|
+
|
|
1017
|
+
const fx = new ParticleEmitter(scene, {
|
|
1018
|
+
texture: "spark.png", // sprite source (data URL / path / image)
|
|
1019
|
+
capacity: 256, // pool size (max live particles)
|
|
1020
|
+
layer: 50,
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
fx.burst(12, {
|
|
1024
|
+
x: 200,
|
|
1025
|
+
y: 120,
|
|
1026
|
+
dir: -Math.PI / 2,
|
|
1027
|
+
spread: Math.PI,
|
|
1028
|
+
speed: 180, // emission cone
|
|
1029
|
+
gx: 0,
|
|
1030
|
+
gy: -300,
|
|
1031
|
+
drag: 2, // forces
|
|
1032
|
+
life: 0.4,
|
|
1033
|
+
size: 8,
|
|
1034
|
+
sFrom: 1,
|
|
1035
|
+
sTo: 0.1, // scale over life
|
|
1036
|
+
aFrom: 0.7,
|
|
1037
|
+
aTo: 0, // alpha over life
|
|
1038
|
+
cr: 255,
|
|
1039
|
+
cg: 220,
|
|
1040
|
+
cb: 120,
|
|
1041
|
+
additive: true, // tint + blend
|
|
1042
|
+
rotSpeed: 6,
|
|
1043
|
+
shape: "ring",
|
|
1044
|
+
radius: 12, // "point" | "ring" | "circle" | "box"
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
// in the loop:
|
|
1048
|
+
fx.update(dt);
|
|
1049
|
+
fx.activeCount; // live particles
|
|
1050
|
+
fx.reset(); // kill all immediately
|
|
1051
|
+
fx.destroy(); // remove pooled objects from the scene
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
This is the go-to for one-off bursts (dust, sparkles, confetti, hit effects). For
|
|
1055
|
+
the curve-driven, instanced system see [Advanced Particles](#advanced-particles)
|
|
1056
|
+
below.
|
|
1057
|
+
|
|
1058
|
+
## Advanced Particles
|
|
1059
|
+
|
|
1060
|
+
`ParticleSettings` gained emitter shapes and over-lifetime curves, plus per-particle
|
|
1061
|
+
`rotationSpeed` and `drag`. These layer on top of the existing particle fields.
|
|
1062
|
+
|
|
1063
|
+
```javascript
|
|
1064
|
+
import { ParticleSettings, Vector2, Color } from "emeraldengine";
|
|
1065
|
+
|
|
1066
|
+
const settings = new ParticleSettings({
|
|
1067
|
+
lifetime: 1.0,
|
|
1068
|
+
amount: 24,
|
|
1069
|
+
velocity: new Vector2(0, 260),
|
|
1070
|
+
gravity: new Vector2(0, -300),
|
|
1071
|
+
|
|
1072
|
+
// Emitter shape — where new particles spawn relative to the emit point:
|
|
1073
|
+
// "point" | "circle" | "ring" | "box" | "cone" (default)
|
|
1074
|
+
shape: "ring",
|
|
1075
|
+
shapeRadius: 24, // used by circle/ring
|
|
1076
|
+
shapeSize: new Vector2(40, 10), // used by box
|
|
1077
|
+
|
|
1078
|
+
// Over-lifetime curves ({ from, to } interpolated by normalized age):
|
|
1079
|
+
scaleOverLife: { from: 1.4, to: 0.0 }, // size multiplier
|
|
1080
|
+
alphaOverLife: { from: 1.0, to: 0.0 }, // opacity
|
|
1081
|
+
colorOverLife: {
|
|
1082
|
+
// 0..255 channels
|
|
1083
|
+
from: new Color(255, 240, 180),
|
|
1084
|
+
to: new Color(255, 90, 60),
|
|
1085
|
+
},
|
|
1086
|
+
|
|
1087
|
+
rotationSpeed: Math.PI, // radians/sec per particle
|
|
1088
|
+
drag: 1.2, // velocity damping per second (0 = none)
|
|
1089
|
+
});
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
## Positional (spatial) Audio
|
|
1093
|
+
|
|
1094
|
+
`AudioManager` can attenuate and pan sounds based on a listener position. It uses
|
|
1095
|
+
the Web Audio `StereoPanner` when available and falls back to volume-only panning
|
|
1096
|
+
otherwise.
|
|
1097
|
+
|
|
1098
|
+
```javascript
|
|
1099
|
+
audio.setListener(player.x, player.y); // usually the camera/player each frame
|
|
1100
|
+
audio.setSpatialRange(100, 800); // full volume <100px, silent >800px
|
|
1101
|
+
|
|
1102
|
+
audio.playSpatial("explosion", { x: 1200, y: 50 }); // one-shot, positioned
|
|
1103
|
+
|
|
1104
|
+
// Pure helper (also used internally) — handy for custom routing/tests:
|
|
1105
|
+
const { volume, pan, distance } = audio.computeSpatial({ x, y });
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
## Audio buses & fades
|
|
1109
|
+
|
|
1110
|
+
Every sound belongs to a named **mix bus** — `"music"` and `"sfx"` exist by
|
|
1111
|
+
default (new sounds land on `"sfx"`), and any name you use creates a bus on the
|
|
1112
|
+
fly. Effective volume is `master × bus × sound × fade`, so one slider mutes all
|
|
1113
|
+
music without touching the SFX:
|
|
1114
|
+
|
|
1115
|
+
```javascript
|
|
1116
|
+
audio.add("theme.mp3", "theme", { bus: "music", loop: true });
|
|
1117
|
+
audio.add("jump.wav", "jump"); // default bus: "sfx"
|
|
1118
|
+
|
|
1119
|
+
audio.setBusVolume("music", 0.5); // the settings-menu "music volume" slider
|
|
1120
|
+
audio.setBusVolume("sfx", 0.8);
|
|
1121
|
+
audio.setSoundBus("thunder", "ambience"); // move a sound, creating the bus
|
|
1122
|
+
audio.getBusVolume("music"); // 0.5
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
Fades run on the manager's clock (self-driven via rAF by default; pass
|
|
1126
|
+
`{ autoTick: false }` and call `audio.update(dt)` yourself to tie them to the
|
|
1127
|
+
game loop):
|
|
1128
|
+
|
|
1129
|
+
```javascript
|
|
1130
|
+
audio.fadeIn("theme", 1.5); // play from silence to full over 1.5s
|
|
1131
|
+
audio.fadeOut("theme", 2.0); // fade to silence, then stop
|
|
1132
|
+
audio.fadeTo("theme", 0.2, 0.5); // duck under dialogue
|
|
1133
|
+
audio.crossfade("theme", "boss", 2.0); // level -> boss music, one call
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
## AssetManager (loading)
|
|
1137
|
+
|
|
1138
|
+
One async loader for everything a game needs at startup — images/textures, audio,
|
|
1139
|
+
JSON, text, and web fonts — with deduplication and aggregate progress for a
|
|
1140
|
+
loading bar. Images are routed through `TextureManager`, so the GL upload cache is
|
|
1141
|
+
shared with the rest of the engine.
|
|
1142
|
+
|
|
1143
|
+
```javascript
|
|
1144
|
+
import { AssetManager } from "emeraldengine";
|
|
1145
|
+
|
|
1146
|
+
const assets = new AssetManager();
|
|
1147
|
+
assets
|
|
1148
|
+
.image("player", "player.png", { pixelart: true })
|
|
1149
|
+
.audio("jump", "jump.wav")
|
|
1150
|
+
.json("level1", "levels/1.json")
|
|
1151
|
+
.text("credits", "credits.txt")
|
|
1152
|
+
.font("Press Start 2P", "fonts/press-start.woff2");
|
|
1153
|
+
|
|
1154
|
+
assets.onProgress((loaded, total) => bar.set(loaded / total));
|
|
1155
|
+
await assets.load({ continueOnError: false }); // rejects on a failed asset unless true
|
|
1156
|
+
|
|
1157
|
+
assets.get("player"); // HTMLImageElement
|
|
1158
|
+
assets.get("level1"); // parsed JSON
|
|
1159
|
+
assets.has("jump"); // boolean
|
|
1160
|
+
assets.progress(); // 0..1
|
|
1161
|
+
await assets.getTexture("player"); // { texture, width, height } from the GL cache
|
|
1162
|
+
assets.clear();
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
## Asset importers (Tiled & Aseprite)
|
|
1166
|
+
|
|
1167
|
+
Import maps from [Tiled](https://www.mapeditor.org) and sprite-sheet animations
|
|
1168
|
+
from [Aseprite](https://www.aseprite.org). Both are pure parsers — hand them the
|
|
1169
|
+
already-parsed JSON (load it with `AssetManager.json` or `fetch`).
|
|
1170
|
+
|
|
1171
|
+
### Tiled maps
|
|
1172
|
+
|
|
1173
|
+
```javascript
|
|
1174
|
+
import { TiledMap } from "emeraldengine";
|
|
1175
|
+
|
|
1176
|
+
// Build a ready-to-render Tilemap from a Tiled JSON map + its tile sheet.
|
|
1177
|
+
const map = TiledMap.toTilemap(mapJson, "tiles.png", { layer: "ground" });
|
|
1178
|
+
scene.add(map.gameObject);
|
|
1179
|
+
map.buildColliders(physics);
|
|
1180
|
+
|
|
1181
|
+
// Or just the frame grid (for your own Tilemap.setMap call):
|
|
1182
|
+
const grid = TiledMap.toFrameGrid(mapJson, { layer: "ground" });
|
|
1183
|
+
|
|
1184
|
+
// Object layers (spawn points, triggers) as plain data; Tiled `properties` are
|
|
1185
|
+
// flattened into `props`, and flipY converts to a y-up world.
|
|
1186
|
+
const spawns = TiledMap.objects(mapJson, { layer: "spawns", flipY: true });
|
|
1187
|
+
// -> [{ name, type, x, y, width, height, gid, props, ... }]
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
### Aseprite sheets
|
|
1191
|
+
|
|
1192
|
+
```javascript
|
|
1193
|
+
import { Aseprite, Texture, Animator } from "emeraldengine";
|
|
1194
|
+
|
|
1195
|
+
const cfg = Aseprite.spriteConfig(sheetJson); // { frameWidth, frameHeight, framesPerRow, totalFrames }
|
|
1196
|
+
const tex = new Texture(
|
|
1197
|
+
"hero.png",
|
|
1198
|
+
cfg.frameWidth,
|
|
1199
|
+
cfg.frameHeight,
|
|
1200
|
+
cfg.framesPerRow,
|
|
1201
|
+
cfg.totalFrames,
|
|
1202
|
+
0,
|
|
1203
|
+
false
|
|
1204
|
+
);
|
|
1205
|
+
obj.addComponent(tex);
|
|
1206
|
+
|
|
1207
|
+
const anim = new Animator();
|
|
1208
|
+
obj.addComponent(anim);
|
|
1209
|
+
Aseprite.applyTo(anim, sheetJson); // registers a clip per frame-tag
|
|
1210
|
+
anim.play("run");
|
|
1211
|
+
|
|
1212
|
+
// Or inspect the clips yourself (handles forward/reverse/pingpong):
|
|
1213
|
+
Aseprite.toClips(sheetJson); // -> [{ name, frames:[...], speed }]
|
|
1214
|
+
```
|
|
1215
|
+
|
|
1216
|
+
## Networking (NetworkManager + Interpolator)
|
|
1217
|
+
|
|
1218
|
+
A thin, **optional** multiplayer layer over [Colyseus](https://colyseus.io).
|
|
1219
|
+
`colyseus.js` is a peer dependency imported dynamically, so games that don't use
|
|
1220
|
+
networking never load it. It bundles an `Interpolator` for smooth remote entities.
|
|
1221
|
+
|
|
1222
|
+
```javascript
|
|
1223
|
+
import { NetworkManager } from "emeraldengine";
|
|
1224
|
+
|
|
1225
|
+
const net = new NetworkManager({ interpolation: { delay: 0.1 } });
|
|
1226
|
+
await net.connect("wss://my-server:2567"); // dynamically imports colyseus.js
|
|
1227
|
+
const room = await net.join("arena", { name: "P1" });
|
|
1228
|
+
|
|
1229
|
+
net.onMessage("hit", (msg) => applyHit(msg));
|
|
1230
|
+
net.onStateChange((state) => {
|
|
1231
|
+
for (const [id, p] of state.players) net.interpolator.push(id, p, net.now());
|
|
1232
|
+
});
|
|
1233
|
+
net.onLeave((code) => showDisconnected(code));
|
|
1234
|
+
|
|
1235
|
+
net.send("move", { dir: 1 });
|
|
1236
|
+
net.sessionId; // this client's id
|
|
1237
|
+
await net.leave();
|
|
1238
|
+
|
|
1239
|
+
// each frame, render remote entities "in the past" for smoothness:
|
|
1240
|
+
const pos = net.interpolator.sample(remoteId, net.now()); // { x, y } | null
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
`Interpolator` is also exported standalone and is pure (no network/DOM), so you can
|
|
1244
|
+
use it with any transport or in tests:
|
|
1245
|
+
|
|
1246
|
+
```javascript
|
|
1247
|
+
import { Interpolator } from "emeraldengine";
|
|
1248
|
+
|
|
1249
|
+
const interp = new Interpolator({ delay: 0.1, maxBuffer: 60 });
|
|
1250
|
+
interp.push(entityId, { x, y }, serverTimeSeconds); // on each authoritative update
|
|
1251
|
+
const smoothed = interp.sample(entityId, nowSeconds); // each frame
|
|
1252
|
+
interp.prune(nowSeconds); // bound memory for long-lived entities
|
|
1253
|
+
interp.remove(entityId); // when an entity leaves
|
|
1254
|
+
interp.clear();
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
## DebugOverlay (upgraded)
|
|
1258
|
+
|
|
1259
|
+
The overlay now shows a frame-time sparkline with min/avg/max milliseconds and heap
|
|
1260
|
+
usage, accepts custom metric rows, and can visualize physics colliders.
|
|
1261
|
+
|
|
1262
|
+
```javascript
|
|
1263
|
+
import { DebugOverlay } from "emeraldengine";
|
|
1264
|
+
|
|
1265
|
+
const debug = new DebugOverlay();
|
|
1266
|
+
debug.setVisible(true); // toggle (e.g. bind to F3)
|
|
1267
|
+
debug.setMetric("enemies", enemies.length); // add/refresh a custom row
|
|
1268
|
+
debug.showColliders(scene, true); // overlay collider shapes for the scene
|
|
1269
|
+
// after drawScene each frame:
|
|
1270
|
+
debug.update(emerald, scene); // FPS / frame-time graph / objects / cameras
|
|
1271
|
+
debug.destroy();
|
|
1272
|
+
```
|
|
1273
|
+
|
|
1274
|
+
## Storage (versioned saves)
|
|
1275
|
+
|
|
1276
|
+
`Storage.save`/`Storage.load` wrap your data in a versioned envelope
|
|
1277
|
+
(`{ v, t, data }`) with an automatic `.bak` mirror, so saves survive both
|
|
1278
|
+
corrupted writes (a torn write recovers from backup) and **schema changes**
|
|
1279
|
+
(old saves migrate forward instead of being discarded):
|
|
1280
|
+
|
|
1281
|
+
```javascript
|
|
1282
|
+
import { Storage } from "emeraldengine";
|
|
1283
|
+
|
|
1284
|
+
// Write: version + timestamp envelope, plus a .bak backup by default.
|
|
1285
|
+
Storage.save("profile", { level: 3, coins: 120 }, { version: 2 });
|
|
1286
|
+
|
|
1287
|
+
// Read: falls back, recovers from backup, and migrates old versions.
|
|
1288
|
+
const profile = Storage.load("profile", {
|
|
1289
|
+
version: 2,
|
|
1290
|
+
fallback: { level: 1, coins: 0 },
|
|
1291
|
+
migrate: (old, fromVersion) => {
|
|
1292
|
+
// v1 saves had no coins field — upgrade them instead of losing progress
|
|
1293
|
+
return { ...old, coins: old.coins ?? 0 };
|
|
1294
|
+
},
|
|
1295
|
+
// rewrite: true (default) re-saves migrated data in the new format
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
Storage.hasSave("profile"); // true
|
|
1299
|
+
Storage.removeSave("profile"); // deletes the save AND its backup
|
|
1300
|
+
```
|
|
1301
|
+
|
|
1302
|
+
Plain pre-versioning values load as version 0, so adopting the envelope on an
|
|
1303
|
+
existing game is safe. (For whole-scene snapshots, see
|
|
1304
|
+
[Serializer](#serializer-saveload-scenes); for raw key/value access,
|
|
1305
|
+
`Storage.saveToLocalStorage` / `readFromLocalStorage` still exist.)
|
|
1306
|
+
|
|
1307
|
+
## EmeraldDB (IndexedDB saves)
|
|
1308
|
+
|
|
1309
|
+
`Storage` lives on localStorage, which caps out around **5MB** — plenty for
|
|
1310
|
+
settings and high scores, not for a big persistent world (every tile's state,
|
|
1311
|
+
chests, NPC relationships...). `EmeraldDB` is the async, big-world companion:
|
|
1312
|
+
same versioned envelope, `.bak` backup, and migration semantics, backed by
|
|
1313
|
+
IndexedDB — effectively unlimited, and values are **structured-cloned** (no
|
|
1314
|
+
JSON round-trip), so Maps, Sets, Dates, and typed arrays save as-is and large
|
|
1315
|
+
saves stay fast.
|
|
1316
|
+
|
|
1317
|
+
```javascript
|
|
1318
|
+
import { EmeraldDB } from "emeraldengine";
|
|
1319
|
+
|
|
1320
|
+
// Versioned world save — mirrors Storage.save/load, but async:
|
|
1321
|
+
await EmeraldDB.save("world", world, { version: 3 });
|
|
1322
|
+
const world = await EmeraldDB.load("world", {
|
|
1323
|
+
version: 3,
|
|
1324
|
+
fallback: makeNewWorld(),
|
|
1325
|
+
migrate: (old, fromVersion) => upgradeWorld(old, fromVersion),
|
|
1326
|
+
});
|
|
1327
|
+
await EmeraldDB.hasSave("world"); // true (checks the .bak too)
|
|
1328
|
+
await EmeraldDB.removeSave("world"); // deletes save + backup
|
|
1329
|
+
|
|
1330
|
+
// Plain async key/value (no envelope):
|
|
1331
|
+
await EmeraldDB.set("settings", { volume: 0.8, keybinds: new Map() });
|
|
1332
|
+
const settings = await EmeraldDB.get("settings", {});
|
|
1333
|
+
await EmeraldDB.keys(); // every key in the store
|
|
1334
|
+
|
|
1335
|
+
// Optional setup:
|
|
1336
|
+
EmeraldDB.configure({ name: "my-game", store: "saves" }); // before first use
|
|
1337
|
+
EmeraldDB.isSupported(); // feature-detect (falls back to Storage if false)
|
|
1338
|
+
await EmeraldDB.importFromStorage("profile"); // one-time upgrade of an old localStorage save
|
|
1339
|
+
```
|
|
1340
|
+
|
|
1341
|
+
The backup write happens **in the same IndexedDB transaction** as the save, so
|
|
1342
|
+
a crash mid-write can never leave you with both copies torn. Rule of thumb:
|
|
1343
|
+
`Storage` for small synchronous bits (settings, best times), `EmeraldDB` for
|
|
1344
|
+
the world.
|
|
1345
|
+
|
|
1346
|
+
## Resolution independence (design resolution)
|
|
1347
|
+
|
|
1348
|
+
Author your game at one fixed resolution and let the engine scale it to any
|
|
1349
|
+
screen. The world renders at the design size and is fitted per mode; input
|
|
1350
|
+
helpers convert back, so gameplay code never sees the difference:
|
|
1351
|
+
|
|
1352
|
+
```javascript
|
|
1353
|
+
// Design at 960x540, letterboxed onto whatever screen the player has:
|
|
1354
|
+
emerald.setDesignResolution(960, 540, "fit");
|
|
1355
|
+
|
|
1356
|
+
// Modes:
|
|
1357
|
+
// "fit" letterbox — whole design visible, bars if aspect differs
|
|
1358
|
+
// "fill" cover — fills the screen, crops the overflow
|
|
1359
|
+
// "stretch" distorts to fill exactly (no bars, no crop)
|
|
1360
|
+
// "pixel" integer scaling — crisp for pixel art
|
|
1361
|
+
emerald.clearDesignResolution(); // back to 1:1 CSS pixels
|
|
1362
|
+
|
|
1363
|
+
// Mouse/touch coordinates -> world space (accounts for the design scale,
|
|
1364
|
+
// letterbox offset, camera zoom/position, and DPR):
|
|
1365
|
+
const world = emerald.screenToWorld(input.mouse.x, input.mouse.y);
|
|
1366
|
+
```
|
|
1367
|
+
|
|
1368
|
+
## Auto-pause & lifecycle
|
|
1369
|
+
|
|
1370
|
+
`run()` pauses the loop when the tab is hidden (stops audio-desync, timer
|
|
1371
|
+
pileups, and giant delta-time spikes on return). Hooks let you pause music or
|
|
1372
|
+
show an overlay; you can also pause manually:
|
|
1373
|
+
|
|
1374
|
+
```javascript
|
|
1375
|
+
emerald.run(update, {
|
|
1376
|
+
pauseOnBlur: true, // default: pause when the tab is hidden
|
|
1377
|
+
pauseOnWindowBlur: false, // stricter: also pause when the window loses focus
|
|
1378
|
+
onPause: () => audio.setMasterVolume(0),
|
|
1379
|
+
onResume: () => audio.setMasterVolume(1),
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
emerald.pause(); // e.g. from your own pause menu
|
|
1383
|
+
emerald.resume();
|
|
1384
|
+
```
|
|
1385
|
+
|
|
1386
|
+
The first `dt` after resuming is clamped (`maxDelta`, default 0.25s), so
|
|
1387
|
+
physics never explodes after a long background stint.
|
|
1388
|
+
|
|
1389
|
+
## Production hardening (resource lifecycle, context loss, render stats)
|
|
1390
|
+
|
|
1391
|
+
### Freeing GPU memory
|
|
1392
|
+
|
|
1393
|
+
Removing an object from a scene keeps its GPU resources alive so it can be
|
|
1394
|
+
re-added. When something is gone for good, dispose it — shared textures are
|
|
1395
|
+
reference-counted and freed when their last user disposes:
|
|
1396
|
+
|
|
1397
|
+
```javascript
|
|
1398
|
+
scene.remove(enemy, { dispose: true }); // buffers + texture reference freed
|
|
1399
|
+
gameObject.destroy(); // same, plus physics bodies + Behaviour.onDestroy
|
|
1400
|
+
scene.dispose(); // tear down an entire level/screen
|
|
1401
|
+
drawable.dispose(); // lowest level, safe to call twice
|
|
1402
|
+
```
|
|
1403
|
+
|
|
1404
|
+
### WebGL context loss
|
|
1405
|
+
|
|
1406
|
+
Lost contexts (mobile tab switches, GPU resets, laptops waking) are survived
|
|
1407
|
+
automatically: rendering pauses on loss, and on restore the engine recompiles
|
|
1408
|
+
shaders, re-uploads every cached texture from the surviving image cache,
|
|
1409
|
+
rebuilds all drawable buffers, custom `Material`s, post effects, and render
|
|
1410
|
+
targets, then resumes. Optional hooks:
|
|
1411
|
+
|
|
1412
|
+
```javascript
|
|
1413
|
+
emerald.onContextLost(() => overlay.show("Recovering graphics..."));
|
|
1414
|
+
emerald.onContextRestored(() => overlay.hide());
|
|
1415
|
+
```
|
|
1416
|
+
|
|
1417
|
+
### Render stats
|
|
1418
|
+
|
|
1419
|
+
Every draw site reports into per-frame counters, shown automatically by
|
|
1420
|
+
`DebugOverlay` (`draws` / `quads` / `binds`) and readable in code:
|
|
1421
|
+
|
|
1422
|
+
```javascript
|
|
1423
|
+
const { drawCalls, quads, textureBinds } = emerald.getRenderStats();
|
|
1424
|
+
```
|
|
1425
|
+
|
|
1426
|
+
Draw calls growing with level size means something isn't batched — use
|
|
1427
|
+
`Tilemap`, `SpriteBatch`, or the level loader's instanced tile path.
|
|
1428
|
+
|
|
1429
|
+
### Pixel-perfect rendering
|
|
1430
|
+
|
|
1431
|
+
Spritesheet frame UVs are inset half a texel everywhere, so frames never bleed
|
|
1432
|
+
into neighboring cells (the classic "white seams between tiles" artifact). For
|
|
1433
|
+
pixel-art games also snap the camera to whole pixels:
|
|
1434
|
+
|
|
1435
|
+
```javascript
|
|
1436
|
+
emerald.camera.setPixelSnap(true); // rendered position rounds; stored position stays smooth
|
|
1437
|
+
```
|
|
1438
|
+
|
|
1439
|
+
## Usage
|
|
1440
|
+
|
|
1441
|
+
### Basic Setup
|
|
1442
|
+
|
|
1443
|
+
```javascript
|
|
1444
|
+
import { Emerald } from "emeraldengine";
|
|
1445
|
+
import { Scene } from "emeraldengine";
|
|
1446
|
+
import { Color } from "emeraldengine";
|
|
1447
|
+
import { SceneManager } from "emeraldengine";
|
|
1448
|
+
|
|
1449
|
+
const emerald = new Emerald(canvas); // You should pass your own canvas element here
|
|
1450
|
+
const scene = new Scene();
|
|
1451
|
+
SceneManager.setScene(scene);
|
|
1452
|
+
```
|
|
1453
|
+
|
|
1454
|
+
### Set the background color for the engine
|
|
1455
|
+
|
|
1456
|
+
```javascript
|
|
1457
|
+
emerald.setBackgroundColor(color); // color = new Color(r, g, b, a = 255);
|
|
1458
|
+
```
|
|
1459
|
+
|
|
1460
|
+
### Drawing the scene
|
|
1461
|
+
|
|
1462
|
+
To draw items in the screen you need some sort of animation loop. I use `window.requestAnimationFrame` for this. Here is a basic example:
|
|
1463
|
+
|
|
1464
|
+
```javascript
|
|
1465
|
+
let lastTime = 0;
|
|
1466
|
+
const animate = (currentTime) => {
|
|
1467
|
+
const deltaTime = (currentTime - lastTime) / 1000;
|
|
1468
|
+
lastTime = currentTime;
|
|
1469
|
+
emerald.drawScene(scene, deltaTime); // You need this line to tell the engine what to draw
|
|
1470
|
+
window.requestAnimationFrame(animate);
|
|
1471
|
+
};
|
|
1472
|
+
animate(0);
|
|
1473
|
+
```
|
|
1474
|
+
|
|
1475
|
+
## Scene
|
|
1476
|
+
|
|
1477
|
+
Emerald has multiple scenes support. In order to render any object it has to be added to the scene using the `add` method.
|
|
1478
|
+
|
|
1479
|
+
### Adding and removing items from the scene
|
|
1480
|
+
|
|
1481
|
+
```javascript
|
|
1482
|
+
// Adding an object to the scene
|
|
1483
|
+
scene.add(gameObject);
|
|
1484
|
+
|
|
1485
|
+
// Removing an object from the scene
|
|
1486
|
+
scene.remove(gameObject);
|
|
1487
|
+
```
|
|
1488
|
+
|
|
1489
|
+
### Set Active Scene
|
|
1490
|
+
|
|
1491
|
+
```javascript
|
|
1492
|
+
// When changing a scene you should deactivate current scene to not mess up the event manager.
|
|
1493
|
+
|
|
1494
|
+
// Activate Scene
|
|
1495
|
+
scene.setIsActive(true);
|
|
1496
|
+
|
|
1497
|
+
// Deactivate Scene
|
|
1498
|
+
scene.setIsActive(false);
|
|
1499
|
+
```
|
|
1500
|
+
|
|
1501
|
+
## Game Objects
|
|
1502
|
+
|
|
1503
|
+
### Creating a new GameObject
|
|
1504
|
+
|
|
1505
|
+
```javascript
|
|
1506
|
+
import { GameObject } from "emeraldengine";
|
|
1507
|
+
import { Vector3, Vector2 } from "emeraldengine";
|
|
1508
|
+
/*
|
|
1509
|
+
ARGUMENTS:
|
|
1510
|
+
1. name: string = Name of the new GameObject
|
|
1511
|
+
2. position: Vector3 = Position of the new GameObject
|
|
1512
|
+
3. rotation: number = Rotation of the new GameObject
|
|
1513
|
+
4. scale: Vector2 = Scale of the new GameObject
|
|
1514
|
+
*/
|
|
1515
|
+
const gameObject = new GameObject(name, position, rotation, scale);
|
|
1516
|
+
```
|
|
1517
|
+
|
|
1518
|
+
This will create a new empty GameObject. At this stage you will not see anything on the screen until you add some components.
|
|
1519
|
+
|
|
1520
|
+
### Components
|
|
1521
|
+
|
|
1522
|
+
There are currently 8 components: Texture, InstancedTexture, Square2D, Circle2D, Triangle2D, RigidBody, BoxCollider, CircleCollider
|
|
1523
|
+
|
|
1524
|
+
#### Texture
|
|
1525
|
+
|
|
1526
|
+
```javascript
|
|
1527
|
+
import { Texture } from "emeraldengine";
|
|
1528
|
+
/*
|
|
1529
|
+
ARGUMENTS:
|
|
1530
|
+
1. texturePath = Specify the path for the texture that you want to use.
|
|
1531
|
+
2. frameWidth: number = The width of each frame.
|
|
1532
|
+
3. frameHeight: number = The height of each frame.
|
|
1533
|
+
4. framesPerRow: number = How many frames are in one row in your spritesheet.
|
|
1534
|
+
5. totalFrames: number = How many total frames does your spritesheet have.
|
|
1535
|
+
6. animationSpeed: number = Speed of change of every frame.
|
|
1536
|
+
7. autoPlay: boolean = Specify if you want the animation to play automatically. If you don't want any animation then pass false for it.
|
|
1537
|
+
8. pixelart: boolean = Specify whether the texture should be rendered in pixel art style. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
|
|
1538
|
+
9. useLighting: boolean = Specify whether the texture should react to lighting or not. If you don't want any lighting then pass false for it. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
|
|
1539
|
+
*/
|
|
1540
|
+
const texture = new Texture(
|
|
1541
|
+
texturePath,
|
|
1542
|
+
frameWidth,
|
|
1543
|
+
frameHeight,
|
|
1544
|
+
framesPerRow,
|
|
1545
|
+
totalFrames,
|
|
1546
|
+
animationSpeed,
|
|
1547
|
+
autoPlay,
|
|
1548
|
+
(pixelart = true),
|
|
1549
|
+
(useLighting = true)
|
|
1550
|
+
);
|
|
1551
|
+
|
|
1552
|
+
// Add the texture to a game object
|
|
1553
|
+
gameObject.addComponent(texture);
|
|
1554
|
+
```
|
|
1555
|
+
|
|
1556
|
+

|
|
1557
|
+
|
|
1558
|
+
#### InstancedTexture
|
|
1559
|
+
|
|
1560
|
+
InstancedTexture is perfect for rendering many objects with the same texture efficiently, such as tiles, particles, or repeating elements.
|
|
1561
|
+
|
|
1562
|
+
```javascript
|
|
1563
|
+
import { InstancedTexture } from "emeraldengine";
|
|
1564
|
+
/*
|
|
1565
|
+
ARGUMENTS:
|
|
1566
|
+
1. texturePath = Specify the path for the texture that you want to use.
|
|
1567
|
+
2. instanceCount: number = How many instances of the texture you want to create.
|
|
1568
|
+
3. frameWidth: number = The width of each frame.
|
|
1569
|
+
4. frameHeight: number = The height of each frame.
|
|
1570
|
+
5. framesPerRow: number = How many frames are in one row in your spritesheet.
|
|
1571
|
+
6. totalFrames: number = How many total frames does your spritesheet have.
|
|
1572
|
+
7. animationSpeed: number = Speed of change of every frame.
|
|
1573
|
+
8. autoPlay: boolean = Specify if you want the animation to play automatically. If you don't want any animation then pass false for it.
|
|
1574
|
+
9. pixelart: boolean = Specify whether the texture should be rendered in pixel art style. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
|
|
1575
|
+
10. useLighting: boolean = Specify whether the texture should react to lighting or not. If you don't want any lighting then pass false for it. (THIS IS OPTIONAL. If you don't specify it then it will be defaulted to true)
|
|
1576
|
+
*/
|
|
1577
|
+
const instancedTexture = new InstancedTexture(
|
|
1578
|
+
texturePath,
|
|
1579
|
+
instanceCount,
|
|
1580
|
+
frameWidth,
|
|
1581
|
+
frameHeight,
|
|
1582
|
+
framesPerRow,
|
|
1583
|
+
totalFrames,
|
|
1584
|
+
animationSpeed,
|
|
1585
|
+
autoPlay,
|
|
1586
|
+
(pixelart = true),
|
|
1587
|
+
(useLighting = true)
|
|
1588
|
+
);
|
|
1589
|
+
|
|
1590
|
+
// Add the instanced texture to a game object
|
|
1591
|
+
gameObject.addComponent(instancedTexture);
|
|
1592
|
+
```
|
|
1593
|
+
|
|
1594
|
+

|
|
1595
|
+
|
|
1596
|
+
#### Square2D
|
|
1597
|
+
|
|
1598
|
+
```javascript
|
|
1599
|
+
import { Square2D } from "emeraldengine";
|
|
1600
|
+
let square = new Square2D();
|
|
1601
|
+
gameObject.addComponent(square);
|
|
1602
|
+
```
|
|
1603
|
+
|
|
1604
|
+

|
|
1605
|
+
|
|
1606
|
+
#### Triangle2D
|
|
1607
|
+
|
|
1608
|
+
```javascript
|
|
1609
|
+
import { Triangle2D } from "emeraldengine";
|
|
1610
|
+
let triangle = new Triangle2D();
|
|
1611
|
+
gameObject.addComponent(triangle);
|
|
1612
|
+
```
|
|
1613
|
+
|
|
1614
|
+

|
|
1615
|
+
|
|
1616
|
+
#### Circle2D
|
|
1617
|
+
|
|
1618
|
+
```javascript
|
|
1619
|
+
import { Circle2D } from "emeraldengine";
|
|
1620
|
+
/*
|
|
1621
|
+
ARGUMENTS:
|
|
1622
|
+
1. segments = number of segments that the circle will have. Default is 32.
|
|
1623
|
+
*/
|
|
1624
|
+
let circle = new Circle2D(segments);
|
|
1625
|
+
gameObject.addComponent(circle);
|
|
1626
|
+
```
|
|
1627
|
+
|
|
1628
|
+

|
|
1629
|
+
|
|
1630
|
+
#### RigidBody
|
|
1631
|
+
|
|
1632
|
+
RigidBody is a component that allows you to add physics to your game objects. However, it won't work until you create a Physics instance at the top of your code.
|
|
1633
|
+
|
|
1634
|
+
```javascript
|
|
1635
|
+
import { RigidBody } from "emeraldengine";
|
|
1636
|
+
import { Physics, Vector2 } from "emeraldengine";
|
|
1637
|
+
// Create physics engine first
|
|
1638
|
+
const physics = new Physics(-70, 32, 2); // gravity, scale, velocityThreshold
|
|
1639
|
+
|
|
1640
|
+
/*
|
|
1641
|
+
ARGUMENTS:
|
|
1642
|
+
1. physics: Physics = Instance of the Physics class that you created at the top of your code.
|
|
1643
|
+
2. type: string = Type of the rigid body. It can be "dynamic", "kinematic", or "static".
|
|
1644
|
+
3. position: Vector2 = Position of the rigid body is Vector2 because it doesn't need any Z index.
|
|
1645
|
+
4. fixedRotation: boolean = Specify whether the rigid body should have a fixed rotation or not. Default is false.
|
|
1646
|
+
5. parentObject: GameObject = (OPTIONAL) If you want to attach the rigid body to a GameObject you can pass it here. If you don't want to attach it to any GameObject then pass null.
|
|
1647
|
+
6. offset: Vector2 = (OPTIONAL) Offset from the GameObject's position.
|
|
1648
|
+
*/
|
|
1649
|
+
const rigidBody = new RigidBody(
|
|
1650
|
+
physics,
|
|
1651
|
+
"dynamic",
|
|
1652
|
+
new Vector2(0, 0),
|
|
1653
|
+
false,
|
|
1654
|
+
gameObject,
|
|
1655
|
+
new Vector2(0, 0)
|
|
1656
|
+
);
|
|
1657
|
+
|
|
1658
|
+
gameObject.addComponent(rigidBody);
|
|
1659
|
+
```
|
|
1660
|
+
|
|
1661
|
+
#### BoxCollider
|
|
1662
|
+
|
|
1663
|
+
```javascript
|
|
1664
|
+
import { BoxCollider } from "emeraldengine";
|
|
1665
|
+
|
|
1666
|
+
/*
|
|
1667
|
+
ARGUMENTS:
|
|
1668
|
+
1. rigidBody: RigidBody = The rigid body component that this collider will be attached to.
|
|
1669
|
+
2. size: Vector2 = Size of the box collider.
|
|
1670
|
+
3. density: number = Density of the collider.
|
|
1671
|
+
4. friction: number = Friction of the collider.
|
|
1672
|
+
5. restitution: number = Restitution (bounciness) of the collider.
|
|
1673
|
+
6. isSensor: boolean = Whether this collider is a sensor (triggers events but doesn't collide physically).
|
|
1674
|
+
7. parentObject: GameObject = (OPTIONAL) Parent GameObject.
|
|
1675
|
+
*/
|
|
1676
|
+
const boxCollider = new BoxCollider(
|
|
1677
|
+
rigidBody,
|
|
1678
|
+
new Vector2(1, 1),
|
|
1679
|
+
1,
|
|
1680
|
+
0.3,
|
|
1681
|
+
0.1,
|
|
1682
|
+
false,
|
|
1683
|
+
gameObject
|
|
1684
|
+
);
|
|
1685
|
+
|
|
1686
|
+
gameObject.addComponent(boxCollider);
|
|
1687
|
+
```
|
|
1688
|
+
|
|
1689
|
+

|
|
1690
|
+
|
|
1691
|
+
The `BoxCollider` is specifically made bigger than the `Square2D` component in this image to demonstrate how it works. You can adjust the size of the collider to fit your needs.
|
|
1692
|
+
|
|
1693
|
+
#### CircleCollider
|
|
1694
|
+
|
|
1695
|
+
```javascript
|
|
1696
|
+
import { CircleCollider } from "emeraldengine";
|
|
1697
|
+
|
|
1698
|
+
/*
|
|
1699
|
+
ARGUMENTS:
|
|
1700
|
+
1. rigidBody: RigidBody = The rigid body component that this collider will be attached to.
|
|
1701
|
+
2. radius: number = Radius of the circle collider.
|
|
1702
|
+
3. density: number = Density of the collider.
|
|
1703
|
+
4. friction: number = Friction of the collider.
|
|
1704
|
+
5. restitution: number = Restitution (bounciness) of the collider.
|
|
1705
|
+
6. isSensor: boolean = Whether this collider is a sensor.
|
|
1706
|
+
7. parentObject: GameObject = (OPTIONAL) Parent GameObject.
|
|
1707
|
+
*/
|
|
1708
|
+
const circleCollider = new CircleCollider(
|
|
1709
|
+
rigidBody,
|
|
1710
|
+
1.5,
|
|
1711
|
+
1,
|
|
1712
|
+
0.3,
|
|
1713
|
+
0.8,
|
|
1714
|
+
false,
|
|
1715
|
+
gameObject
|
|
1716
|
+
);
|
|
1717
|
+
|
|
1718
|
+
gameObject.addComponent(circleCollider);
|
|
1719
|
+
```
|
|
1720
|
+
|
|
1721
|
+

|
|
1722
|
+
|
|
1723
|
+
The `CircleCollider` is specifically made bigger than the `Circle2D` component in this image to demonstrate how it works. You can adjust the radius of the collider to fit your needs.
|
|
1724
|
+
|
|
1725
|
+
## Object methods
|
|
1726
|
+
|
|
1727
|
+
### Position
|
|
1728
|
+
|
|
1729
|
+
```javascript
|
|
1730
|
+
// Set position
|
|
1731
|
+
gameObject.transform.position.x = 100;
|
|
1732
|
+
gameObject.transform.position.y = 200;
|
|
1733
|
+
gameObject.transform.position.z = 0;
|
|
1734
|
+
|
|
1735
|
+
// Or set all at once
|
|
1736
|
+
gameObject.transform.position = new Vector3(100, 200, 0);
|
|
1737
|
+
```
|
|
1738
|
+
|
|
1739
|
+

|
|
1740
|
+
|
|
1741
|
+
### Rotation
|
|
1742
|
+
|
|
1743
|
+
```javascript
|
|
1744
|
+
// Set rotation (in radians)
|
|
1745
|
+
gameObject.transform.rotation = Math.PI / 4; // 45 degrees
|
|
1746
|
+
```
|
|
1747
|
+
|
|
1748
|
+

|
|
1749
|
+
|
|
1750
|
+
### Scale
|
|
1751
|
+
|
|
1752
|
+
```javascript
|
|
1753
|
+
// Set scale
|
|
1754
|
+
gameObject.transform.scale.x = 2;
|
|
1755
|
+
gameObject.transform.scale.y = 2;
|
|
1756
|
+
|
|
1757
|
+
// Or set both at once
|
|
1758
|
+
gameObject.transform.scale = new Vector2(2, 2);
|
|
1759
|
+
```
|
|
1760
|
+
|
|
1761
|
+

|
|
1762
|
+
|
|
1763
|
+
### Change color
|
|
1764
|
+
|
|
1765
|
+
```javascript
|
|
1766
|
+
// For textures
|
|
1767
|
+
const texture = gameObject.getComponent(Texture);
|
|
1768
|
+
texture.setColor(new Color(255, 0, 0)); // Red
|
|
1769
|
+
```
|
|
1770
|
+
|
|
1771
|
+

|
|
1772
|
+
|
|
1773
|
+
### Set texture frame
|
|
1774
|
+
|
|
1775
|
+
```javascript
|
|
1776
|
+
// For animated textures
|
|
1777
|
+
const texture = gameObject.getComponent(Texture);
|
|
1778
|
+
texture.setFrame(2); // Set to frame 2
|
|
1779
|
+
```
|
|
1780
|
+
|
|
1781
|
+
## Animations
|
|
1782
|
+
|
|
1783
|
+
```javascript
|
|
1784
|
+
// Play animation
|
|
1785
|
+
texture.playAnimation([0, 1, 2, 3], 200); // frames array, speed in ms
|
|
1786
|
+
|
|
1787
|
+
// Stop animation
|
|
1788
|
+
texture.stopAnimation();
|
|
1789
|
+
|
|
1790
|
+
// Check if playing
|
|
1791
|
+
if (texture.isPlaying) {
|
|
1792
|
+
// Animation is currently playing
|
|
1793
|
+
}
|
|
1794
|
+
```
|
|
1795
|
+
|
|
1796
|
+

|
|
1797
|
+
|
|
1798
|
+
## Instance System
|
|
1799
|
+
|
|
1800
|
+
The Instance system allows you to efficiently manage multiple copies of the same object.
|
|
1801
|
+
|
|
1802
|
+
### Creating Instances
|
|
1803
|
+
|
|
1804
|
+
```javascript
|
|
1805
|
+
import { Instance } from "emeraldengine";
|
|
1806
|
+
|
|
1807
|
+
// Create an instance
|
|
1808
|
+
const instance = new Instance(
|
|
1809
|
+
"InstanceName",
|
|
1810
|
+
new Vector3(x, y, z),
|
|
1811
|
+
new Vector2(width, height),
|
|
1812
|
+
rotation,
|
|
1813
|
+
frame
|
|
1814
|
+
);
|
|
1815
|
+
|
|
1816
|
+
// Add to InstancedTexture
|
|
1817
|
+
const instancedTexture = gameObject.getComponent(InstancedTexture);
|
|
1818
|
+
instancedTexture.addInstance(instance);
|
|
1819
|
+
```
|
|
1820
|
+
|
|
1821
|
+
### Instance Management
|
|
1822
|
+
|
|
1823
|
+
```javascript
|
|
1824
|
+
// Remove instance
|
|
1825
|
+
instancedTexture.removeInstance(instanceId);
|
|
1826
|
+
|
|
1827
|
+
// Get instance by ID
|
|
1828
|
+
const instance = instancedTexture.getInstanceWithId(instanceId);
|
|
1829
|
+
|
|
1830
|
+
// Get instance at position
|
|
1831
|
+
const instance = instancedTexture.getInstanceAtPosition(position, tolerance);
|
|
1832
|
+
|
|
1833
|
+
// Clear all instances
|
|
1834
|
+
instancedTexture.clearInstances();
|
|
1835
|
+
```
|
|
1836
|
+
|
|
1837
|
+
### Instance Events
|
|
1838
|
+
|
|
1839
|
+
```javascript
|
|
1840
|
+
// Add click event to specific instance
|
|
1841
|
+
instancedTexture.addInstanceClickEvent(instanceId, (event) => {
|
|
1842
|
+
console.log("Instance clicked!");
|
|
1843
|
+
});
|
|
1844
|
+
|
|
1845
|
+
// Add hover events to specific instance
|
|
1846
|
+
instancedTexture.addInstanceHoverEvent(
|
|
1847
|
+
instanceId,
|
|
1848
|
+
(event) => console.log("Mouse entered"),
|
|
1849
|
+
(event) => console.log("Mouse left")
|
|
1850
|
+
);
|
|
1851
|
+
```
|
|
1852
|
+
|
|
1853
|
+
## Physics Engine
|
|
1854
|
+
|
|
1855
|
+
Emerald includes a comprehensive physics engine built on top of Planck.js.
|
|
1856
|
+
|
|
1857
|
+
### Setting up Physics
|
|
1858
|
+
|
|
1859
|
+
```javascript
|
|
1860
|
+
import { Physics } from "emeraldengine";
|
|
1861
|
+
/*
|
|
1862
|
+
ARGUMENTS:
|
|
1863
|
+
1. gravity: number = Gravity force (negative for downward)
|
|
1864
|
+
2. scale: number = Scale factor for physics units to pixels
|
|
1865
|
+
3. velocityThreshold: number = Minimum velocity threshold
|
|
1866
|
+
*/
|
|
1867
|
+
const physics = new Physics(-70, 32, 2);
|
|
1868
|
+
```
|
|
1869
|
+
|
|
1870
|
+
### Physics Bodies
|
|
1871
|
+
|
|
1872
|
+
```javascript
|
|
1873
|
+
// Get the physics body from a RigidBody component
|
|
1874
|
+
const body = rigidBody.getBody();
|
|
1875
|
+
|
|
1876
|
+
// Set velocity
|
|
1877
|
+
body.setLinearVelocity(new Vector2(10, 0));
|
|
1878
|
+
|
|
1879
|
+
// Get velocity
|
|
1880
|
+
const velocity = body.getLinearVelocity();
|
|
1881
|
+
|
|
1882
|
+
// Get position
|
|
1883
|
+
const position = body.getPosition();
|
|
1884
|
+
```
|
|
1885
|
+
|
|
1886
|
+
### Collision Detection
|
|
1887
|
+
|
|
1888
|
+
```javascript
|
|
1889
|
+
// Handle collision enter
|
|
1890
|
+
physics.onCollisionEnter((bodyA, bodyB, contact) => {
|
|
1891
|
+
console.log("Collision started!");
|
|
1892
|
+
|
|
1893
|
+
// Get collision normal
|
|
1894
|
+
const normal = contact.getWorldManifold().normal;
|
|
1895
|
+
//normal.y = -1 when player is on the ground
|
|
1896
|
+
//normal.y = 1 when player hits the ceiling
|
|
1897
|
+
//normal.x = -1 when player hits the left wall
|
|
1898
|
+
//normal.x = 1 when player hits the right wall
|
|
1899
|
+
|
|
1900
|
+
// Check if bodies are sensors
|
|
1901
|
+
const fixtureA = contact.getFixtureA();
|
|
1902
|
+
const fixtureB = contact.getFixtureB();
|
|
1903
|
+
if (fixtureA.isSensor() || fixtureB.isSensor()) {
|
|
1904
|
+
// Handle sensor collision
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
|
|
1908
|
+
// Handle collision exit
|
|
1909
|
+
physics.onCollisionExit((bodyA, bodyB, contact) => {
|
|
1910
|
+
console.log("Collision ended!");
|
|
1911
|
+
});
|
|
1912
|
+
```
|
|
1913
|
+
|
|
1914
|
+
### Collision Events
|
|
1915
|
+
|
|
1916
|
+
```javascript
|
|
1917
|
+
// Process physics in your update loop
|
|
1918
|
+
const animate = (currentTime) => {
|
|
1919
|
+
physics.process(deltaTime);
|
|
1920
|
+
};
|
|
1921
|
+
```
|
|
1922
|
+
|
|
1923
|
+
## Particle System
|
|
1924
|
+
|
|
1925
|
+
Emerald includes a powerful particle system for creating visual effects.
|
|
1926
|
+
|
|
1927
|
+

|
|
1928
|
+
|
|
1929
|
+
### Particle Settings
|
|
1930
|
+
|
|
1931
|
+
```javascript
|
|
1932
|
+
import { ParticleSettings } from "emeraldengine";
|
|
1933
|
+
|
|
1934
|
+
const particleSettings = new ParticleSettings({
|
|
1935
|
+
lifetime: 1.2,
|
|
1936
|
+
velocity: new Vector2(200, 300),
|
|
1937
|
+
gravity: new Vector2(0, -400),
|
|
1938
|
+
amount: 16,
|
|
1939
|
+
direction: new Vector2(0, 1), // upward
|
|
1940
|
+
spread: Math.PI * 2,
|
|
1941
|
+
emissionRate: Infinity, // one-shot emission
|
|
1942
|
+
frame: 0,
|
|
1943
|
+
offset: 5,
|
|
1944
|
+
rotation: 0,
|
|
1945
|
+
scale: new Vector2(5, 5),
|
|
1946
|
+
animation: { frames: [0, 1, 2], speed: 200 },
|
|
1947
|
+
});
|
|
1948
|
+
```
|
|
1949
|
+
|
|
1950
|
+
### Creating Particle Systems
|
|
1951
|
+
|
|
1952
|
+
```javascript
|
|
1953
|
+
import { Particles } from "emeraldengine";
|
|
1954
|
+
|
|
1955
|
+
/*
|
|
1956
|
+
ARGUMENTS:
|
|
1957
|
+
1. name: string = Name of the particle system
|
|
1958
|
+
2. texturePath: string = Path to the texture
|
|
1959
|
+
3. frameWidth: number = Width of each frame
|
|
1960
|
+
4. frameHeight: number = Height of each frame
|
|
1961
|
+
5. framesPerRow: number = Frames per row in spritesheet
|
|
1962
|
+
6. totalFrames: number = Total frames in spritesheet
|
|
1963
|
+
7. duration: number = Duration of the effect
|
|
1964
|
+
8. settings: ParticleSettings = Particle settings object
|
|
1965
|
+
*/
|
|
1966
|
+
const particles = new Particles(
|
|
1967
|
+
"explosion",
|
|
1968
|
+
texturePath,
|
|
1969
|
+
16,
|
|
1970
|
+
16,
|
|
1971
|
+
9,
|
|
1972
|
+
27,
|
|
1973
|
+
1.2,
|
|
1974
|
+
particleSettings
|
|
1975
|
+
);
|
|
1976
|
+
|
|
1977
|
+
// Add to scene
|
|
1978
|
+
scene.add(particles.gameObject);
|
|
1979
|
+
```
|
|
1980
|
+
|
|
1981
|
+
### Particle System Methods
|
|
1982
|
+
|
|
1983
|
+
```javascript
|
|
1984
|
+
// Play particle effect at position
|
|
1985
|
+
particles.play(new Vector3(x, y, z));
|
|
1986
|
+
|
|
1987
|
+
// Stop particle system
|
|
1988
|
+
particles.stop();
|
|
1989
|
+
|
|
1990
|
+
// Reset particle system
|
|
1991
|
+
particles.reset();
|
|
1992
|
+
|
|
1993
|
+
// Update particles (call in your animation loop)
|
|
1994
|
+
particles.update(deltaTime);
|
|
1995
|
+
|
|
1996
|
+
// Check if active
|
|
1997
|
+
if (particles.active) {
|
|
1998
|
+
// Particles are currently active
|
|
1999
|
+
}
|
|
2000
|
+
```
|
|
2001
|
+
|
|
2002
|
+
## Lighting System
|
|
2003
|
+
|
|
2004
|
+
Emerald supports ambient, point, and directional lighting.
|
|
2005
|
+
|
|
2006
|
+
### Ambient Light
|
|
2007
|
+
|
|
2008
|
+
```javascript
|
|
2009
|
+
// Set ambient light
|
|
2010
|
+
emerald.setAmbientLight(new Vector3(0.3, 0.3, 0.3)); // RGB values 0-1
|
|
2011
|
+
```
|
|
2012
|
+
|
|
2013
|
+
### Point Light
|
|
2014
|
+
|
|
2015
|
+
```javascript
|
|
2016
|
+
import { PointLight } from "emeraldengine";
|
|
2017
|
+
|
|
2018
|
+
/*
|
|
2019
|
+
ARGUMENTS:
|
|
2020
|
+
1. position: Vector2 = Position of the light
|
|
2021
|
+
2. color: Color = Color of the light
|
|
2022
|
+
3. intensity: number = Light intensity
|
|
2023
|
+
4. radius: number = Light radius
|
|
2024
|
+
*/
|
|
2025
|
+
const pointLight = new PointLight(
|
|
2026
|
+
new Vector2(100, 0),
|
|
2027
|
+
new Color(255, 204, 153),
|
|
2028
|
+
1.5,
|
|
2029
|
+
400
|
|
2030
|
+
);
|
|
2031
|
+
|
|
2032
|
+
// Add to engine
|
|
2033
|
+
emerald.addPointLight(pointLight);
|
|
2034
|
+
|
|
2035
|
+
// Update position
|
|
2036
|
+
pointLight.position.x = newX;
|
|
2037
|
+
pointLight.position.y = newY;
|
|
2038
|
+
```
|
|
2039
|
+
|
|
2040
|
+
### Directional Light
|
|
2041
|
+
|
|
2042
|
+
```javascript
|
|
2043
|
+
import { DirectionalLight } from "emeraldengine";
|
|
2044
|
+
|
|
2045
|
+
/*
|
|
2046
|
+
ARGUMENTS:
|
|
2047
|
+
1. position: Vector2 = Position of the light
|
|
2048
|
+
2. direction: Vector2 = Direction vector
|
|
2049
|
+
3. color: Color = Color of the light
|
|
2050
|
+
4. intensity: number = Light intensity
|
|
2051
|
+
5. width: number = Width of the light beam
|
|
2052
|
+
*/
|
|
2053
|
+
const directionalLight = new DirectionalLight(
|
|
2054
|
+
new Vector2(0, 300),
|
|
2055
|
+
new Vector2(0, -1), // pointing down
|
|
2056
|
+
new Color(255, 255, 255),
|
|
2057
|
+
3.0,
|
|
2058
|
+
200
|
|
2059
|
+
);
|
|
2060
|
+
|
|
2061
|
+
// Add to engine
|
|
2062
|
+
emerald.addDirectionalLight(directionalLight);
|
|
2063
|
+
|
|
2064
|
+
// Rotate direction
|
|
2065
|
+
const angle = 0.1;
|
|
2066
|
+
const newX =
|
|
2067
|
+
directionalLight.direction.x * Math.cos(angle) -
|
|
2068
|
+
directionalLight.direction.y * Math.sin(angle);
|
|
2069
|
+
const newY =
|
|
2070
|
+
directionalLight.direction.x * Math.sin(angle) +
|
|
2071
|
+
directionalLight.direction.y * Math.cos(angle);
|
|
2072
|
+
directionalLight.direction.x = newX;
|
|
2073
|
+
directionalLight.direction.y = newY;
|
|
2074
|
+
```
|
|
2075
|
+
|
|
2076
|
+
## Text Rendering
|
|
2077
|
+
|
|
2078
|
+
### BitmapText
|
|
2079
|
+
|
|
2080
|
+
Emerald supports bitmap font rendering using the BitmapText component. This allows you to display text with custom fonts and styles.
|
|
2081
|
+
|
|
2082
|
+

|
|
2083
|
+
|
|
2084
|
+
```javascript
|
|
2085
|
+
import { BitmapText } from "emeraldengine";
|
|
2086
|
+
|
|
2087
|
+
/*
|
|
2088
|
+
ARGUMENTS:
|
|
2089
|
+
1. text: string = Text to display
|
|
2090
|
+
2. texturePath: string = Path to bitmap font texture
|
|
2091
|
+
3. letters: string = String containing all available characters
|
|
2092
|
+
4. letterSpacing: number = Spacing between letters
|
|
2093
|
+
5. frameWidth: number = Width of each character frame
|
|
2094
|
+
6. frameHeight: number = Height of each character frame
|
|
2095
|
+
7. framesPerRow: number = Characters per row in font texture
|
|
2096
|
+
8. totalFrames: number = Total character frames
|
|
2097
|
+
9. pixelArt: boolean = Whether to use pixel art rendering
|
|
2098
|
+
10. fontSize: number = Font size
|
|
2099
|
+
11. color: Color = Text color
|
|
2100
|
+
12. position: Vector3 = Text position
|
|
2101
|
+
13. rotation: number = Text rotation
|
|
2102
|
+
14. useLighting: boolean = Whether text should react to lighting
|
|
2103
|
+
*/
|
|
2104
|
+
const bitmapText = new BitmapText(
|
|
2105
|
+
"Hello World!",
|
|
2106
|
+
fontTexturePath,
|
|
2107
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?.",
|
|
2108
|
+
16,
|
|
2109
|
+
32,
|
|
2110
|
+
32,
|
|
2111
|
+
10,
|
|
2112
|
+
95,
|
|
2113
|
+
true,
|
|
2114
|
+
24,
|
|
2115
|
+
new Color(255, 255, 255),
|
|
2116
|
+
new Vector3(0, 200, 0),
|
|
2117
|
+
0,
|
|
2118
|
+
false
|
|
2119
|
+
);
|
|
2120
|
+
|
|
2121
|
+
// Add to scene
|
|
2122
|
+
scene.add(bitmapText.gameObject);
|
|
2123
|
+
|
|
2124
|
+
// Update text
|
|
2125
|
+
bitmapText.setText("New Text!");
|
|
2126
|
+
bitmapText.setColor(new Color(255, 0, 0));
|
|
2127
|
+
bitmapText.setFontSize(32);
|
|
2128
|
+
bitmapText.setLetterSpacing(20);
|
|
2129
|
+
```
|
|
2130
|
+
|
|
2131
|
+
### CanvasText (system fonts, word-wrap, Retina-crisp)
|
|
2132
|
+
|
|
2133
|
+
`CanvasText` renders any CSS font (including loaded webfonts) into a texture.
|
|
2134
|
+
It renders at the device pixel ratio, so text is crisp on Retina/HiDPI
|
|
2135
|
+
displays, and it supports multi-line strings with word-wrapping and alignment:
|
|
2136
|
+
|
|
2137
|
+
```javascript
|
|
2138
|
+
import { CanvasText } from "emeraldengine";
|
|
2139
|
+
|
|
2140
|
+
// Factory: returns a GameObject already sized to the text
|
|
2141
|
+
const label = CanvasText.create("Score: 0", {
|
|
2142
|
+
font: "700 24px 'Pixelify Sans', sans-serif",
|
|
2143
|
+
color: "#8fe0ff",
|
|
2144
|
+
screenSpace: true, // HUD: fixed on screen, position in px from center
|
|
2145
|
+
position: new Vector3(0, 240, 0),
|
|
2146
|
+
});
|
|
2147
|
+
scene.add(label);
|
|
2148
|
+
|
|
2149
|
+
// Multi-line + wrapping
|
|
2150
|
+
const dialog = CanvasText.create(
|
|
2151
|
+
"A long line of dialogue that wraps automatically.\nExplicit breaks work too.",
|
|
2152
|
+
{ font: "16px system-ui", maxWidth: 320, align: "left", lineHeight: 22 }
|
|
2153
|
+
);
|
|
2154
|
+
|
|
2155
|
+
// Updating (re-renders the texture; attached GameObject rescales to fit)
|
|
2156
|
+
const text = label.getComponent(CanvasText);
|
|
2157
|
+
text.setText("Score: 120");
|
|
2158
|
+
text.setColor("#ffd166");
|
|
2159
|
+
text.setMaxWidth(400);
|
|
2160
|
+
text.setAlign("center"); // "left" | "center" | "right"
|
|
2161
|
+
```
|
|
2162
|
+
|
|
2163
|
+
Rule of thumb: `BitmapText` for retro/pixel fonts from a glyph sheet,
|
|
2164
|
+
`CanvasText` for everything else (UI, dialogue, any real font).
|
|
2165
|
+
|
|
2166
|
+
## EventManager
|
|
2167
|
+
|
|
2168
|
+
Emerald supports keyboard, mouse, click, and hover events. All events are handled using the built-in EventManager class.
|
|
2169
|
+
|
|
2170
|
+

|
|
2171
|
+
|
|
2172
|
+
```javascript
|
|
2173
|
+
import { EventManager } from "emeraldengine";
|
|
2174
|
+
|
|
2175
|
+
let eventManager = new EventManager(canvas, scene, emerald.camera);
|
|
2176
|
+
```
|
|
2177
|
+
|
|
2178
|
+
### Keyboard Events
|
|
2179
|
+
|
|
2180
|
+
```javascript
|
|
2181
|
+
// Key down events
|
|
2182
|
+
eventManager.addKeyDown("w", () => {
|
|
2183
|
+
console.log("W key pressed");
|
|
2184
|
+
});
|
|
2185
|
+
|
|
2186
|
+
// Key up events
|
|
2187
|
+
eventManager.addKeyUp("w", () => {
|
|
2188
|
+
console.log("W key released");
|
|
2189
|
+
});
|
|
2190
|
+
|
|
2191
|
+
// Check if key is currently pressed
|
|
2192
|
+
if (eventManager.isKeyPressed("w")) {
|
|
2193
|
+
// W key is currently held down
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
// Remove key events
|
|
2197
|
+
eventManager.removeKeyDown("w", callbackFunction);
|
|
2198
|
+
eventManager.removeKeyUp("w", callbackFunction);
|
|
2199
|
+
```
|
|
2200
|
+
|
|
2201
|
+
### Mouse Events
|
|
2202
|
+
|
|
2203
|
+
```javascript
|
|
2204
|
+
// Get mouse position
|
|
2205
|
+
const mousePos = eventManager.getMousePosition();
|
|
2206
|
+
console.log(mousePos.x, mousePos.y);
|
|
2207
|
+
|
|
2208
|
+
// Check if camera was moved
|
|
2209
|
+
if (eventManager.wasCameraMoved()) {
|
|
2210
|
+
// Camera was moved by dragging
|
|
2211
|
+
eventManager.resetCameraMoved();
|
|
2212
|
+
}
|
|
2213
|
+
```
|
|
2214
|
+
|
|
2215
|
+
### Object Events
|
|
2216
|
+
|
|
2217
|
+
```javascript
|
|
2218
|
+
// Click events
|
|
2219
|
+
eventManager.addClickEvent(gameObject, (event, object) => {
|
|
2220
|
+
console.log("Object clicked!");
|
|
2221
|
+
});
|
|
2222
|
+
|
|
2223
|
+
// Hover events
|
|
2224
|
+
eventManager.addHoverEvent(
|
|
2225
|
+
gameObject,
|
|
2226
|
+
(event) => {
|
|
2227
|
+
console.log("Mouse entered object");
|
|
2228
|
+
},
|
|
2229
|
+
(event) => {
|
|
2230
|
+
console.log("Mouse left object");
|
|
2231
|
+
}
|
|
2232
|
+
);
|
|
2233
|
+
|
|
2234
|
+
// Remove events
|
|
2235
|
+
eventManager.removeClickEvent(gameObject, callbackFunction);
|
|
2236
|
+
eventManager.removeHoverEvent(gameObject, enterCallback, leaveCallback);
|
|
2237
|
+
```
|
|
2238
|
+
|
|
2239
|
+
### Event Cleanup
|
|
2240
|
+
|
|
2241
|
+
```javascript
|
|
2242
|
+
// Clean up all events when done
|
|
2243
|
+
eventManager.clean();
|
|
2244
|
+
|
|
2245
|
+
// Change scene
|
|
2246
|
+
eventManager.changeScene(newScene);
|
|
2247
|
+
```
|
|
2248
|
+
|
|
2249
|
+
## AudioManager
|
|
2250
|
+
|
|
2251
|
+
Emerald includes a comprehensive audio management system.
|
|
2252
|
+
|
|
2253
|
+
### Adding Audio
|
|
2254
|
+
|
|
2255
|
+
```javascript
|
|
2256
|
+
import { AudioManager } from "emeraldengine";
|
|
2257
|
+
|
|
2258
|
+
const audioManager = new AudioManager();
|
|
2259
|
+
|
|
2260
|
+
// Add audio files
|
|
2261
|
+
audioManager.add("path/to/sound.wav", "soundName");
|
|
2262
|
+
audioManager.add("path/to/music.mp3", "backgroundMusic");
|
|
2263
|
+
```
|
|
2264
|
+
|
|
2265
|
+
### Playing Audio
|
|
2266
|
+
|
|
2267
|
+
```javascript
|
|
2268
|
+
// Play audio
|
|
2269
|
+
audioManager.play("soundName");
|
|
2270
|
+
|
|
2271
|
+
// Play exclusively (stops all other audio first)
|
|
2272
|
+
audioManager.playExclusive("soundName");
|
|
2273
|
+
```
|
|
2274
|
+
|
|
2275
|
+
### Audio Control
|
|
2276
|
+
|
|
2277
|
+
```javascript
|
|
2278
|
+
// Stop specific audio
|
|
2279
|
+
audioManager.stop("soundName");
|
|
2280
|
+
|
|
2281
|
+
// Stop all audio
|
|
2282
|
+
audioManager.stopAll();
|
|
2283
|
+
|
|
2284
|
+
// Remove audio
|
|
2285
|
+
audioManager.remove("soundName");
|
|
2286
|
+
|
|
2287
|
+
// Get audio object
|
|
2288
|
+
const sound = audioManager.getSound("soundName");
|
|
2289
|
+
```
|
|
2290
|
+
|
|
2291
|
+
## Camera
|
|
2292
|
+
|
|
2293
|
+
The engine has simple controls for the camera. The camera is stored in the emerald variable.
|
|
2294
|
+
|
|
2295
|
+
```javascript
|
|
2296
|
+
// Set camera position
|
|
2297
|
+
emerald.camera.setPosition(x, y, z);
|
|
2298
|
+
|
|
2299
|
+
// Access camera transform directly
|
|
2300
|
+
emerald.camera.transform.position.x = 100;
|
|
2301
|
+
emerald.camera.transform.position.y = 200;
|
|
2302
|
+
emerald.camera.transform.scale.x = 1.5;
|
|
2303
|
+
emerald.camera.transform.scale.y = 1.5;
|
|
2304
|
+
```
|
|
2305
|
+
|
|
2306
|
+
## FPSCounter
|
|
2307
|
+
|
|
2308
|
+
Emerald has a built-in FPS counter.
|
|
2309
|
+
|
|
2310
|
+
```javascript
|
|
2311
|
+
import { FPSCounter } from "emeraldengine";
|
|
2312
|
+
let fpsCounter = new FPSCounter();
|
|
2313
|
+
|
|
2314
|
+
const animate = (currentTime) => {
|
|
2315
|
+
emerald.drawScene(scene, deltaTime);
|
|
2316
|
+
fpsCounter.update(); // Call this in your animation loop
|
|
2317
|
+
window.requestAnimationFrame(animate);
|
|
2318
|
+
};
|
|
2319
|
+
animate();
|
|
2320
|
+
```
|
|
2321
|
+
|
|
2322
|
+
## Scene Management
|
|
2323
|
+
|
|
2324
|
+
```javascript
|
|
2325
|
+
import { SceneManager } from "emeraldengine";
|
|
2326
|
+
|
|
2327
|
+
// Set active scene
|
|
2328
|
+
SceneManager.setScene(scene);
|
|
2329
|
+
|
|
2330
|
+
// Get current scene
|
|
2331
|
+
const currentScene = SceneManager.getScene();
|
|
2332
|
+
```
|
|
2333
|
+
|
|
2334
|
+
## Time Management
|
|
2335
|
+
|
|
2336
|
+
```javascript
|
|
2337
|
+
import { Time } from "emeraldengine";
|
|
2338
|
+
|
|
2339
|
+
// Get delta time
|
|
2340
|
+
const deltaTime = Time.deltaTime;
|
|
2341
|
+
|
|
2342
|
+
// Time is automatically updated when you call emerald.drawScene()
|
|
2343
|
+
// You can also manually set it
|
|
2344
|
+
Time.setDeltaTime(deltaTime);
|
|
2345
|
+
```
|
|
2346
|
+
|
|
2347
|
+
## Advanced Features
|
|
2348
|
+
|
|
2349
|
+
### Resize Handling
|
|
2350
|
+
|
|
2351
|
+
```javascript
|
|
2352
|
+
// Handle window resize
|
|
2353
|
+
const handleResize = () => {
|
|
2354
|
+
const { width, height } = getCanvasDimensions();
|
|
2355
|
+
emerald.resize(width, height);
|
|
2356
|
+
};
|
|
2357
|
+
|
|
2358
|
+
window.addEventListener("resize", handleResize);
|
|
2359
|
+
```
|