littlejsengine 1.4.7 → 1.4.9

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.
@@ -1,4464 +0,0 @@
1
- /*
2
- LittleJS - Release Build
3
- MIT License - Copyright 2021 Frank Force
4
-
5
- - This file is used for release builds in place of engineDebug.js
6
- - Debug functionality will be disabled to lower size and increase performance
7
- */
8
-
9
- 'use strict';
10
-
11
- let showWatermark = 0;
12
- let godMode = 0;
13
- const debug = 0;
14
- const debugOverlay = 0;
15
- const debugPhysics = 0;
16
- const debugParticles = 0;
17
- const debugRaycast = 0;
18
- const debugGamepads = 0;
19
- const debugMedals = 0;
20
-
21
- // debug commands are automatically removed from the final build
22
- const ASSERT = ()=> {}
23
- const debugInit = ()=> {}
24
- const debugUpdate = ()=> {}
25
- const debugRender = ()=> {}
26
- const debugRect = ()=> {}
27
- const debugCircle = ()=> {}
28
- const debugPoint = ()=> {}
29
- const debugLine = ()=> {}
30
- const debugAABB = ()=> {}
31
- const debugText = ()=> {}
32
- const debugClear = ()=> {}
33
- const debugSaveCanvas = ()=> {}
34
- /**
35
- * LittleJS Utility Classes and Functions
36
- * <br> - General purpose math library
37
- * <br> - Vector2 - fast, simple, easy 2D vector class
38
- * <br> - Color - holds a rgba color with some math functions
39
- * <br> - Timer - tracks time automatically
40
- * @namespace Utilities
41
- */
42
-
43
- 'use strict';
44
-
45
- /** A shortcut to get Math.PI
46
- * @const
47
- * @memberof Utilities */
48
- const PI = Math.PI;
49
-
50
- /** Returns absoulte value of value passed in
51
- * @param {Number} value
52
- * @return {Number}
53
- * @memberof Utilities */
54
- const abs = (a)=> a < 0 ? -a : a;
55
-
56
- /** Returns lowest of two values passed in
57
- * @param {Number} valueA
58
- * @param {Number} valueB
59
- * @return {Number}
60
- * @memberof Utilities */
61
- const min = (a, b)=> a < b ? a : b;
62
-
63
- /** Returns highest of two values passed in
64
- * @param {Number} valueA
65
- * @param {Number} valueB
66
- * @return {Number}
67
- * @memberof Utilities */
68
- const max = (a, b)=> a > b ? a : b;
69
-
70
- /** Returns the sign of value passed in (also returns 1 if 0)
71
- * @param {Number} value
72
- * @return {Number}
73
- * @memberof Utilities */
74
- const sign = (a)=> a < 0 ? -1 : 1;
75
-
76
- /** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
77
- * @param {Number} dividend
78
- * @param {Number} [divisor=1]
79
- * @return {Number}
80
- * @memberof Utilities */
81
- const mod = (a, b=1)=> ((a % b) + b) % b;
82
-
83
- /** Clamps the value beween max and min
84
- * @param {Number} value
85
- * @param {Number} [min=0]
86
- * @param {Number} [max=1]
87
- * @return {Number}
88
- * @memberof Utilities */
89
- const clamp = (v, min=0, max=1)=> v < min ? min : v > max ? max : v;
90
-
91
- /** Returns what percentage the value is between max and min
92
- * @param {Number} value
93
- * @param {Number} [min=0]
94
- * @param {Number} [max=1]
95
- * @return {Number}
96
- * @memberof Utilities */
97
- const percent = (v, min=0, max=1)=> max-min ? clamp((v-min) / (max-min)) : 0;
98
-
99
- /** Linearly interpolates the percent value between max and min
100
- * @param {Number} percent
101
- * @param {Number} [min=0]
102
- * @param {Number} [max=1]
103
- * @return {Number}
104
- * @memberof Utilities */
105
- const lerp = (p, min=0, max=1)=> min + clamp(p) * (max-min);
106
-
107
- /** Applies smoothstep function to the percentage value
108
- * @param {Number} value
109
- * @return {Number}
110
- * @memberof Utilities */
111
- const smoothStep = (p)=> p * p * (3 - 2 * p);
112
-
113
- /** Returns the nearest power of two not less then the value
114
- * @param {Number} value
115
- * @return {Number}
116
- * @memberof Utilities */
117
- const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
118
-
119
- /** Returns true if two axis aligned bounding boxes are overlapping
120
- * @param {Vector2} pointA - Center of box A
121
- * @param {Vector2} sizeA - Size of box A
122
- * @param {Vector2} pointB - Center of box B
123
- * @param {Vector2} [sizeB] - Size of box B
124
- * @return {Boolean} - True if overlapping
125
- * @memberof Utilities */
126
- const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x & abs(pA.y - pB.y)*2 < sA.y + sB.y;
127
-
128
- /** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
129
- * @param {Number} [frequency=1] - Frequency of the wave in Hz
130
- * @param {Number} [amplitude=1] - Amplitude (max height) of the wave
131
- * @param {Number} [t=time] - Value to use for time of the wave
132
- * @return {Number} - Value waving between 0 and amplitude
133
- * @memberof Utilities */
134
- const wave = (frequency=1, amplitude=1, t=time)=> amplitude/2 * (1 - Math.cos(t*frequency*2*PI));
135
-
136
- /** Formats seconds to mm:ss style for display purposes
137
- * @param {Number} t - time in seconds
138
- * @return {String}
139
- * @memberof Utilities */
140
- const formatTime = (t)=> (t/60|0)+':'+(t%60<10?'0':'')+(t%60|0);
141
-
142
- ///////////////////////////////////////////////////////////////////////////////
143
-
144
- /** Random global functions
145
- * @namespace Random */
146
-
147
- /** Returns a random value between the two values passed in
148
- * @param {Number} [valueA=1]
149
- * @param {Number} [valueB=0]
150
- * @return {Number}
151
- * @memberof Random */
152
- const rand = (a=1, b=0)=> b + (a-b)*Math.random();
153
-
154
- /** Returns a floored random value the two values passed in
155
- * @param {Number} [valueA=1]
156
- * @param {Number} [valueB=0]
157
- * @return {Number}
158
- * @memberof Random */
159
- const randInt = (a=1, b=0)=> rand(a,b)|0;
160
-
161
- /** Randomly returns either -1 or 1
162
- * @return {Number}
163
- * @memberof Random */
164
- const randSign = ()=> (rand(2)|0) * 2 - 1;
165
-
166
- /** Returns a random Vector2 within a circular shape
167
- * @param {Number} [radius=1]
168
- * @param {Number} [minRadius=0]
169
- * @return {Vector2}
170
- * @memberof Random */
171
- const randInCircle = (radius=1, minRadius=0)=> radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2;
172
-
173
- /** Returns a random Vector2 with the passed in length
174
- * @param {Number} [length=1]
175
- * @return {Vector2}
176
- * @memberof Random */
177
- const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
178
-
179
- /** Returns a random color between the two passed in colors, combine components if linear
180
- * @param {Color} [colorA=new Color(1,1,1,1)]
181
- * @param {Color} [colorB=new Color(0,0,0,1)]
182
- * @param {Boolean} [linear]
183
- * @return {Color}
184
- * @memberof Random */
185
- const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
186
- linear ? cA.lerp(cB, rand()) : new Color(rand(cA.r,cB.r),rand(cA.g,cB.g),rand(cA.b,cB.b),rand(cA.a,cB.a));
187
-
188
- /** The seed used by the randSeeded function, should not be 0
189
- * @memberof Random */
190
- let randSeed = 1;
191
-
192
- /** Returns a seeded random value between the two values passed in using randSeed
193
- * @param {Number} [valueA=1]
194
- * @param {Number} [valueB=0]
195
- * @return {Number}
196
- * @memberof Random */
197
- const randSeeded = (a=1, b=0)=>
198
- {
199
- randSeed ^= randSeed << 13; randSeed ^= randSeed >>> 17; randSeed ^= randSeed << 5; // xorshift
200
- return b + (a-b) * abs(randSeed % 1e9) / 1e9;
201
- }
202
-
203
- ///////////////////////////////////////////////////////////////////////////////
204
-
205
- /**
206
- * Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
207
- * @param {Number} [x=0]
208
- * @param {Number} [y=0]
209
- * @return {Vector2}
210
- * @example
211
- * let a = vec2(0, 1); // vector with coordinates (0, 1)
212
- * let b = vec2(a); // copy a into b
213
- * a = vec2(5); // set a to (5, 5)
214
- * b = vec2(); // set b to (0, 0)
215
- * @memberof Utilities
216
- */
217
- const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
218
-
219
- /**
220
- * 2D Vector object with vector math library
221
- * <br> - Functions do not change this so they can be chained together
222
- * @example
223
- * let a = new Vector2(2, 3); // vector with coordinates (2, 3)
224
- * let b = new Vector2; // vector with coordinates (0, 0)
225
- * let c = vec2(4, 2); // use the vec2 function to make a Vector2
226
- * let d = a.add(b).scale(5); // operators can be chained
227
- */
228
- class Vector2
229
- {
230
- /** Create a 2D vector with the x and y passed in, can also be created with vec2()
231
- * @param {Number} [x=0] - X axis location
232
- * @param {Number} [y=0] - Y axis location */
233
- constructor(x=0, y=0)
234
- {
235
- /** @property {Number} - X axis location */
236
- this.x = x;
237
- /** @property {Number} - Y axis location */
238
- this.y = y;
239
- }
240
-
241
- /** Returns a new vector that is a copy of this
242
- * @return {Vector2} */
243
- copy() { return new Vector2(this.x, this.y); }
244
-
245
- /** Returns a copy of this vector plus the vector passed in
246
- * @param {Vector2} vector
247
- * @return {Vector2} */
248
- add(v) { ASSERT(v.x!=undefined); return new Vector2(this.x + v.x, this.y + v.y); }
249
-
250
- /** Returns a copy of this vector minus the vector passed in
251
- * @param {Vector2} vector
252
- * @return {Vector2} */
253
- subtract(v) { ASSERT(v.x!=undefined); return new Vector2(this.x - v.x, this.y - v.y); }
254
-
255
- /** Returns a copy of this vector times the vector passed in
256
- * @param {Vector2} vector
257
- * @return {Vector2} */
258
- multiply(v) { ASSERT(v.x!=undefined); return new Vector2(this.x * v.x, this.y * v.y); }
259
-
260
- /** Returns a copy of this vector divided by the vector passed in
261
- * @param {Vector2} vector
262
- * @return {Vector2} */
263
- divide(v) { ASSERT(v.x!=undefined); return new Vector2(this.x / v.x, this.y / v.y); }
264
-
265
- /** Returns a copy of this vector scaled by the vector passed in
266
- * @param {Number} scale
267
- * @return {Vector2} */
268
- scale(s) { ASSERT(s.x==undefined); return new Vector2(this.x * s, this.y * s); }
269
-
270
- /** Returns the length of this vector
271
- * @return {Number} */
272
- length() { return this.lengthSquared()**.5; }
273
-
274
- /** Returns the length of this vector squared
275
- * @return {Number} */
276
- lengthSquared() { return this.x**2 + this.y**2; }
277
-
278
- /** Returns the distance from this vector to vector passed in
279
- * @param {Vector2} vector
280
- * @return {Number} */
281
- distance(v) { return this.distanceSquared(v)**.5; }
282
-
283
- /** Returns the distance squared from this vector to vector passed in
284
- * @param {Vector2} vector
285
- * @return {Number} */
286
- distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
287
-
288
- /** Returns a new vector in same direction as this one with the length passed in
289
- * @param {Number} [length=1]
290
- * @return {Vector2} */
291
- normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(0, length); }
292
-
293
- /** Returns a new vector clamped to length passed in
294
- * @param {Number} [length=1]
295
- * @return {Vector2} */
296
- clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
297
-
298
- /** Returns the dot product of this and the vector passed in
299
- * @param {Vector2} vector
300
- * @return {Number} */
301
- dot(v) { ASSERT(v.x!=undefined); return this.x*v.x + this.y*v.y; }
302
-
303
- /** Returns the cross product of this and the vector passed in
304
- * @param {Vector2} vector
305
- * @return {Number} */
306
- cross(v) { ASSERT(v.x!=undefined); return this.x*v.y - this.y*v.x; }
307
-
308
- /** Returns the angle of this vector, up is angle 0
309
- * @return {Number} */
310
- angle() { return Math.atan2(this.x, this.y); }
311
-
312
- /** Sets this vector with angle and length passed in
313
- * @param {Number} [angle=0]
314
- * @param {Number} [length=1] */
315
- setAngle(a=0, length=1) { this.x = length*Math.sin(a); this.y = length*Math.cos(a); return this; }
316
-
317
- /** Returns copy of this vector rotated by the angle passed in
318
- * @param {Number} angle
319
- * @return {Vector2} */
320
- rotate(a) { const c = Math.cos(a), s = Math.sin(a); return new Vector2(this.x*c-this.y*s, this.x*s+this.y*c); }
321
-
322
- /** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
323
- * @return {Number} */
324
- direction() { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
325
-
326
- /** Returns a copy of this vector that has been inverted
327
- * @return {Vector2} */
328
- invert() { return new Vector2(this.y, -this.x); }
329
-
330
- /** Returns a copy of this vector with each axis floored
331
- * @return {Vector2} */
332
- floor() { return new Vector2(Math.floor(this.x), Math.floor(this.y)); }
333
-
334
- /** Returns the area this vector covers as a rectangle
335
- * @return {Number} */
336
- area() { return abs(this.x * this.y); }
337
-
338
- /** Returns a new vector that is p percent between this and the vector passed in
339
- * @param {Vector2} vector
340
- * @param {Number} percent
341
- * @return {Vector2} */
342
- lerp(v, p) { ASSERT(v.x!=undefined); return this.add(v.subtract(this).scale(clamp(p))); }
343
-
344
- /** Returns true if this vector is within the bounds of an array size passed in
345
- * @param {Vector2} arraySize
346
- * @return {Boolean} */
347
- arrayCheck(arraySize) { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
348
-
349
- /** Returns this vector expressed as a string
350
- * @param {float} digits - precision to display
351
- * @return {String} */
352
- toString(digits=3)
353
- { return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }
354
- }
355
-
356
- ///////////////////////////////////////////////////////////////////////////////
357
-
358
- /**
359
- * Create a color object with RGBA values
360
- * @param {Number} [r=1]
361
- * @param {Number} [g=1]
362
- * @param {Number} [b=1]
363
- * @param {Number} [a=1]
364
- * @return {Color}
365
- * @memberof Utilities
366
- */
367
- const colorRGBA = (r, g, b, a)=> new Color(r, g, b, a);
368
-
369
- /**
370
- * Create a color object with HSLA values
371
- * @param {Number} [h=0]
372
- * @param {Number} [s=0]
373
- * @param {Number} [l=1]
374
- * @param {Number} [a=1]
375
- * @return {Color}
376
- * @memberof Utilities
377
- */
378
- const colorHSLA = (h, s, l, a)=> new Color().setHSLA(h, s, l, a);
379
-
380
- /**
381
- * Color object (red, green, blue, alpha) with some helpful functions
382
- * @example
383
- * let a = new Color; // white
384
- * let b = new Color(1, 0, 0); // red
385
- * let c = new Color(0, 0, 0, 0); // transparent black
386
- */
387
- class Color
388
- {
389
- /** Create a color with the components passed in, white by default
390
- * @param {Number} [red=1]
391
- * @param {Number} [green=1]
392
- * @param {Number} [blue=1]
393
- * @param {Number} [alpha=1] */
394
- constructor(r=1, g=1, b=1, a=1)
395
- {
396
- /** @property {Number} - Red */
397
- this.r = r;
398
- /** @property {Number} - Green */
399
- this.g = g;
400
- /** @property {Number} - Blue */
401
- this.b = b;
402
- /** @property {Number} - Alpha */
403
- this.a = a;
404
- }
405
-
406
- /** Returns a new color that is a copy of this
407
- * @return {Color} */
408
- copy() { return new Color(this.r, this.g, this.b, this.a); }
409
-
410
- /** Returns a copy of this color plus the color passed in
411
- * @param {Color} color
412
- * @return {Color} */
413
- add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
414
-
415
- /** Returns a copy of this color minus the color passed in
416
- * @param {Color} color
417
- * @return {Color} */
418
- subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
419
-
420
- /** Returns a copy of this color times the color passed in
421
- * @param {Color} color
422
- * @return {Color} */
423
- multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
424
-
425
- /** Returns a copy of this color divided by the color passed in
426
- * @param {Color} color
427
- * @return {Color} */
428
- divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
429
-
430
- /** Returns a copy of this color scaled by the value passed in, alpha can be scaled separately
431
- * @param {Number} scale
432
- * @param {Number} [alphaScale=scale]
433
- * @return {Color} */
434
- scale(s, a=s) { return new Color(this.r*s, this.g*s, this.b*s, this.a*a); }
435
-
436
- /** Returns a copy of this color clamped to the valid range between 0 and 1
437
- * @return {Color} */
438
- clamp() { return new Color(clamp(this.r), clamp(this.g), clamp(this.b), clamp(this.a)); }
439
-
440
- /** Returns a new color that is p percent between this and the color passed in
441
- * @param {Color} color
442
- * @param {Number} percent
443
- * @return {Color} */
444
- lerp(c, p) { return this.add(c.subtract(this).scale(clamp(p))); }
445
-
446
- /** Sets this color given a hue, saturation, lightness, and alpha
447
- * @param {Number} [hue=0]
448
- * @param {Number} [saturation=0]
449
- * @param {Number} [lightness=1]
450
- * @param {Number} [alpha=1]
451
- * @return {Color} */
452
- setHSLA(h=0, s=0, l=1, a=1)
453
- {
454
- const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
455
- f = (p, q, t)=>
456
- (t = ((t%1)+1)%1) < 1/6 ? p+(q-p)*6*t :
457
- t < 1/2 ? q :
458
- t < 2/3 ? p+(q-p)*(2/3-t)*6 : p;
459
-
460
- this.r = f(p, q, h + 1/3);
461
- this.g = f(p, q, h);
462
- this.b = f(p, q, h - 1/3);
463
- this.a = a;
464
- return this;
465
- }
466
-
467
- /** Returns this color expressed in hsla format
468
- * @return {Array} */
469
- getHSLA()
470
- {
471
- const r = clamp(this.r);
472
- const g = clamp(this.g);
473
- const b = clamp(this.b);
474
- const a = clamp(this.a);
475
- const max = Math.max(r, g, b);
476
- const min = Math.min(r, g, b);
477
- const l = (max + min) / 2;
478
-
479
- let h = 0, s = 0;
480
- if (max != min)
481
- {
482
- let d = max - min;
483
- s = l > .5 ? d / (2 - max - min) : d / (max + min);
484
- if (r == max)
485
- h = (g - b) / d + (g < b ? 6 : 0);
486
- else if (g == max)
487
- h = (b - r) / d + 2;
488
- else if (b == max)
489
- h = (r - g) / d + 4;
490
- }
491
-
492
- return [h / 6, s, l, a];
493
- }
494
-
495
- /** Returns a new color that has each component randomly adjusted
496
- * @param {Number} [amount=.05]
497
- * @param {Number} [alphaAmount=0]
498
- * @return {Color} */
499
- mutate(amount=.05, alphaAmount=0)
500
- {
501
- return new Color
502
- (
503
- this.r + rand(amount, -amount),
504
- this.g + rand(amount, -amount),
505
- this.b + rand(amount, -amount),
506
- this.a + rand(alphaAmount, -alphaAmount)
507
- ).clamp();
508
- }
509
-
510
- /** Returns this color expressed as a hex color code
511
- * @param {Boolean} [useAlpha=1] - if alpha should be included in result
512
- * @return {String} */
513
- toString(useAlpha = 1)
514
- {
515
- const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
516
- return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
517
- }
518
-
519
- /** Set this color from a hex code
520
- * @param {String} hex - html hex code
521
- * @return {Color} */
522
- setHex(hex)
523
- {
524
- const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
525
- this.r = fromHex(1);
526
- this.g = fromHex(3),
527
- this.b = fromHex(5);
528
- this.a = hex.length > 7 ? fromHex(7) : 1;
529
- return this;
530
- }
531
-
532
- /** Returns this color expressed as 32 bit RGBA value
533
- * @return {Number} */
534
- rgbaInt()
535
- {
536
- const toByte = (c)=> clamp(c)*255|0;
537
- const r = toByte(this.r);
538
- const g = toByte(this.g)<<8;
539
- const b = toByte(this.b)<<16;
540
- const a = toByte(this.a)<<24;
541
- return r + g + b + a;
542
- }
543
- }
544
-
545
- ///////////////////////////////////////////////////////////////////////////////
546
-
547
- /**
548
- * Timer object tracks how long has passed since it was set
549
- * @example
550
- * let a = new Timer; // creates a timer that is not set
551
- * a.set(3); // sets the timer to 3 seconds
552
- *
553
- * let b = new Timer(1); // creates a timer with 1 second left
554
- * b.unset(); // unsets the timer
555
- */
556
- class Timer
557
- {
558
- /** Create a timer object set time passed in
559
- * @param {Number} [timeLeft] - How much time left before the timer elapses in seconds */
560
- constructor(timeLeft) { this.time = timeLeft == undefined ? undefined : time + timeLeft; this.setTime = timeLeft; }
561
-
562
- /** Set the timer with seconds passed in
563
- * @param {Number} [timeLeft=0] - How much time left before the timer is elapsed in seconds */
564
- set(timeLeft=0) { this.time = time + timeLeft; this.setTime = timeLeft; }
565
-
566
- /** Unset the timer */
567
- unset() { this.time = undefined; }
568
-
569
- /** Returns true if set
570
- * @return {Boolean} */
571
- isSet() { return this.time != undefined; }
572
-
573
- /** Returns true if set and has not elapsed
574
- * @return {Boolean} */
575
- active() { return time <= this.time; }
576
-
577
- /** Returns true if set and elapsed
578
- * @return {Boolean} */
579
- elapsed() { return time > this.time; }
580
-
581
- /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
582
- * @return {Number} */
583
- get() { return this.isSet()? time - this.time : 0; }
584
-
585
- /** Get percentage elapsed based on time it was set to, returns 0 if not set
586
- * @return {Number} */
587
- getPercent() { return this.isSet()? percent(this.time - time, this.setTime, 0) : 0; }
588
-
589
- /** Returns this timer expressed as a string
590
- * @return {String} */
591
- toString() { if (debug) { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); } }
592
-
593
- /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
594
- * @return {Number} */
595
- valueOf() { return this.get(); }
596
- }
597
- /**
598
- * LittleJS Engine Settings
599
- * @namespace Settings
600
- */
601
-
602
- 'use strict';
603
-
604
- ///////////////////////////////////////////////////////////////////////////////
605
- // Display settings
606
-
607
- /** The max size of the canvas, centered if window is larger
608
- * @type {Vector2}
609
- * @default
610
- * @memberof Settings */
611
- let canvasMaxSize = vec2(1920, 1200);
612
-
613
- /** Fixed size of the canvas, if enabled canvas size never changes
614
- * - you may also need to set mainCanvasSize if using screen space coords in startup
615
- * @type {Vector2}
616
- * @default
617
- * @memberof Settings */
618
- let canvasFixedSize = vec2();
619
-
620
- /** Disables anti aliasing for pixel art if true
621
- * @default
622
- * @memberof Settings */
623
- let cavasPixelated = 1;
624
-
625
- /** Default font used for text rendering
626
- * @default
627
- * @memberof Settings */
628
- let fontDefault = 'arial';
629
-
630
- ///////////////////////////////////////////////////////////////////////////////
631
- // Tile sheet settings
632
-
633
- /** Default size of tiles in pixels
634
- * @type {Vector2}
635
- * @default
636
- * @memberof Settings */
637
- let tileSizeDefault = vec2(16);
638
-
639
- /** Prevent tile bleeding from neighbors in pixels
640
- * @default
641
- * @memberof Settings */
642
- let tileFixBleedScale = .3;
643
-
644
- ///////////////////////////////////////////////////////////////////////////////
645
- // Object settings
646
-
647
- /** Default size of objects
648
- * @type {Vector2}
649
- * @default
650
- * @memberof Settings */
651
- let objectDefaultSize = vec2(1);
652
-
653
- /** Enable physics solver for collisions between objects
654
- * @default
655
- * @memberof Settings */
656
- let enablePhysicsSolver = 1;
657
-
658
- /** Default object mass for collison calcuations (how heavy objects are)
659
- * @default
660
- * @memberof Settings */
661
- let objectDefaultMass = 1;
662
-
663
- /** How much to slow velocity by each frame (0-1)
664
- * @default
665
- * @memberof Settings */
666
- let objectDefaultDamping = .99;
667
-
668
- /** How much to slow angular velocity each frame (0-1)
669
- * @default
670
- * @memberof Settings */
671
- let objectDefaultAngleDamping = .99;
672
-
673
- /** How much to bounce when a collision occurs (0-1)
674
- * @default
675
- * @memberof Settings */
676
- let objectDefaultElasticity = 0;
677
-
678
- /** How much to slow when touching (0-1)
679
- * @default
680
- * @memberof Settings */
681
- let objectDefaultFriction = .8;
682
-
683
- /** Clamp max speed to avoid fast objects missing collisions
684
- * @default
685
- * @memberof Settings */
686
- let objectMaxSpeed = 1;
687
-
688
- /** How much gravity to apply to objects along the Y axis, negative is down
689
- * @default
690
- * @memberof Settings */
691
- let gravity = 0;
692
-
693
- /** Scales emit rate of particles, useful for low graphics mode (0 disables particle emitters)
694
- * @default
695
- * @memberof Settings */
696
- let particleEmitRateScale = 1;
697
-
698
- ///////////////////////////////////////////////////////////////////////////////
699
- // Camera settings
700
-
701
- /** Position of camera in world space
702
- * @type {Vector2}
703
- * @default
704
- * @memberof Settings */
705
- let cameraPos = vec2();
706
-
707
- /** Scale of camera in world space
708
- * @default
709
- * @memberof Settings */
710
- let cameraScale = max(tileSizeDefault.x, tileSizeDefault.y);
711
-
712
- ///////////////////////////////////////////////////////////////////////////////
713
- // WebGL settings
714
-
715
- /** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
716
- * @default
717
- * @memberof Settings */
718
- let glEnable = 1;
719
-
720
- /** Fixes slow rendering in some browsers by not compositing the WebGL canvas
721
- * @default
722
- * @memberof Settings */
723
- let glOverlay = 1;
724
-
725
- ///////////////////////////////////////////////////////////////////////////////
726
- // Input settings
727
-
728
- /** Should gamepads be allowed
729
- * @default
730
- * @memberof Settings */
731
- let gamepadsEnable = 1;
732
-
733
- /** If true, the dpad input is also routed to the left analog stick (for better accessability)
734
- * @default
735
- * @memberof Settings */
736
- let gamepadDirectionEmulateStick = 1;
737
-
738
- /** If true the WASD keys are also routed to the direction keys (for better accessability)
739
- * @default
740
- * @memberof Settings */
741
- let inputWASDEmulateDirection = 1;
742
-
743
- /** True if touch gamepad should appear on mobile devices
744
- * <br> - Supports left analog stick, 4 face buttons and start button (button 9)
745
- * <br> - Must be set by end of gameInit to be activated
746
- * @default
747
- * @memberof Settings */
748
- let touchGamepadEnable = 0;
749
-
750
- /** True if touch gamepad should be analog stick or false to use if 8 way dpad
751
- * @default
752
- * @memberof Settings */
753
- let touchGamepadAnalog = 1;
754
-
755
- /** Size of virutal gamepad for touch devices in pixels
756
- * @default
757
- * @memberof Settings */
758
- let touchGamepadSize = 99;
759
-
760
- /** Transparency of touch gamepad overlay
761
- * @default
762
- * @memberof Settings */
763
- let touchGamepadAlpha = .3;
764
-
765
- /** Allow vibration hardware if it exists
766
- * @default
767
- * @memberof Settings */
768
- let vibrateEnable = 1;
769
-
770
- ///////////////////////////////////////////////////////////////////////////////
771
- // Audio settings
772
-
773
- /** Volume scale to apply to all sound, music and speech
774
- * @default
775
- * @memberof Settings */
776
- let soundVolume = .5;
777
-
778
- /** All audio code can be disabled and removed from build
779
- * @default
780
- * @memberof Settings */
781
- let soundEnable = 1;
782
-
783
- /** Default range where sound no longer plays
784
- * @default
785
- * @memberof Settings */
786
- let soundDefaultRange = 30;
787
-
788
- /** Default range percent to start tapering off sound (0-1)
789
- * @default
790
- * @memberof Settings */
791
- let soundDefaultTaper = .7;
792
-
793
- ///////////////////////////////////////////////////////////////////////////////
794
- // Medals settings
795
-
796
- /** How long to show medals for in seconds
797
- * @default
798
- * @memberof Settings */
799
- let medalDisplayTime = 5;
800
-
801
- /** How quickly to slide on/off medals in seconds
802
- * @default
803
- * @memberof Settings */
804
- let medalDisplaySlideTime = .5;
805
-
806
- /** Width of medal display
807
- * @default
808
- * @memberof Settings */
809
- let medalDisplayWidth = 640;
810
-
811
- /** Height of medal display
812
- * @default
813
- * @memberof Settings */
814
- let medalDisplayHeight = 80;
815
-
816
- /** Size of icon in medal display
817
- * @default
818
- * @memberof Settings */
819
- let medalDisplayIconSize = 50;
820
- /*
821
- LittleJS - The Tiny JavaScript Game Engine That Can!
822
- MIT License - Copyright 2021 Frank Force
823
-
824
- Engine Features
825
- - Object oriented system with base class engine object
826
- - Base class object handles update, physics, collision, rendering, etc
827
- - Engine helper classes and functions like Vector2, Color, and Timer
828
- - Super fast rendering system for tile sheets
829
- - Sound effects audio with zzfx and music with zzfxm
830
- - Input processing system with gamepad and touchscreen support
831
- - Tile layer rendering and collision system
832
- - Particle effect system
833
- - Medal system tracks and displays achievements
834
- - Debug tools and debug rendering system
835
- - Call engineInit() to start it up!
836
- */
837
-
838
- 'use strict';
839
-
840
- /** Name of engine */
841
- const engineName = 'LittleJS';
842
-
843
- /** Version of engine */
844
- const engineVersion = '1.4.7';
845
-
846
- /** Frames per second to update objects
847
- * @default */
848
- const frameRate = 60;
849
-
850
- /** How many seconds each frame lasts, engine uses a fixed time step
851
- * @default 1/60 */
852
- const timeDelta = 1/frameRate;
853
-
854
- /** Array containing all engine objects */
855
- let engineObjects = [];
856
-
857
- /** Array containing only objects that are set to collide with other objects this frame (for optimization) */
858
- let engineObjectsCollide = [];
859
-
860
- /** Current update frame, used to calculate time */
861
- let frame = 0;
862
-
863
- /** Current engine time since start in seconds, derived from frame */
864
- let time = 0;
865
-
866
- /** Actual clock time since start in seconds (not affected by pause or frame rate clamping) */
867
- let timeReal = 0;
868
-
869
- /** Is the game paused? Causes time and objects to not be updated. */
870
- let paused = 0;
871
-
872
- // Engine internal variables not exposed to documentation
873
- let tileImageSize, tileImageFixBleed;
874
-
875
- // Engine stat tracking, if showWatermark is true
876
- let averageFPS, drawCount;
877
-
878
- ///////////////////////////////////////////////////////////////////////////////
879
-
880
- /** Start up LittleJS engine with your callback functions
881
- * @param {Function} gameInit - Called once after the engine starts up, setup the game
882
- * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
883
- * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
884
- * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
885
- * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
886
- * @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading
887
- */
888
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
889
- {
890
- // init engine when tiles load or fail to load
891
- tileImage.onerror = tileImage.onload = ()=>
892
- {
893
- // save tile image info
894
- tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
895
- debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
896
-
897
- // setup css
898
- const styleBody = 'margin:0;overflow:hidden;background:#000' + // fill the window
899
- ';touch-action:none' + // prevent mobile pinch to resize
900
- ';user-select:none' + // prevent mobile hold to select
901
- ';-webkit-user-select:none;-moz-user-select:none'; // compatibility for mobile
902
-
903
- // setup html
904
- document.body.style = styleBody;
905
- document.body.appendChild(mainCanvas = document.createElement('canvas'));
906
- mainContext = mainCanvas.getContext('2d');
907
-
908
- // init stuff and start engine
909
- debugInit();
910
- glEnable && glInit();
911
-
912
- // create overlay canvas for hud to appear above gl canvas
913
- document.body.appendChild(overlayCanvas = document.createElement('canvas'));
914
- overlayContext = overlayCanvas.getContext('2d');
915
-
916
- // set canvas style to fill the window
917
- const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
918
- (glCanvas||mainCanvas).style = overlayCanvas.style = mainCanvas.style = styleCanvas;
919
-
920
- gameInit();
921
- touchGamepadCreate();
922
- engineUpdate();
923
- };
924
-
925
- // frame time tracking
926
- let frameTimeLastMS = 0, frameTimeBufferMS = 0;
927
-
928
- // main update loop
929
- const engineUpdate = (frameTimeMS=0)=>
930
- {
931
- // update time keeping
932
- let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
933
- frameTimeLastMS = frameTimeMS;
934
- if (debug || showWatermark)
935
- averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
936
- const debugSpeedUp = debug && keyIsDown(107); // +
937
- const debugSpeedDown = debug && keyIsDown(109); // -
938
- if (debug)
939
- frameTimeDeltaMS *= debugSpeedUp ? 5 : debugSpeedDown ? .2 : 1; // +/- to speed/slow time
940
- timeReal += frameTimeDeltaMS / 1e3;
941
- frameTimeBufferMS += !paused * frameTimeDeltaMS;
942
- if (!debugSpeedUp)
943
- frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp incase of slow framerate
944
-
945
- if (canvasFixedSize.x)
946
- {
947
- // clear set fixed size
948
- overlayCanvas.width = mainCanvas.width = canvasFixedSize.x;
949
- overlayCanvas.height = mainCanvas.height = canvasFixedSize.y;
950
-
951
- // fit to window by adding space on top or bottom if necessary
952
- const aspect = innerWidth / innerHeight;
953
- const fixedAspect = mainCanvas.width / mainCanvas.height;
954
- mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
955
- mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
956
- if (glCanvas)
957
- {
958
- glCanvas.style.width = mainCanvas.style.width;
959
- glCanvas.style.height = mainCanvas.style.height;
960
- }
961
- }
962
- else
963
- {
964
- // clear and set size to same as window
965
- overlayCanvas.width = mainCanvas.width = min(innerWidth, canvasMaxSize.x);
966
- overlayCanvas.height = mainCanvas.height = min(innerHeight, canvasMaxSize.y);
967
- }
968
-
969
- // save canvas size
970
- mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
971
-
972
- if (paused)
973
- {
974
- // do post update even when paused
975
- inputUpdate();
976
- debugUpdate();
977
- gameUpdatePost();
978
- inputUpdatePost();
979
- }
980
- else
981
- {
982
- // apply time delta smoothing, improves smoothness of framerate in some browsers
983
- let deltaSmooth = 0;
984
- if (frameTimeBufferMS < 0 && frameTimeBufferMS > -9)
985
- {
986
- // force an update each frame if time is close enough (not just a fast refresh rate)
987
- deltaSmooth = frameTimeBufferMS;
988
- frameTimeBufferMS = 0;
989
- }
990
-
991
- // update multiple frames if necessary in case of slow framerate
992
- for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
993
- {
994
- // update game and objects
995
- inputUpdate();
996
- gameUpdate();
997
- engineObjectsUpdate();
998
-
999
- // do post update
1000
- debugUpdate();
1001
- gameUpdatePost();
1002
- inputUpdatePost();
1003
- }
1004
-
1005
- // add the time smoothing back in
1006
- frameTimeBufferMS += deltaSmooth;
1007
- }
1008
-
1009
- // render sort then render while removing destroyed objects
1010
- enginePreRender();
1011
- gameRender();
1012
- engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
1013
- for (const o of engineObjects)
1014
- o.destroyed || o.render();
1015
- gameRenderPost();
1016
- glRenderPostProcess();
1017
- medalsRender();
1018
- touchGamepadRender();
1019
- debugRender();
1020
- glEnable && glCopyToContext(mainContext);
1021
-
1022
- if (showWatermark)
1023
- {
1024
- // update fps
1025
- overlayContext.textAlign = 'right';
1026
- overlayContext.textBaseline = 'top';
1027
- overlayContext.font = '1em monospace';
1028
- overlayContext.fillStyle = '#000';
1029
- const text = engineName + ' ' + 'v' + engineVersion + ' / '
1030
- + drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
1031
- + ' ' + (glEnable ? 'GL' : '2D') ;
1032
- overlayContext.fillText(text, mainCanvas.width-3, 3);
1033
- overlayContext.fillStyle = '#fff';
1034
- overlayContext.fillText(text, mainCanvas.width-2, 2);
1035
- drawCount = 0;
1036
- }
1037
-
1038
- requestAnimationFrame(engineUpdate);
1039
- }
1040
-
1041
- // set tile image source to load the image and start the engine
1042
- tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
1043
- }
1044
-
1045
- // called by engine to setup render system
1046
- function enginePreRender()
1047
- {
1048
- // save canvas size
1049
- mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
1050
-
1051
- // disable smoothing for pixel art
1052
- mainContext.imageSmoothingEnabled = !cavasPixelated;
1053
-
1054
- // setup gl rendering if enabled
1055
- glEnable && glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
1056
- }
1057
-
1058
- ///////////////////////////////////////////////////////////////////////////////
1059
-
1060
- /** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
1061
- function engineObjectsUpdate()
1062
- {
1063
- // get list of solid objects for physics optimzation
1064
- engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
1065
-
1066
- // recursive object update
1067
- const updateObject = (o)=>
1068
- {
1069
- if (!o.destroyed)
1070
- {
1071
- o.update();
1072
- for (const child of o.children)
1073
- updateObject(child);
1074
- }
1075
- }
1076
- for (const o of engineObjects)
1077
- o.parent || updateObject(o);
1078
-
1079
- // remove destroyed objects
1080
- engineObjects = engineObjects.filter(o=>!o.destroyed);
1081
-
1082
- // increment frame and update time
1083
- time = ++frame / frameRate;
1084
- }
1085
-
1086
- /** Destroy and remove all objects */
1087
- function engineObjectsDestroy()
1088
- {
1089
- for (const o of engineObjects)
1090
- o.parent || o.destroy();
1091
- engineObjects = engineObjects.filter(o=>!o.destroyed);
1092
- }
1093
-
1094
- /** Triggers a callback for each object within a given area
1095
- * @param {Vector2} [pos] - Center of test area
1096
- * @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
1097
- * @param {Function} [callbackFunction] - Calls this function on every object that passes the test
1098
- * @param {Array} [objects=engineObjects] - List of objects to check */
1099
- function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
1100
- {
1101
- if (!pos) // all objects
1102
- {
1103
- for (const o of objects)
1104
- callbackFunction(o);
1105
- }
1106
- else if (size.x != undefined) // bounding box test
1107
- {
1108
- for (const o of objects)
1109
- isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
1110
- }
1111
- else // circle test
1112
- {
1113
- const sizeSquared = size*size;
1114
- for (const o of objects)
1115
- pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
1116
- }
1117
- }
1118
- /*
1119
- LittleJS Object System
1120
- */
1121
-
1122
- 'use strict';
1123
-
1124
- /**
1125
- * LittleJS Object Base Object Class
1126
- * <br> - Base object class used by the engine
1127
- * <br> - Automatically adds self to object list
1128
- * <br> - Will be updated and rendered each frame
1129
- * <br> - Renders as a sprite from a tilesheet by default
1130
- * <br> - Can have color and addtive color applied
1131
- * <br> - 2d Physics and collision system
1132
- * <br> - Sorted by renderOrder
1133
- * <br> - Objects can have children attached
1134
- * <br> - Parents are updated before children, and set child transform
1135
- * <br> - Call destroy() to get rid of objects
1136
- * <br>
1137
- * <br>The physics system used by objects is simple and fast with some caveats...
1138
- * <br> - Collision uses the axis aligned size, the object's rotation angle is only for rendering
1139
- * <br> - Objects are guaranteed to not intersect tile collision from physics
1140
- * <br> - If an object starts or is moved inside tile collision, it will not collide with that tile
1141
- * <br> - Collision for objects can be set to be solid to block other objects
1142
- * <br> - Objects may get pushed into overlapping other solid objects, if so they will push away
1143
- * <br> - Solid objects are more performance intensive and should be used sparingly
1144
- * @example
1145
- * // create an engine object, normally you would first extend the class with your own
1146
- * const pos = vec2(2,3);
1147
- * const object = new EngineObject(pos);
1148
- */
1149
- class EngineObject
1150
- {
1151
- /** Create an engine object and adds it to the list of objects
1152
- * @param {Vector2} [position=new Vector2()] - World space position of the object
1153
- * @param {Vector2} [size=objectDefaultSize] - World space size of the object
1154
- * @param {Number} [tileIndex=-1] - Tile to use to render object (-1 is untextured)
1155
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
1156
- * @param {Number} [angle=0] - Angle the object is rotated by
1157
- * @param {Color} [color] - Color to apply to tile when rendered
1158
- * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
1159
- */
1160
- constructor(pos=vec2(), size=objectDefaultSize, tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
1161
- {
1162
- // set passed in params
1163
- ASSERT(pos && pos.x != undefined && size.x != undefined); // ensure pos and size are vec2s
1164
-
1165
- /** @property {Vector2} - World space position of the object */
1166
- this.pos = pos.copy();
1167
- /** @property {Vector2} - World space width and height of the object */
1168
- this.size = size;
1169
- /** @property {Vector2} - Size of object used for drawing, uses size if not set */
1170
- this.drawSize;
1171
- /** @property {Number} - Tile to use to render object (-1 is untextured) */
1172
- this.tileIndex = tileIndex;
1173
- /** @property {Vector2} - Size of tile in source pixels */
1174
- this.tileSize = tileSize;
1175
- /** @property {Number} - Angle to rotate the object */
1176
- this.angle = angle;
1177
- /** @property {Color} - Color to apply when rendered */
1178
- this.color = color;
1179
- /** @property {Color} - Additive color to apply when rendered */
1180
- this.additiveColor;
1181
-
1182
- // set object defaults
1183
- /** @property {Number} [mass=objectDefaultMass] - How heavy the object is, static if 0 */
1184
- this.mass = objectDefaultMass;
1185
- /** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
1186
- this.damping = objectDefaultDamping;
1187
- /** @property {Number} [angleDamping=objectDefaultAngleDamping] - How much to slow down rotation each frame (0-1) */
1188
- this.angleDamping = objectDefaultAngleDamping;
1189
- /** @property {Number} [elasticity=objectDefaultElasticity] - How bouncy the object is when colliding (0-1) */
1190
- this.elasticity = objectDefaultElasticity;
1191
- /** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
1192
- this.friction = objectDefaultFriction;
1193
- /** @property {Number} [gravityScale=1] - How much to scale gravity by for this object */
1194
- this.gravityScale = 1;
1195
- /** @property {Number} [renderOrder=0] - Objects are sorted by render order */
1196
- this.renderOrder = renderOrder;
1197
- /** @property {Vector2} [velocity=new Vector2()] - Velocity of the object */
1198
- this.velocity = new Vector2();
1199
- /** @property {Number} [angleVelocity=0] - Angular velocity of the object */
1200
- this.angleVelocity = 0;
1201
-
1202
- // init other internal object stuff
1203
- this.spawnTime = time;
1204
- this.children = [];
1205
- this.collideTiles = 1;
1206
-
1207
- // add to list of objects
1208
- engineObjects.push(this);
1209
- }
1210
-
1211
- /** Update the object transform and physics, called automatically by engine once each frame */
1212
- update()
1213
- {
1214
- const parent = this.parent;
1215
- if (parent)
1216
- {
1217
- // copy parent pos/angle
1218
- this.pos = this.localPos.multiply(vec2(parent.getMirrorSign(),1)).rotate(-parent.angle).add(parent.pos);
1219
- this.angle = parent.getMirrorSign()*this.localAngle + parent.angle;
1220
- return;
1221
- }
1222
-
1223
- // limit max speed to prevent missing collisions
1224
- this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
1225
- this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
1226
-
1227
- // apply physics
1228
- const oldPos = this.pos.copy();
1229
- this.pos.x += this.velocity.x = this.damping * this.velocity.x;
1230
- this.pos.y += this.velocity.y = this.damping * this.velocity.y + gravity * this.gravityScale;
1231
- this.angle += this.angleVelocity *= this.angleDamping;
1232
-
1233
- // physics sanity checks
1234
- ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
1235
- ASSERT(this.damping >= 0 && this.damping <= 1);
1236
-
1237
- if (!enablePhysicsSolver || !this.mass) // do not update collision for fixed objects
1238
- return;
1239
-
1240
- const wasMovingDown = this.velocity.y < 0;
1241
- if (this.groundObject)
1242
- {
1243
- // apply friction in local space of ground object
1244
- const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
1245
- this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * this.friction;
1246
- this.groundObject = 0;
1247
- //debugOverlay && debugPhysics && debugPoint(this.pos.subtract(vec2(0,this.size.y/2)), '#0f0');
1248
- }
1249
-
1250
- if (this.collideSolidObjects)
1251
- {
1252
- // check collisions against solid objects
1253
- const epsilon = 1e-3; // necessary to push slightly outside of the collision
1254
- for (const o of engineObjectsCollide)
1255
- {
1256
- // non solid objects don't collide with eachother
1257
- if (!this.isSolid & !o.isSolid || o.destroyed || o.parent || o == this)
1258
- continue;
1259
-
1260
- // check collision
1261
- if (!isOverlapping(this.pos, this.size, o.pos, o.size))
1262
- continue;
1263
-
1264
- // pass collision to objects
1265
- if (!this.collideWithObject(o) | !o.collideWithObject(this))
1266
- continue;
1267
-
1268
- if (isOverlapping(oldPos, this.size, o.pos, o.size))
1269
- {
1270
- // if already was touching, try to push away
1271
- const deltaPos = oldPos.subtract(o.pos);
1272
- const length = deltaPos.length();
1273
- const pushAwayAccel = .001; // push away if already overlapping
1274
- const velocity = length < .01 ? randVector(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
1275
- this.velocity = this.velocity.add(velocity);
1276
- if (o.mass) // push away if not fixed
1277
- o.velocity = o.velocity.subtract(velocity);
1278
-
1279
- debugOverlay && debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f00');
1280
- continue;
1281
- }
1282
-
1283
- // check for collision
1284
- const sizeBoth = this.size.add(o.size);
1285
- const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity; // prefer to push up if small delta
1286
- const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
1287
- const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
1288
-
1289
- if (smallStepUp || isBlockedY || !isBlockedX) // resolve y collision
1290
- {
1291
- // push outside object collision
1292
- this.pos.y = o.pos.y + (sizeBoth.y/2 + epsilon) * sign(oldPos.y - o.pos.y);
1293
- if (o.groundObject && wasMovingDown || !o.mass)
1294
- {
1295
- // set ground object if landed on something
1296
- if (wasMovingDown)
1297
- this.groundObject = o;
1298
-
1299
- // bounce if other object is fixed or grounded
1300
- this.velocity.y *= -this.elasticity;
1301
- }
1302
- else if (o.mass)
1303
- {
1304
- // inelastic collision
1305
- const inelastic = (this.mass * this.velocity.y + o.mass * o.velocity.y) / (this.mass + o.mass);
1306
-
1307
- // elastic collision
1308
- const elastic0 = this.velocity.y * (this.mass - o.mass) / (this.mass + o.mass)
1309
- + o.velocity.y * 2 * o.mass / (this.mass + o.mass);
1310
- const elastic1 = o.velocity.y * (o.mass - this.mass) / (this.mass + o.mass)
1311
- + this.velocity.y * 2 * this.mass / (this.mass + o.mass);
1312
-
1313
- // lerp betwen elastic or inelastic based on elasticity
1314
- const elasticity = max(this.elasticity, o.elasticity);
1315
- this.velocity.y = lerp(elasticity, inelastic, elastic0);
1316
- o.velocity.y = lerp(elasticity, inelastic, elastic1);
1317
- }
1318
- }
1319
- if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
1320
- {
1321
- // push outside collision
1322
- this.pos.x = o.pos.x + (sizeBoth.x/2 + epsilon) * sign(oldPos.x - o.pos.x);
1323
- if (o.mass)
1324
- {
1325
- // inelastic collision
1326
- const inelastic = (this.mass * this.velocity.x + o.mass * o.velocity.x) / (this.mass + o.mass);
1327
-
1328
- // elastic collision
1329
- const elastic0 = this.velocity.x * (this.mass - o.mass) / (this.mass + o.mass)
1330
- + o.velocity.x * 2 * o.mass / (this.mass + o.mass);
1331
- const elastic1 = o.velocity.x * (o.mass - this.mass) / (this.mass + o.mass)
1332
- + this.velocity.x * 2 * this.mass / (this.mass + o.mass);
1333
-
1334
- // lerp betwen elastic or inelastic based on elasticity
1335
- const elasticity = max(this.elasticity, o.elasticity);
1336
- this.velocity.x = lerp(elasticity, inelastic, elastic0);
1337
- o.velocity.x = lerp(elasticity, inelastic, elastic1);
1338
- }
1339
- else // bounce if other object is fixed
1340
- this.velocity.x *= -this.elasticity;
1341
- }
1342
- debugOverlay && debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
1343
- }
1344
- }
1345
- if (this.collideTiles)
1346
- {
1347
- // check collision against tiles
1348
- if (tileCollisionTest(this.pos, this.size, this))
1349
- {
1350
- // if already was stuck in collision, don't do anything
1351
- // this should not happen unless something starts in collision
1352
- if (!tileCollisionTest(oldPos, this.size, this))
1353
- {
1354
- // test which side we bounced off (or both if a corner)
1355
- const isBlockedY = tileCollisionTest(new Vector2(oldPos.x, this.pos.y), this.size, this);
1356
- const isBlockedX = tileCollisionTest(new Vector2(this.pos.x, oldPos.y), this.size, this);
1357
- if (isBlockedY || !isBlockedX)
1358
- {
1359
- // set if landed on ground
1360
- this.groundObject = wasMovingDown;
1361
-
1362
- // bounce velocity
1363
- this.velocity.y *= -this.elasticity;
1364
-
1365
- // adjust next velocity to settle on ground
1366
- const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
1367
- if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
1368
- this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
1369
-
1370
- // move to previous position
1371
- this.pos.y = oldPos.y;
1372
- }
1373
- if (isBlockedX)
1374
- {
1375
- // move to previous position and bounce
1376
- this.pos.x = oldPos.x;
1377
- this.velocity.x *= -this.elasticity;
1378
- }
1379
- }
1380
- }
1381
- }
1382
- }
1383
-
1384
- /** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
1385
- render()
1386
- {
1387
- // default object render
1388
- drawTile(this.pos, this.drawSize || this.size, this.tileIndex, this.tileSize, this.color, this.angle, this.mirror, this.additiveColor);
1389
- }
1390
-
1391
- /** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
1392
- destroy()
1393
- {
1394
- if (this.destroyed)
1395
- return;
1396
-
1397
- // disconnect from parent and destroy chidren
1398
- this.destroyed = 1;
1399
- this.parent && this.parent.removeChild(this);
1400
- for (const child of this.children)
1401
- child.destroy(child.parent = 0);
1402
- }
1403
-
1404
- /** Called to check if a tile collision should be resolved
1405
- * @param {Number} tileData - the value of the tile at the position
1406
- * @param {Vector2} pos - tile where the collision occured
1407
- * @return {Boolean} - true if the collision should be resolved */
1408
- collideWithTile(tileData, pos) { return tileData > 0; }
1409
-
1410
- /** Called to check if a tile raycast hit
1411
- * @param {Number} tileData - the value of the tile at the position
1412
- * @param {Vector2} pos - tile where the raycast is
1413
- * @return {Boolean} - true if the raycast should hit */
1414
- collideWithTileRaycast(tileData, pos) { return tileData > 0; }
1415
-
1416
- /** Called to check if a object collision should be resolved
1417
- * @param {EngineObject} object - the object to test against
1418
- * @return {Boolean} - true if the collision should be resolved
1419
- */
1420
- collideWithObject(o) { return 1; }
1421
-
1422
- /** How long since the object was created
1423
- * @return {Number} */
1424
- getAliveTime() { return time - this.spawnTime; }
1425
-
1426
- /** Apply acceleration to this object (adjust velocity, not affected by mass)
1427
- * @param {Vector2} acceleration */
1428
- applyAcceleration(a) { if (this.mass) this.velocity = this.velocity.add(a); }
1429
-
1430
- /** Apply force to this object (adjust velocity, affected by mass)
1431
- * @param {Vector2} force */
1432
- applyForce(force) { this.applyAcceleration(force.scale(1/this.mass)); }
1433
-
1434
- /** Get the direction of the mirror
1435
- * @return {Number} -1 if this.mirror is true, or 1 if not mirrored */
1436
- getMirrorSign() { return this.mirror ? -1 : 1; }
1437
-
1438
- /** Attaches a child to this with a given local transform
1439
- * @param {EngineObject} child
1440
- * @param {Vector2} [localPos=new Vector2]
1441
- * @param {Number} [localAngle=0] */
1442
- addChild(child, localPos=vec2(), localAngle=0)
1443
- {
1444
- ASSERT(!child.parent && !this.children.includes(child));
1445
- this.children.push(child);
1446
- child.parent = this;
1447
- child.localPos = localPos.copy();
1448
- child.localAngle = localAngle;
1449
- }
1450
-
1451
- /** Removes a child from this one
1452
- * @param {EngineObject} child */
1453
- removeChild(child)
1454
- {
1455
- ASSERT(child.parent == this && this.children.includes(child));
1456
- this.children.splice(this.children.indexOf(child), 1);
1457
- child.parent = 0;
1458
- }
1459
-
1460
- /** Set how this object collides
1461
- * @param {boolean} [collideSolidObjects=1] - Does it collide with solid objects
1462
- * @param {boolean} [isSolid=1] - Does it collide with and block other objects (expensive in large numbers)
1463
- * @param {boolean} [collideTiles=1] - Does it collide with the tile collision */
1464
- setCollision(collideSolidObjects=1, isSolid=1, collideTiles=1)
1465
- {
1466
- ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
1467
-
1468
- this.collideSolidObjects = collideSolidObjects;
1469
- this.isSolid = isSolid;
1470
- this.collideTiles = collideTiles;
1471
- }
1472
-
1473
- toString()
1474
- {
1475
- if (debug)
1476
- {
1477
- let text = 'type = ' + this.constructor.name;
1478
- if (this.pos.x || this.pos.y)
1479
- text += '\npos = ' + this.pos;
1480
- if (this.velocity.x || this.velocity.y)
1481
- text += '\nvelocity = ' + this.velocity;
1482
- if (this.size.x || this.size.y)
1483
- text += '\nsize = ' + this.size;
1484
- if (this.angle)
1485
- text += '\nangle = ' + this.angle.toFixed(3);
1486
- if (this.color)
1487
- text += '\ncolor = ' + this.color;
1488
- return text;
1489
- }
1490
- }
1491
- }
1492
- /**
1493
- * LittleJS Drawing System
1494
- * <br> - Hybrid with both Canvas2D and WebGL available
1495
- * <br> - Super fast tile sheet rendering with WebGL
1496
- * <br> - Can apply rotation, mirror, color and additive color
1497
- * <br> - Many useful utility functions
1498
- * <br>
1499
- * <br>LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
1500
- * <br>There are 3 canvas/contexts available to draw to...
1501
- * <br> - mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
1502
- * <br> - glCanvas - Used by the accelerated WebGL batch rendering system.
1503
- * <br> - overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
1504
- * <br>
1505
- * <br>The WebGL rendering system is very fast with some caveats...
1506
- * <br> - The default setup supports only 1 tile sheet, to support more call glCreateTexture and glSetTexture
1507
- * <br> - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
1508
- * <br> - Group additive rendering together using renderOrder to mitigate this issue
1509
- * <br>
1510
- * <br>The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
1511
- * @namespace Draw
1512
- */
1513
-
1514
- 'use strict';
1515
-
1516
- /** Tile sheet for batch rendering system
1517
- * @type {Image}
1518
- * @memberof Draw */
1519
- const tileImage = new Image();
1520
-
1521
- /** The primary 2D canvas visible to the user
1522
- * @type {HTMLCanvasElement}
1523
- * @memberof Draw */
1524
- let mainCanvas;
1525
-
1526
- /** 2d context for mainCanvas
1527
- * @type {CanvasRenderingContext2D}
1528
- * @memberof Draw */
1529
- let mainContext;
1530
-
1531
- /** A canvas that appears on top of everything the same size as mainCanvas
1532
- * @type {HTMLCanvasElement}
1533
- * @memberof Draw */
1534
- let overlayCanvas;
1535
-
1536
- /** 2d context for overlayCanvas
1537
- * @type {CanvasRenderingContext2D}
1538
- * @memberof Draw */
1539
- let overlayContext;
1540
-
1541
- /** The size of the main canvas (and other secondary canvases)
1542
- * @type {Vector2}
1543
- * @memberof Draw */
1544
- let mainCanvasSize = vec2();
1545
-
1546
- /** Convert from screen to world space coordinates
1547
- * - if calling outside of render, you may need to manually set mainCanvasSize
1548
- * @param {Vector2} screenPos
1549
- * @return {Vector2}
1550
- * @memberof Draw */
1551
- const screenToWorld = (screenPos)=>
1552
- {
1553
- ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
1554
- return screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
1555
- }
1556
-
1557
- /** Convert from world to screen space coordinates
1558
- * - if calling outside of render, you may need to manually set mainCanvasSize
1559
- * @param {Vector2} worldPos
1560
- * @return {Vector2}
1561
- * @memberof Draw */
1562
- const worldToScreen = (worldPos)=>
1563
- {
1564
- ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
1565
- return worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
1566
- }
1567
-
1568
- /** Draw textured tile centered in world space, with color applied if using WebGL
1569
- * @param {Vector2} pos - Center of the tile in world space
1570
- * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space, width and height
1571
- * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
1572
- * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1573
- * @param {Color} [color=new Color(1,1,1)] - Color to modulate with
1574
- * @param {Number} [angle=0] - Angle to rotate by
1575
- * @param {Boolean} [mirror=0] - If true image is flipped along the Y axis
1576
- * @param {Color} [additiveColor=new Color(0,0,0,0)] - Additive color to be applied
1577
- * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
1578
- * @memberof Draw */
1579
- function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle=0, mirror,
1580
- additiveColor=new Color(0,0,0,0), useWebGL=glEnable)
1581
- {
1582
- showWatermark && ++drawCount;
1583
- if (glEnable && useWebGL)
1584
- {
1585
- if (tileIndex < 0 || !tileImage.width)
1586
- {
1587
- // if negative tile index or image not found, force untextured
1588
- glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
1589
- }
1590
- else
1591
- {
1592
- // calculate uvs and render
1593
- const cols = tileImageSize.x / tileSize.x |0;
1594
- const uvSizeX = tileSize.x / tileImageSize.x;
1595
- const uvSizeY = tileSize.y / tileImageSize.y;
1596
- const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
1597
-
1598
- glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
1599
- uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
1600
- uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
1601
- color.rgbaInt(), additiveColor.rgbaInt());
1602
- }
1603
- }
1604
- else
1605
- {
1606
- // normal canvas 2D rendering method (slower)
1607
- drawCanvas2D(pos, size, angle, mirror, (context)=>
1608
- {
1609
- if (tileIndex < 0)
1610
- {
1611
- // if negative tile index, force untextured
1612
- context.fillStyle = color;
1613
- context.fillRect(-.5, -.5, 1, 1);
1614
- }
1615
- else
1616
- {
1617
- // calculate uvs and render
1618
- const cols = tileImageSize.x / tileSize.x |0;
1619
- const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
1620
- const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
1621
- const sWidth = tileSize.x - 2*tileFixBleedScale;
1622
- const sHeight = tileSize.y - 2*tileFixBleedScale;
1623
- context.globalAlpha = color.a; // only alpha is supported
1624
- context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
1625
- }
1626
- });
1627
- }
1628
- }
1629
-
1630
- /** Draw colored rect centered on pos
1631
- * @param {Vector2} pos
1632
- * @param {Vector2} [size=new Vector2(1,1)]
1633
- * @param {Color} [color=new Color(1,1,1)]
1634
- * @param {Number} [angle=0]
1635
- * @param {Boolean} [useWebGL=glEnable]
1636
- * @memberof Draw */
1637
- function drawRect(pos, size, color, angle, useWebGL)
1638
- {
1639
- drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
1640
- }
1641
-
1642
- /** Draw textured tile centered on pos in screen space
1643
- * @param {Vector2} pos - Center of the tile
1644
- * @param {Vector2} [size=new Vector2(1,1)] - Size of the tile
1645
- * @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
1646
- * @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
1647
- * @param {Color} [color=new Color]
1648
- * @param {Number} [angle=0]
1649
- * @param {Boolean} [mirror=0]
1650
- * @param {Color} [additiveColor=new Color(0,0,0,0)]
1651
- * @param {Boolean} [useWebGL=glEnable]
1652
- * @memberof Draw */
1653
- function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL)
1654
- {
1655
- drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL);
1656
- }
1657
-
1658
- /** Draw colored rectangle in screen space
1659
- * @param {Vector2} pos
1660
- * @param {Vector2} [size=new Vector2(1,1)]
1661
- * @param {Color} [color=new Color(1,1,1)]
1662
- * @param {Number} [angle=0]
1663
- * @param {Boolean} [useWebGL=glEnable]
1664
- * @memberof Draw */
1665
- function drawRectScreenSpace(pos, size, color, angle, useWebGL)
1666
- {
1667
- drawTileScreenSpace(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
1668
- }
1669
-
1670
- /** Draw colored line between two points
1671
- * @param {Vector2} posA
1672
- * @param {Vector2} posB
1673
- * @param {Number} [thickness=.1]
1674
- * @param {Color} [color=new Color(1,1,1)]
1675
- * @param {Boolean} [useWebGL=glEnable]
1676
- * @memberof Draw */
1677
- function drawLine(posA, posB, thickness=.1, color, useWebGL)
1678
- {
1679
- const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
1680
- const size = vec2(thickness, halfDelta.length()*2);
1681
- drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL);
1682
- }
1683
-
1684
- /** Draw directly to a 2d canvas context in world space
1685
- * @param {Vector2} pos
1686
- * @param {Vector2} size
1687
- * @param {Number} angle
1688
- * @param {Boolean} mirror
1689
- * @param {Function} drawFunction
1690
- * @param {CanvasRenderingContext2D} [context=mainContext]
1691
- * @memberof Draw */
1692
- function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainContext)
1693
- {
1694
- // create canvas transform from world space to screen space
1695
- pos = worldToScreen(pos);
1696
- size = size.scale(cameraScale);
1697
- context.save();
1698
- context.translate(pos.x+.5|0, pos.y+.5|0);
1699
- context.rotate(angle);
1700
- context.scale(mirror ? -size.x : size.x, size.y);
1701
- drawFunction(context);
1702
- context.restore();
1703
- }
1704
-
1705
- /** Enable normal or additive blend mode
1706
- * @param {Boolean} [additive=0]
1707
- * @param {Boolean} [useWebGL=glEnable]
1708
- * @memberof Draw */
1709
- function setBlendMode(additive, useWebGL=glEnable)
1710
- {
1711
- if (glEnable && useWebGL)
1712
- glSetBlendMode(additive);
1713
- else
1714
- mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
1715
- }
1716
-
1717
- /** Draw text on overlay canvas in screen space
1718
- * Automatically splits new lines into rows
1719
- * @param {String} text
1720
- * @param {Vector2} pos
1721
- * @param {Number} [size=1]
1722
- * @param {Color} [color=new Color(1,1,1)]
1723
- * @param {Number} [lineWidth=0]
1724
- * @param {Color} [lineColor=new Color(0,0,0)]
1725
- * @param {String} [textAlign='center']
1726
- * @memberof Draw */
1727
- function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
1728
- {
1729
- context.fillStyle = color;
1730
- context.lineWidth = lineWidth;
1731
- context.strokeStyle = lineColor;
1732
- context.textAlign = textAlign;
1733
- context.font = size + 'px '+ font;
1734
- context.textBaseline = 'middle';
1735
- context.lineJoin = 'round';
1736
-
1737
- pos = pos.copy();
1738
- (text+'').split('\n').forEach(line=>
1739
- {
1740
- lineWidth && context.strokeText(line, pos.x, pos.y);
1741
- context.fillText(line, pos.x, pos.y);
1742
- pos.y += size;
1743
- });
1744
- }
1745
-
1746
- /** Draw text on overlay canvas in world space
1747
- * Automatically splits new lines into rows
1748
- * @param {String} text
1749
- * @param {Vector2} pos
1750
- * @param {Number} [size=1]
1751
- * @param {Color} [color=new Color(1,1,1)]
1752
- * @param {Number} [lineWidth=0]
1753
- * @param {Color} [lineColor=new Color(0,0,0)]
1754
- * @param {String} [textAlign='center']
1755
- * @memberof Draw */
1756
- function drawText(text, pos, size=1, color, lineWidth, lineColor, textAlign, font)
1757
- {
1758
- drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, mainContext);
1759
- }
1760
-
1761
- ///////////////////////////////////////////////////////////////////////////////
1762
-
1763
- /**
1764
- * Font Image Object - Draw text on a 2D canvas by using characters in an image
1765
- * <br> - 96 characters (from space to tilde) are stored in an image
1766
- * <br> - Uses a default 8x8 font if none is supplied
1767
- * <br> - You can also use fonts from the main tile sheet
1768
- * @example
1769
- * // use built in font
1770
- * const font = new ImageFont;
1771
- *
1772
- * // draw text
1773
- * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
1774
- */
1775
-
1776
- let engineFontImage;
1777
-
1778
- class FontImage
1779
- {
1780
- /** Create an image font
1781
- * @param {HTMLImageElement} [image] - The image the font is stored in, if undefined the default font is used
1782
- * @param {Vector2} [tileSize=vec2(8)] - The size of the font source tiles
1783
- * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
1784
- * @param {Number} [startTileIndex=0] - Tile index in image where font starts
1785
- * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
1786
- */
1787
- constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
1788
- {
1789
- if (!image && !engineFontImage)
1790
- {
1791
- // load default font image
1792
- engineFontImage = new Image();
1793
- engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
1794
- }
1795
-
1796
- this.image = image || engineFontImage;
1797
- this.tileSize = tileSize;
1798
- this.paddingSize = paddingSize;
1799
- this.startTileIndex = startTileIndex;
1800
- this.context = context;
1801
- }
1802
-
1803
- /** Draw text in screen space using the image font
1804
- * @param {String} text
1805
- * @param {Vector2} pos
1806
- * @param {Number} [scale=4]
1807
- * @param {Boolean} [center]
1808
- */
1809
- drawTextScreen(text, pos, scale=4, center)
1810
- {
1811
- const context = this.context;
1812
- context.save();
1813
- context.imageSmoothingEnabled = !cavasPixelated;
1814
-
1815
- const size = this.tileSize;
1816
- const drawSize = size.add(this.paddingSize).scale(scale);
1817
- const cols = this.image.width / this.tileSize.x |0;
1818
- (text+'').split('\n').forEach((line, i)=>
1819
- {
1820
- const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
1821
- for(let j=line.length; j--;)
1822
- {
1823
- // draw each character
1824
- let charCode = line[j].charCodeAt();
1825
- if (charCode < 32 || charCode > 127)
1826
- charCode = 127; // unknown character
1827
-
1828
- // get the character source location and draw it
1829
- const tile = this.startTileIndex + charCode - 32;
1830
- const x = tile % cols;
1831
- const y = tile / cols |0;
1832
- const drawPos = pos.add(vec2(j,i).multiply(drawSize));
1833
- context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
1834
- drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
1835
- }
1836
- });
1837
-
1838
- context.restore();
1839
- }
1840
-
1841
- /** Draw text in world space using the image font
1842
- * @param {String} text
1843
- * @param {Vector2} pos
1844
- * @param {Number} [scale=.25]
1845
- * @param {Boolean} [center]
1846
- */
1847
- drawText(text, pos, scale=1, center)
1848
- {
1849
- this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
1850
- }
1851
- }
1852
-
1853
- ///////////////////////////////////////////////////////////////////////////////
1854
- // Fullscreen mode
1855
-
1856
- /** Returns true if fullscreen mode is active
1857
- * @return {Boolean}
1858
- * @memberof Draw */
1859
- const isFullscreen =()=> document.fullscreenElement;
1860
-
1861
- /** Toggle fullsceen mode
1862
- * @memberof Draw */
1863
- function toggleFullscreen()
1864
- {
1865
- if (isFullscreen())
1866
- {
1867
- if (document.exitFullscreen)
1868
- document.exitFullscreen();
1869
- else if (document.mozCancelFullScreen)
1870
- document.mozCancelFullScreen();
1871
- }
1872
- else
1873
- {
1874
- if (document.body.webkitRequestFullScreen)
1875
- document.body.webkitRequestFullScreen();
1876
- else if (document.body.mozRequestFullScreen)
1877
- document.body.mozRequestFullScreen();
1878
- }
1879
- }
1880
-
1881
- /**
1882
- * LittleJS Input System
1883
- * <br> - Tracks key down, pressed, and released
1884
- * <br> - Also tracks mouse buttons, position, and wheel
1885
- * <br> - Supports multiple gamepads
1886
- * <br> - Virtual gamepad for touch devices with touchGamepadSize
1887
- * @namespace Input
1888
- */
1889
-
1890
- 'use strict';
1891
-
1892
- /** Returns true if device key is down
1893
- * @param {Number} key
1894
- * @param {Number} [device=0]
1895
- * @return {Boolean}
1896
- * @memberof Input */
1897
- const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1 ? 1 : 0;
1898
-
1899
- /** Returns true if device key was pressed this frame
1900
- * @param {Number} key
1901
- * @param {Number} [device=0]
1902
- * @return {Boolean}
1903
- * @memberof Input */
1904
- const keyWasPressed = (key, device=0)=> inputData[device] && inputData[device][key] & 2 ? 1 : 0;
1905
-
1906
- /** Returns true if device key was released this frame
1907
- * @param {Number} key
1908
- * @param {Number} [device=0]
1909
- * @return {Boolean}
1910
- * @memberof Input */
1911
- const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][key] & 4 ? 1 : 0;
1912
-
1913
- /** Clears all input
1914
- * @memberof Input */
1915
- const clearInput = ()=> inputData = [[]];
1916
-
1917
- /** Returns true if mouse button is down
1918
- * @param {Number} button
1919
- * @return {Boolean}
1920
- * @memberof Input */
1921
- const mouseIsDown = keyIsDown;
1922
-
1923
- /** Returns true if mouse button was pressed
1924
- * @param {Number} button
1925
- * @return {Boolean}
1926
- * @memberof Input */
1927
- const mouseWasPressed = keyWasPressed;
1928
-
1929
- /** Returns true if mouse button was released
1930
- * @param {Number} button
1931
- * @return {Boolean}
1932
- * @memberof Input */
1933
- const mouseWasReleased = keyWasReleased;
1934
-
1935
- /** Mouse pos in world space
1936
- * @type {Vector2}
1937
- * @memberof Input */
1938
- let mousePos = vec2();
1939
-
1940
- /** Mouse pos in screen space
1941
- * @type {Vector2}
1942
- * @memberof Input */
1943
- let mousePosScreen = vec2();
1944
-
1945
- /** Mouse wheel delta this frame
1946
- * @memberof Input */
1947
- let mouseWheel = 0;
1948
-
1949
- /** Returns true if user is using gamepad (has more recently pressed a gamepad button)
1950
- * @memberof Input */
1951
- let isUsingGamepad = 0;
1952
-
1953
- /** Prevents input continuing to the default browser handling (false by default)
1954
- * @memberof Input */
1955
- let preventDefaultInput = 0;
1956
-
1957
- /** Returns true if gamepad button is down
1958
- * @param {Number} button
1959
- * @param {Number} [gamepad=0]
1960
- * @return {Boolean}
1961
- * @memberof Input */
1962
- const gamepadIsDown = (button, gamepad=0)=> keyIsDown(button, gamepad+1);
1963
-
1964
- /** Returns true if gamepad button was pressed
1965
- * @param {Number} button
1966
- * @param {Number} [gamepad=0]
1967
- * @return {Boolean}
1968
- * @memberof Input */
1969
- const gamepadWasPressed = (button, gamepad=0)=> keyWasPressed(button, gamepad+1);
1970
-
1971
- /** Returns true if gamepad button was released
1972
- * @param {Number} button
1973
- * @param {Number} [gamepad=0]
1974
- * @return {Boolean}
1975
- * @memberof Input */
1976
- const gamepadWasReleased = (button, gamepad=0)=> keyWasReleased(button, gamepad+1);
1977
-
1978
- /** Returns gamepad stick value
1979
- * @param {Number} stick
1980
- * @param {Number} [gamepad=0]
1981
- * @return {Vector2}
1982
- * @memberof Input */
1983
- const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2();
1984
-
1985
- ///////////////////////////////////////////////////////////////////////////////
1986
- // Input update called by engine
1987
-
1988
- // store input as a bit field for each key: 1 = isDown, 2 = wasPressed, 4 = wasReleased
1989
- // mouse and keyboard are stored together in device 0, gamepads are in devices > 0
1990
- let inputData = [[]];
1991
-
1992
- function inputUpdate()
1993
- {
1994
- // clear input when lost focus (prevent stuck keys)
1995
- isTouchDevice || document.hasFocus() || clearInput();
1996
-
1997
- // update mouse world space position
1998
- mousePos = screenToWorld(mousePosScreen);
1999
-
2000
- // update gamepads if enabled
2001
- gamepadsUpdate();
2002
- }
2003
-
2004
- function inputUpdatePost()
2005
- {
2006
- // clear input to prepare for next frame
2007
- for (const deviceInputData of inputData)
2008
- for (const i in deviceInputData)
2009
- deviceInputData[i] &= 1;
2010
- mouseWheel = 0;
2011
- }
2012
-
2013
- ///////////////////////////////////////////////////////////////////////////////
2014
- // Keyboard event handlers
2015
-
2016
- onkeydown = (e)=>
2017
- {
2018
- if (debug && e.target != document.body) return;
2019
- e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
2020
- preventDefaultInput && e.preventDefault();
2021
- }
2022
- onkeyup = (e)=>
2023
- {
2024
- if (debug && e.target != document.body) return;
2025
- inputData[0][remapKeyCode(e.keyCode)] = 4;
2026
- }
2027
- const remapKeyCode = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
2028
-
2029
- ///////////////////////////////////////////////////////////////////////////////
2030
- // Mouse event handlers
2031
-
2032
- onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
2033
- onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
2034
- onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
2035
- onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
2036
- oncontextmenu = (e)=> !1; // prevent right click menu
2037
-
2038
- // convert a mouse or touch event position to screen space
2039
- const mouseToScreen = (mousePos)=>
2040
- {
2041
- if (!mainCanvas)
2042
- return vec2(); // fix bug that can occur if user clicks before page loads
2043
-
2044
- const rect = mainCanvas.getBoundingClientRect();
2045
- return vec2(mainCanvas.width, mainCanvas.height).multiply(
2046
- vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
2047
- }
2048
-
2049
- ///////////////////////////////////////////////////////////////////////////////
2050
- // Gamepad input
2051
-
2052
- const stickData = [];
2053
- function gamepadsUpdate()
2054
- {
2055
- if (touchGamepadEnable && touchGamepadTimer.isSet())
2056
- {
2057
- // read virtual analog stick
2058
- const sticks = stickData[0] || (stickData[0] = []);
2059
- sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
2060
-
2061
- // read virtual gamepad buttons
2062
- const data = inputData[1] || (inputData[1] = []);
2063
- for (let i=10; i--;)
2064
- {
2065
- const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
2066
- data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
2067
- }
2068
- }
2069
-
2070
- if (!gamepadsEnable || !navigator || !navigator.getGamepads || !document.hasFocus() && !debug)
2071
- return;
2072
-
2073
- // poll gamepads
2074
- const gamepads = navigator.getGamepads();
2075
- for (let i = gamepads.length; i--;)
2076
- {
2077
- // get or create gamepad data
2078
- const gamepad = gamepads[i];
2079
- const data = inputData[i+1] || (inputData[i+1] = []);
2080
- const sticks = stickData[i] || (stickData[i] = []);
2081
-
2082
- if (gamepad)
2083
- {
2084
- // read clamp dead zone of analog sticks
2085
- const deadZone = .3, deadZoneMax = .8;
2086
- const applyDeadZone = (v)=>
2087
- v > deadZone ? percent( v, deadZone, deadZoneMax) :
2088
- v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
2089
-
2090
- // read analog sticks
2091
- for (let j = 0; j < gamepad.axes.length-1; j+=2)
2092
- sticks[j>>1] = vec2(applyDeadZone(gamepad.axes[j]), applyDeadZone(-gamepad.axes[j+1])).clampLength();
2093
-
2094
- // read buttons
2095
- for (let j = gamepad.buttons.length; j--;)
2096
- {
2097
- const button = gamepad.buttons[j];
2098
- data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
2099
- isUsingGamepad |= !i && button.pressed;
2100
- touchGamepadEnable && touchGamepadTimer.unset(); // disable touch gamepad if using real gamepad
2101
- }
2102
-
2103
- if (gamepadDirectionEmulateStick)
2104
- {
2105
- // copy dpad to left analog stick when pressed
2106
- const dpad = vec2(gamepadIsDown(15,i) - gamepadIsDown(14,i), gamepadIsDown(12,i) - gamepadIsDown(13,i));
2107
- if (dpad.lengthSquared())
2108
- sticks[0] = dpad.clampLength();
2109
- }
2110
- }
2111
- }
2112
- }
2113
-
2114
- ///////////////////////////////////////////////////////////////////////////////
2115
-
2116
- /** Pulse the vibration hardware if it exists
2117
- * @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
2118
- * @memberof Input */
2119
- const vibrate = (pattern)=> vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern);
2120
-
2121
- /** Cancel any ongoing vibration
2122
- * @memberof Input */
2123
- const vibrateStop = ()=> vibrate(0);
2124
-
2125
- ///////////////////////////////////////////////////////////////////////////////
2126
- // Touch input
2127
-
2128
- /** True if a touch device has been detected
2129
- * @const {boolean}
2130
- * @memberof Input */
2131
- const isTouchDevice = window.ontouchstart !== undefined;
2132
-
2133
- // try to enable touch mouse
2134
- if (isTouchDevice)
2135
- {
2136
- // override mouse events
2137
- const mouseDown = onmousedown, mouseUp = onmouseup, mouseMove = onmousemove;
2138
- onmousedown = onmouseup = onmousemove = (e)=> 0;
2139
-
2140
- // handle all touch events the same way
2141
- let wasTouching, hadTouch;
2142
- ontouchstart = ontouchmove = ontouchend = (e)=>
2143
- {
2144
- e.button = 0; // all touches are left click
2145
-
2146
- // check if touching and pass to mouse events
2147
- const touching = e.touches.length;
2148
- if (touching)
2149
- {
2150
- // fix mobile audio, force it to play a sound on first touch
2151
- hadTouch || zzfx(0, hadTouch=1);
2152
-
2153
- // set event pos and pass it along
2154
- e.x = e.touches[0].clientX;
2155
- e.y = e.touches[0].clientY;
2156
- wasTouching ? mouseMove(e) : mouseDown(e);
2157
- }
2158
- else if (wasTouching)
2159
- mouseUp(e);
2160
-
2161
- // set was touching
2162
- wasTouching = touching;
2163
-
2164
- // must return true so the document will get focus
2165
- return true;
2166
- }
2167
- }
2168
-
2169
- ///////////////////////////////////////////////////////////////////////////////
2170
- // touch gamepad, virtual on screen gamepad emulator for touch devices
2171
-
2172
- // touch input internal variables
2173
- let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
2174
-
2175
- // create the touch gamepad, called automatically by the engine
2176
- function touchGamepadCreate()
2177
- {
2178
- if (!touchGamepadEnable || !isTouchDevice)
2179
- return;
2180
-
2181
- ontouchstart = ontouchmove = ontouchend = (e)=>
2182
- {
2183
- if (!touchGamepadEnable)
2184
- return;
2185
-
2186
- // clear touch gamepad input
2187
- touchGamepadStick = vec2();
2188
- touchGamepadButtons = [];
2189
-
2190
- const touching = e.touches.length;
2191
- if (touching)
2192
- {
2193
- touchGamepadTimer.isSet() || zzfx(0) ; // fix mobile audio, force it to play a sound the first time
2194
-
2195
- // set that gamepad is active
2196
- isUsingGamepad = 1;
2197
- touchGamepadTimer.set();
2198
-
2199
- if (paused)
2200
- {
2201
- // touch anywhere to press start when paused
2202
- touchGamepadButtons[9] = 1;
2203
- return;
2204
- }
2205
- }
2206
-
2207
- // get center of left and right sides
2208
- const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2209
- const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
2210
- const startCenter = mainCanvasSize.scale(.5);
2211
-
2212
- // check each touch point
2213
- for (const touch of e.touches)
2214
- {
2215
- const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
2216
- if (touchPos.distance(stickCenter) < touchGamepadSize)
2217
- {
2218
- // virtual analog stick
2219
- if (touchGamepadAnalog)
2220
- touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
2221
- else
2222
- {
2223
- // 8 way dpad
2224
- const angle = touchPos.subtract(stickCenter).angle();
2225
- touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
2226
- }
2227
- }
2228
- else if (touchPos.distance(buttonCenter) < touchGamepadSize)
2229
- {
2230
- // virtual face buttons
2231
- const button = touchPos.subtract(buttonCenter).direction();
2232
- touchGamepadButtons[button] = 1;
2233
- }
2234
- else if (touchPos.distance(startCenter) < touchGamepadSize)
2235
- {
2236
- // virtual start button in center
2237
- touchGamepadButtons[9] = 1;
2238
- }
2239
- }
2240
- }
2241
- }
2242
-
2243
- // render the touch gamepad, called automatically by the engine
2244
- function touchGamepadRender()
2245
- {
2246
- if (!touchGamepadEnable || !touchGamepadTimer.isSet())
2247
- return;
2248
-
2249
- // fade off when not touching or paused
2250
- const alpha = percent(touchGamepadTimer, 4, 3);
2251
- if (!alpha || paused)
2252
- return;
2253
-
2254
- // setup the canvas
2255
- overlayContext.save();
2256
- overlayContext.globalAlpha = alpha*touchGamepadAlpha;
2257
- overlayContext.strokeStyle = '#fff';
2258
- overlayContext.lineWidth = 3;
2259
-
2260
- // draw left analog stick
2261
- overlayContext.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
2262
- overlayContext.beginPath();
2263
-
2264
- const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2265
- if (touchGamepadAnalog)
2266
- {
2267
- overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
2268
- overlayContext.fill();
2269
- overlayContext.stroke();
2270
- }
2271
- else // draw cross shaped gamepad
2272
- {
2273
- for(let i=10; i--;)
2274
- {
2275
- const angle = i*PI/4;
2276
- overlayContext.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
2277
- i%2 && overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
2278
- i==1 && overlayContext.fill();
2279
- }
2280
- overlayContext.stroke();
2281
- }
2282
-
2283
- // draw right face buttons
2284
- const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
2285
- for (let i=4; i--;)
2286
- {
2287
- const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize/2));
2288
- overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
2289
- overlayContext.beginPath();
2290
- overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
2291
- overlayContext.fill();
2292
- overlayContext.stroke();
2293
- }
2294
-
2295
- // set canvas back to normal
2296
- overlayContext.restore();
2297
- }
2298
- /**
2299
- * LittleJS Audio System
2300
- * <br> - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a>
2301
- * <br> - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a>
2302
- * <br> - Caches sounds and music for fast playback
2303
- * <br> - Can attenuate and apply stereo panning to sounds
2304
- * <br> - Ability to play mp3, ogg, and wave files
2305
- * <br> - Speech synthesis wrapper functions
2306
- */
2307
-
2308
- 'use strict';
2309
-
2310
- /**
2311
- * Sound Object - Stores a zzfx sound for later use and can be played positionally
2312
- * <br>
2313
- * <br><b><a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a></b>
2314
- * @example
2315
- * // create a sound
2316
- * const sound_example = new Sound([.5,.5]);
2317
- *
2318
- * // play the sound
2319
- * sound_example.play();
2320
- */
2321
- class Sound
2322
- {
2323
- /** Create a sound object and cache the zzfx samples for later use
2324
- * @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
2325
- * @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
2326
- * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
2327
- */
2328
- constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
2329
- {
2330
- if (!soundEnable) return;
2331
-
2332
- /** @property {Number} - World space max range of sound, will not play if camera is farther away */
2333
- this.range = range;
2334
-
2335
- /** @property {Number} - At what percentage of range should it start tapering off */
2336
- this.taper = taper;
2337
-
2338
- // get randomness from sound parameters
2339
- this.randomness = zzfxSound[1] || 0;
2340
- zzfxSound[1] = 0;
2341
-
2342
- // generate sound now for fast playback
2343
- this.cachedSamples = zzfxG(...zzfxSound);
2344
- }
2345
-
2346
- /** Play the sound
2347
- * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
2348
- * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2349
- * @param {Number} [pitch=1] - How much to scale pitch by (also adjusted by this.randomness)
2350
- * @param {Number} [randomnessScale=1] - How much to scale randomness
2351
- * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
2352
- */
2353
- play(pos, volume=1, pitch=1, randomnessScale=1)
2354
- {
2355
- if (!soundEnable) return;
2356
-
2357
- let pan = 0;
2358
- if (pos)
2359
- {
2360
- const range = this.range;
2361
- if (range)
2362
- {
2363
- // apply range based fade
2364
- const lengthSquared = cameraPos.distanceSquared(pos);
2365
- if (lengthSquared > range*range)
2366
- return; // out of range
2367
-
2368
- // attenuate volume by distance
2369
- volume *= percent(lengthSquared**.5, range, range*this.taper);
2370
- }
2371
-
2372
- // get pan from screen space coords
2373
- pan = worldToScreen(pos).x * 2/mainCanvas.width - 1;
2374
- }
2375
-
2376
- // play the sound
2377
- const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
2378
- return playSamples([this.cachedSamples], volume, playbackRate, pan);
2379
- }
2380
-
2381
- /** Play the sound as a note with a semitone offset
2382
- * @param {Number} semitoneOffset - How many semitones to offset pitch
2383
- * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
2384
- * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2385
- * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
2386
- */
2387
- playNote(semitoneOffset, pos, volume=1)
2388
- {
2389
- if (!soundEnable) return;
2390
-
2391
- return this.play(pos, volume, 2**(semitoneOffset/12), 0);
2392
- }
2393
- }
2394
-
2395
- /**
2396
- * Music Object - Stores a zzfx music track for later use
2397
- * <br>
2398
- * <br><b><a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a></b>
2399
- * @example
2400
- * // create some music
2401
- * const music_example = new Music(
2402
- * [
2403
- * [ // instruments
2404
- * [,0,400] // simple note
2405
- * ],
2406
- * [ // patterns
2407
- * [ // pattern 1
2408
- * [ // channel 0
2409
- * 0, -1, // instrument 0, left speaker
2410
- * 1, 0, 9, 1 // channel notes
2411
- * ],
2412
- * [ // channel 1
2413
- * 0, 1, // instrument 1, right speaker
2414
- * 0, 12, 17, -1 // channel notes
2415
- * ]
2416
- * ],
2417
- * ],
2418
- * [0, 0, 0, 0], // sequence, play pattern 0 four times
2419
- * 90 // BPM
2420
- * ]);
2421
- *
2422
- * // play the music
2423
- * music_example.play();
2424
- */
2425
- class Music
2426
- {
2427
- /** Create a music object and cache the zzfx music samples for later use
2428
- * @param {Array} zzfxMusic - Array of zzfx music parameters
2429
- */
2430
- constructor(zzfxMusic)
2431
- {
2432
- if (!soundEnable) return;
2433
-
2434
- this.cachedSamples = zzfxM(...zzfxMusic);
2435
- }
2436
-
2437
- /** Play the music
2438
- * @param {Number} [volume=1] - How much to scale volume by
2439
- * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
2440
- * @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
2441
- */
2442
- play(volume = 1, loop = 1)
2443
- {
2444
- if (!soundEnable) return;
2445
-
2446
- return playSamples(this.cachedSamples, volume, 1, 0, loop);
2447
- }
2448
- }
2449
-
2450
- /** Play an mp3 or wav audio from a local file or url
2451
- * @param {String} url - Location of sound file to play
2452
- * @param {Number} [volume=1] - How much to scale volume by
2453
- * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
2454
- * @return {HTMLAudioElement} - The audio element for this sound
2455
- * @memberof Audio */
2456
- function playAudioFile(url, volume=1, loop=1)
2457
- {
2458
- if (!soundEnable) return;
2459
-
2460
- const audio = new Audio(url);
2461
- audio.volume = soundVolume * volume;
2462
- audio.loop = loop;
2463
- audio.play();
2464
- return audio;
2465
- }
2466
-
2467
- /** Speak text with passed in settings
2468
- * @param {String} text - The text to speak
2469
- * @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
2470
- * @param {Number} [volume=1] - How much to scale volume by
2471
- * @param {Number} [rate=1] - How quickly to speak
2472
- * @param {Number} [pitch=1] - How much to change the pitch by
2473
- * @return {SpeechSynthesisUtterance} - The utterance that was spoken
2474
- * @memberof Audio */
2475
- function speak(text, language='', volume=1, rate=1, pitch=1)
2476
- {
2477
- if (!soundEnable || !speechSynthesis) return;
2478
-
2479
- // common languages (not supported by all browsers)
2480
- // en - english, it - italian, fr - french, de - german, es - spanish
2481
- // ja - japanese, ru - russian, zh - chinese, hi - hindi, ko - korean
2482
-
2483
- // build utterance and speak
2484
- const utterance = new SpeechSynthesisUtterance(text);
2485
- utterance.lang = language;
2486
- utterance.volume = 2*volume*soundVolume;
2487
- utterance.rate = rate;
2488
- utterance.pitch = pitch;
2489
- speechSynthesis.speak(utterance);
2490
- return utterance;
2491
- }
2492
-
2493
- /** Stop all queued speech
2494
- * @memberof Audio */
2495
- const speakStop = ()=> speechSynthesis && speechSynthesis.cancel();
2496
-
2497
- /** Get frequency of a note on a musical scale
2498
- * @param {Number} semitoneOffset - How many semitones away from the root note
2499
- * @param {Number} [rootNoteFrequency=220] - Frequency at semitone offset 0
2500
- * @return {Number} - The frequency of the note
2501
- * @memberof Audio */
2502
- const getNoteFrequency = (semitoneOffset, rootFrequency=220)=> rootFrequency * 2**(semitoneOffset/12);
2503
-
2504
- ///////////////////////////////////////////////////////////////////////////////
2505
-
2506
- /** Audio context used by the engine
2507
- * @memberof Audio */
2508
- let audioContext;
2509
-
2510
- /** Play cached audio samples with given settings
2511
- * @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
2512
- * @param {Number} [volume=1] - How much to scale volume by
2513
- * @param {Number} [rate=1] - The playback rate to use
2514
- * @param {Number} [pan=0] - How much to apply stereo panning
2515
- * @param {Boolean} [loop=0] - True if the sound should loop when it reaches the end
2516
- * @return {AudioBufferSourceNode} - The audio node of the sound played
2517
- * @memberof Audio */
2518
- function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2519
- {
2520
- if (!soundEnable) return;
2521
-
2522
- // create audio context
2523
- if (!audioContext)
2524
- audioContext = new (window.AudioContext||webkitAudioContext);
2525
-
2526
- // fix stalled audio
2527
- audioContext.resume();
2528
-
2529
- // prevent sounds from building up if they can't be played
2530
- if (audioContext.state != 'running')
2531
- return;
2532
-
2533
- // create buffer and source
2534
- const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, zzfxR),
2535
- source = audioContext.createBufferSource();
2536
-
2537
- // copy samples to buffer and setup source
2538
- sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
2539
- source.buffer = buffer;
2540
- source.playbackRate.value = rate;
2541
- source.loop = loop;
2542
-
2543
- // create and connect gain node (createGain is more widley spported then GainNode construtor)
2544
- const gainNode = audioContext.createGain();
2545
- gainNode.gain.value = soundVolume*volume;
2546
- gainNode.connect(audioContext.destination);
2547
-
2548
- // connect source to gain
2549
- (
2550
- window.StereoPannerNode ? // create pan node if possible
2551
- source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
2552
- : source
2553
- )
2554
- .connect(gainNode);
2555
-
2556
- // play and return sound
2557
- source.start();
2558
- return source;
2559
- }
2560
-
2561
- ///////////////////////////////////////////////////////////////////////////////
2562
- // ZzFXMicro - Zuper Zmall Zound Zynth - v1.1.8 by Frank Force
2563
-
2564
- /** Generate and play a ZzFX sound
2565
- * <br>
2566
- * <br><b><a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a></b>
2567
- * @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
2568
- * @return {Array} - Array of audio samples
2569
- * @memberof Audio */
2570
- const zzfx = (...zzfxSound) => playSamples([zzfxG(...zzfxSound)]);
2571
-
2572
- /** Sample rate used for all ZzFX sounds
2573
- * @default 44100
2574
- * @memberof Audio */
2575
- const zzfxR = 44100;
2576
-
2577
- /** Generate samples for a ZzFX sound
2578
- * @memberof Audio */
2579
- function zzfxG
2580
- (
2581
- // parameters
2582
- volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
2583
- release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
2584
- pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
2585
- bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0
2586
- )
2587
- {
2588
- // init parameters
2589
- let PI2 = PI*2, startSlide = slide *= 500 * PI2 / zzfxR / zzfxR, b=[],
2590
- startFrequency = frequency *= (1 + randomness*rand(-1,1)) * PI2 / zzfxR,
2591
- t=0, tm=0, i=0, j=1, r=0, c=0, s=0, f, length;
2592
-
2593
- // scale by sample rate
2594
- attack = attack * zzfxR + 9; // minimum attack to prevent pop
2595
- decay *= zzfxR;
2596
- sustain *= zzfxR;
2597
- release *= zzfxR;
2598
- delay *= zzfxR;
2599
- deltaSlide *= 500 * PI2 / zzfxR**3;
2600
- modulation *= PI2 / zzfxR;
2601
- pitchJump *= PI2 / zzfxR;
2602
- pitchJumpTime *= zzfxR;
2603
- repeatTime = repeatTime * zzfxR | 0;
2604
-
2605
- // generate waveform
2606
- for (length = attack + decay + sustain + release + delay | 0;
2607
- i < length; b[i++] = s)
2608
- {
2609
- if (!(++c%(bitCrush*100|0))) // bit crush
2610
- {
2611
- s = shape? shape>1? shape>2? shape>3? // wave shape
2612
- Math.sin((t%PI2)**3) : // 4 noise
2613
- max(min(Math.tan(t),1),-1): // 3 tan
2614
- 1-(2*t/PI2%2+2)%2: // 2 saw
2615
- 1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
2616
- Math.sin(t); // 0 sin
2617
-
2618
- s = (repeatTime ?
2619
- 1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
2620
- : 1) *
2621
- sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
2622
- volume * soundVolume * ( // envelope
2623
- i < attack ? i/attack : // attack
2624
- i < attack + decay ? // decay
2625
- 1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
2626
- i < attack + decay + sustain ? // sustain
2627
- sustainVolume : // sustain volume
2628
- i < length - delay ? // release
2629
- (length - i - delay)/release * // release falloff
2630
- sustainVolume : // release volume
2631
- 0); // post release
2632
-
2633
- s = delay ? s/2 + (delay > i ? 0 : // delay
2634
- (i<length-delay? 1 : (length-i)/delay) * // release delay
2635
- b[i-delay|0]/2) : s; // sample delay
2636
- }
2637
-
2638
- f = (frequency += slide += deltaSlide) * // frequency
2639
- Math.cos(modulation*tm++); // modulation
2640
- t += f - f*noise*(1 - (Math.sin(i)+1)*1e9%2); // noise
2641
-
2642
- if (j && ++j > pitchJumpTime) // pitch jump
2643
- {
2644
- frequency += pitchJump; // apply pitch jump
2645
- startFrequency += pitchJump; // also apply to start
2646
- j = 0; // reset pitch jump time
2647
- }
2648
-
2649
- if (repeatTime && !(++r % repeatTime)) // repeat
2650
- {
2651
- frequency = startFrequency; // reset frequency
2652
- slide = startSlide; // reset slide
2653
- j = j || 1; // reset pitch jump time
2654
- }
2655
- }
2656
-
2657
- return b;
2658
- }
2659
-
2660
- ///////////////////////////////////////////////////////////////////////////////
2661
- // ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
2662
-
2663
- /** Generate samples for a ZzFM song with given parameters
2664
- * @param {Array} instruments - Array of ZzFX sound paramaters
2665
- * @param {Array} patterns - Array of pattern data
2666
- * @param {Array} sequence - Array of pattern indexes
2667
- * @param {Number} [BPM=125] - Playback speed of the song in BPM
2668
- * @returns {Array} - Left and right channel sample data
2669
- * @memberof Audio */
2670
- function zzfxM(instruments, patterns, sequence, BPM = 125)
2671
- {
2672
- let instrumentParameters;
2673
- let i;
2674
- let j;
2675
- let k;
2676
- let note;
2677
- let sample;
2678
- let patternChannel;
2679
- let notFirstBeat;
2680
- let stop;
2681
- let instrument;
2682
- let attenuation;
2683
- let outSampleOffset;
2684
- let isSequenceEnd;
2685
- let sampleOffset = 0;
2686
- let nextSampleOffset;
2687
- let sampleBuffer = [];
2688
- let leftChannelBuffer = [];
2689
- let rightChannelBuffer = [];
2690
- let channelIndex = 0;
2691
- let panning = 0;
2692
- let hasMore = 1;
2693
- let sampleCache = {};
2694
- let beatLength = zzfxR / BPM * 60 >> 2;
2695
-
2696
- // for each channel in order until there are no more
2697
- for (; hasMore; channelIndex++) {
2698
-
2699
- // reset current values
2700
- sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
2701
-
2702
- // for each pattern in sequence
2703
- sequence.forEach((patternIndex, sequenceIndex) => {
2704
- // get pattern for current channel, use empty 1 note pattern if none found
2705
- patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
2706
-
2707
- // check if there are more channels
2708
- hasMore |= !!patterns[patternIndex][channelIndex];
2709
-
2710
- // get next offset, use the length of first channel
2711
- nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - !notFirstBeat) * beatLength;
2712
- // for each beat in pattern, plus one extra if end of sequence
2713
- isSequenceEnd = sequenceIndex == sequence.length - 1;
2714
- for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
2715
-
2716
- // <channel-note>
2717
- note = patternChannel[i];
2718
-
2719
- // stop if end, different instrument or new note
2720
- stop = i == patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
2721
- instrument != (patternChannel[0] || 0) | note | 0;
2722
-
2723
- // fill buffer with samples for previous beat, most cpu intensive part
2724
- for (j = 0; j < beatLength && notFirstBeat;
2725
-
2726
- // fade off attenuation at end of beat if stopping note, prevents clicking
2727
- j++ > beatLength - 99 && stop ? attenuation += (attenuation < 1) / 99 : 0
2728
- ) {
2729
- // copy sample to stereo buffers with panning
2730
- sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
2731
- leftChannelBuffer[k] = (leftChannelBuffer[k] || 0) - sample * panning + sample;
2732
- rightChannelBuffer[k] = (rightChannelBuffer[k++] || 0) + sample * panning + sample;
2733
- }
2734
-
2735
- // set up for next note
2736
- if (note) {
2737
- // set attenuation
2738
- attenuation = note % 1;
2739
- panning = patternChannel[1] || 0;
2740
- if (note |= 0) {
2741
- // get cached sample
2742
- sampleBuffer = sampleCache[
2743
- [
2744
- instrument = patternChannel[sampleOffset = 0] || 0,
2745
- note
2746
- ]
2747
- ] = sampleCache[[instrument, note]] || (
2748
- // add sample to cache
2749
- instrumentParameters = [...instruments[instrument]],
2750
- instrumentParameters[2] *= 2 ** ((note - 12) / 12),
2751
-
2752
- // allow negative values to stop notes
2753
- note > 0 ? zzfxG(...instrumentParameters) : []
2754
- );
2755
- }
2756
- }
2757
- }
2758
-
2759
- // update the sample offset
2760
- outSampleOffset = nextSampleOffset;
2761
- });
2762
- }
2763
-
2764
- return [leftChannelBuffer, rightChannelBuffer];
2765
- }
2766
- /**
2767
- * LittleJS Tile Layer System
2768
- * <br> - Caches arrays of tiles to off screen canvas for fast rendering
2769
- * <br> - Unlimted numbers of layers, allocates canvases as needed
2770
- * <br> - Interfaces with EngineObject for collision
2771
- * <br> - Collision layer is separate from visible layers
2772
- * <br> - It is recommended to have a visible layer that matches the collision
2773
- * <br> - Tile layers can be drawn to using their context with canvas2d
2774
- * <br> - Drawn directly to the main canvas without using WebGL
2775
- * @namespace TileCollision
2776
- */
2777
-
2778
- 'use strict';
2779
-
2780
- /** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access
2781
- * @memberof TileCollision */
2782
- let tileCollision = [];
2783
-
2784
- /** Size of the tile collision layer
2785
- * @type {Vector2}
2786
- * @memberof TileCollision */
2787
- let tileCollisionSize = vec2();
2788
-
2789
- /** Clear and initialize tile collision
2790
- * @param {Vector2} size
2791
- * @memberof TileCollision */
2792
- function initTileCollision(size)
2793
- {
2794
- tileCollisionSize = size;
2795
- tileCollision = [];
2796
- for (let i=tileCollision.length = tileCollisionSize.area(); i--;)
2797
- tileCollision[i] = 0;
2798
- }
2799
-
2800
- /** Set tile collision data
2801
- * @param {Vector2} pos
2802
- * @param {Number} [data=0]
2803
- * @memberof TileCollision */
2804
- const setTileCollisionData = (pos, data=0)=>
2805
- pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
2806
-
2807
- /** Get tile collision data
2808
- * @param {Vector2} pos
2809
- * @return {Number}
2810
- * @memberof TileCollision */
2811
- const getTileCollisionData = (pos)=>
2812
- pos.arrayCheck(tileCollisionSize) ? tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] : 0;
2813
-
2814
- /** Check if collision with another object should occur
2815
- * @param {Vector2} pos
2816
- * @param {Vector2} [size=new Vector2(1,1)]
2817
- * @param {EngineObject} [object]
2818
- * @return {Boolean}
2819
- * @memberof TileCollision */
2820
- function tileCollisionTest(pos, size=vec2(), object)
2821
- {
2822
- const minX = max(pos.x - size.x/2|0, 0);
2823
- const minY = max(pos.y - size.y/2|0, 0);
2824
- const maxX = min(pos.x + size.x/2, tileCollisionSize.x);
2825
- const maxY = min(pos.y + size.y/2, tileCollisionSize.y);
2826
- for (let y = minY; y < maxY; ++y)
2827
- for (let x = minX; x < maxX; ++x)
2828
- {
2829
- const tileData = tileCollision[y*tileCollisionSize.x+x];
2830
- if (tileData && (!object || object.collideWithTile(tileData, new Vector2(x, y))))
2831
- return 1;
2832
- }
2833
- }
2834
-
2835
- /** Return the center of tile if any that is hit (this does not return the exact hit point)
2836
- * @param {Vector2} posStart
2837
- * @param {Vector2} posEnd
2838
- * @param {EngineObject} [object]
2839
- * @return {Vector2}
2840
- * @memberof TileCollision */
2841
- function tileCollisionRaycast(posStart, posEnd, object)
2842
- {
2843
- // test if a ray collides with tiles from start to end
2844
- // todo: a way to get the exact hit point, it must still register as inside the hit tile
2845
- posStart = posStart.floor();
2846
- posEnd = posEnd.floor();
2847
- const posDelta = posEnd.subtract(posStart);
2848
- const dx = abs(posDelta.x), dy = -abs(posDelta.y);
2849
- const sx = sign(posDelta.x), sy = sign(posDelta.y);
2850
- let e = dx + dy;
2851
-
2852
- for (let x = posStart.x, y = posStart.y;;)
2853
- {
2854
- const tileData = getTileCollisionData(vec2(x,y));
2855
- if (tileData && (object ? object.collideWithTileRaycast(tileData, new Vector2(x, y)) : tileData > 0))
2856
- {
2857
- debugRaycast && debugLine(posStart, posEnd, '#f00',.02, 1);
2858
- debugRaycast && debugPoint(new Vector2(x+.5, y+.5), '#ff0', 1);
2859
- return new Vector2(x+.5, y+.5);
2860
- }
2861
-
2862
- // update Bresenham line drawing algorithm
2863
- if (x == posEnd.x & y == posEnd.y) break;
2864
- const e2 = 2*e;
2865
- if (e2 >= dy) e += dy, x += sx;
2866
- if (e2 <= dx) e += dx, y += sy;
2867
- }
2868
- debugRaycast && debugLine(posStart, posEnd, '#00f',.02, 1);
2869
- }
2870
-
2871
- ///////////////////////////////////////////////////////////////////////////////
2872
- // Tile Layer Rendering System
2873
-
2874
- /**
2875
- * Tile layer data object stores info about how to render a tile
2876
- * @example
2877
- * // create tile layer data with tile index 0 and random orientation and color
2878
- * const tileIndex = 0;
2879
- * const direction = randInt(4)
2880
- * const mirror = randInt(2);
2881
- * const color = randColor();
2882
- * const data = new TileLayerData(tileIndex, direction, mirror, color);
2883
- */
2884
- class TileLayerData
2885
- {
2886
- /** Create a tile layer data object, one for each tile in a TileLayer
2887
- * @param {Number} [tile] - The tile to use, untextured if undefined
2888
- * @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments
2889
- * @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis
2890
- * @param {Color} [color=new Color(1,1,1)] - Color of the tile */
2891
- constructor(tile, direction=0, mirror=0, color=new Color)
2892
- {
2893
- /** @property {Number} - The tile to use, untextured if undefined */
2894
- this.tile = tile;
2895
- /** @property {Number} - Integer direction of tile, in 90 degree increments */
2896
- this.direction = direction;
2897
- /** @property {Boolean} - If the tile should be mirrored along the x axis */
2898
- this.mirror = mirror;
2899
- /** @property {Color} - Color of the tile */
2900
- this.color = color;
2901
- }
2902
-
2903
- /** Set this tile to clear, it will not be rendered */
2904
- clear() { this.tile = this.direction = this.mirror = 0; color = new Color; }
2905
- }
2906
-
2907
- /**
2908
- * Tile layer object - cached rendering system for tile layers
2909
- * <br> - Each Tile layer is rendered to an off screen canvas
2910
- * <br> - To allow dynamic modifications, layers are rendered using canvas 2d
2911
- * <br> - Some devices like mobile phones are limited to 4k texture resolution
2912
- * <br> - So with 16x16 tiles this limits layers to 256x256 on mobile devices
2913
- * @extends EngineObject
2914
- * @example
2915
- * // create tile collision and visible tile layer
2916
- * initTileCollision(vec2(200,100));
2917
- * const tileLayer = new TileLayer();
2918
- */
2919
- class TileLayer extends EngineObject
2920
- {
2921
- /** Create a tile layer object
2922
- * @param {Vector2} [position=new Vector2()] - World space position
2923
- * @param {Vector2} [size=tileCollisionSize] - World space size
2924
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
2925
- * @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
2926
- * @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
2927
- */
2928
- constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
2929
- {
2930
- super(pos, size, -1, tileSize, 0, undefined, renderOrder);
2931
-
2932
- /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
2933
- this.canvas = document.createElement('canvas');
2934
- /** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
2935
- this.context = this.canvas.getContext('2d');
2936
- /** @property {Vector2} - How much to scale this layer when rendered */
2937
- this.scale = scale;
2938
- /** @property {Boolean} [isOverlay=0] - If true this layer will render to overlay canvas and appear above all objects */
2939
- this.isOverlay;
2940
-
2941
- // init tile data
2942
- this.data = [];
2943
- for (let j = this.size.area(); j--;)
2944
- this.data.push(new TileLayerData());
2945
- }
2946
-
2947
- /** Set data at a given position in the array
2948
- * @param {Vector2} position - Local position in array
2949
- * @param {TileLayerData} data - Data to set
2950
- * @param {Boolean} [redraw=0] - Force the tile to redraw if true */
2951
- setData(layerPos, data, redraw)
2952
- {
2953
- if (layerPos.arrayCheck(this.size))
2954
- {
2955
- this.data[(layerPos.y|0)*this.size.x+layerPos.x|0] = data;
2956
- redraw && this.drawTileData(layerPos);
2957
- }
2958
- }
2959
-
2960
- /** Get data at a given position in the array
2961
- * @param {Vector2} layerPos - Local position in array
2962
- * @return {TileLayerData} */
2963
- getData(layerPos)
2964
- { return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]; }
2965
-
2966
- // Tile layers are not updated
2967
- update() {}
2968
-
2969
- // Render the tile layer, called automatically by the engine
2970
- render()
2971
- {
2972
- ASSERT(mainContext != this.context); // must call redrawEnd() after drawing tiles
2973
-
2974
- // flush and copy gl canvas because tile canvas does not use webgl
2975
- glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
2976
-
2977
- // draw the entire cached level onto the canvas
2978
- const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
2979
- (this.isOverlay ? overlayContext : mainContext).drawImage
2980
- (
2981
- this.canvas, pos.x, pos.y,
2982
- cameraScale*this.size.x*this.scale.x, cameraScale*this.size.y*this.scale.y
2983
- );
2984
- }
2985
-
2986
- /** Draw all the tile data to an offscreen canvas
2987
- * - This may be slow in some browsers
2988
- */
2989
- redraw()
2990
- {
2991
- this.redrawStart(1);
2992
- this.drawAllTileData();
2993
- this.redrawEnd();
2994
- }
2995
-
2996
- /** Call to start the redraw process
2997
- * @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
2998
- redrawStart(clear = 0)
2999
- {
3000
- if (clear)
3001
- {
3002
- // clear and set size
3003
- this.canvas.width = this.size.x * this.tileSize.x;
3004
- this.canvas.height = this.size.y * this.tileSize.y;
3005
- }
3006
-
3007
- // save current render settings
3008
- this.savedRenderSettings = [mainCanvas, mainContext, cameraPos, cameraScale];
3009
-
3010
- // use normal rendering system to render the tiles
3011
- mainCanvas = this.canvas;
3012
- mainContext = this.context;
3013
- cameraPos = this.size.scale(.5);
3014
- cameraScale = this.tileSize.x;
3015
- enginePreRender();
3016
- }
3017
-
3018
- /** Call to end the redraw process */
3019
- redrawEnd()
3020
- {
3021
- ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
3022
- glEnable && glCopyToContext(mainContext, 1);
3023
- //debugSaveCanvas(this.canvas);
3024
-
3025
- // set stuff back to normal
3026
- [mainCanvas, mainContext, cameraPos, cameraScale] = this.savedRenderSettings;
3027
- }
3028
-
3029
- /** Draw the tile at a given position
3030
- * @param {Vector2} layerPos */
3031
- drawTileData(layerPos)
3032
- {
3033
- // first clear out where the tile was
3034
- const pos = layerPos.floor().add(this.pos).add(vec2(.5));
3035
- this.drawCanvas2D(pos, vec2(1), 0, 0, (context)=>context.clearRect(-.5, -.5, 1, 1));
3036
-
3037
- // draw the tile if not undefined
3038
- const d = this.getData(layerPos);
3039
- if (d.tile != undefined)
3040
- {
3041
- ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
3042
- drawTile(pos, vec2(1), d.tile, this.tileSize, d.color, d.direction*PI/2, d.mirror);
3043
- }
3044
- }
3045
-
3046
- /** Draw all the tiles in this layer */
3047
- drawAllTileData()
3048
- {
3049
- for (let x = this.size.x; x--;)
3050
- for (let y = this.size.y; y--;)
3051
- this.drawTileData(vec2(x,y));
3052
- }
3053
-
3054
- /** Draw directly to the 2D canvas in world space (bipass webgl)
3055
- * @param {Vector2} pos
3056
- * @param {Vector2} size
3057
- * @param {Number} [angle=0]
3058
- * @param {Boolean} [mirror=0]
3059
- * @param {Function} drawFunction */
3060
- drawCanvas2D(pos, size, angle=0, mirror, drawFunction)
3061
- {
3062
- const context = this.context;
3063
- context.save();
3064
- pos = pos.subtract(this.pos).multiply(this.tileSize);
3065
- size = size.multiply(this.tileSize);
3066
- context.translate(pos.x, this.canvas.height - pos.y);
3067
- context.rotate(angle);
3068
- context.scale(mirror ? -size.x : size.x, size.y);
3069
- drawFunction(context);
3070
- context.restore();
3071
- }
3072
-
3073
- /** Draw a tile directly onto the layer canvas
3074
- * @param {Vector2} pos
3075
- * @param {Vector2} [size=new Vector2(1,1)]
3076
- * @param {Number} [tileIndex=-1]
3077
- * @param {Vector2} [tileSize=tileSizeDefault]
3078
- * @param {Color} [color=new Color(1,1,1)]
3079
- * @param {Number} [angle=0]
3080
- * @param {Boolean} [mirror=0] */
3081
- drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle, mirror)
3082
- {
3083
- this.drawCanvas2D(pos, size, angle, mirror, (context)=>
3084
- {
3085
- if (tileIndex < 0)
3086
- {
3087
- // untextured
3088
- context.fillStyle = color;
3089
- context.fillRect(-.5, -.5, 1, 1);
3090
- }
3091
- else
3092
- {
3093
- const cols = tileImage.width/tileSize.x;
3094
- context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
3095
- context.drawImage(tileImage,
3096
- (tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.y,
3097
- tileSize.x, tileSize.y, -.5, -.5, 1, 1);
3098
- }
3099
- });
3100
- }
3101
-
3102
- /** Draw a rectangle directly onto the layer canvas
3103
- * @param {Vector2} pos
3104
- * @param {Vector2} [size=new Vector2(1,1)]
3105
- * @param {Color} [color=new Color(1,1,1)]
3106
- * @param {Number} [angle=0] */
3107
- drawRect(pos, size, color, angle)
3108
- { this.drawTile(pos, size, -1, 0, color, angle); }
3109
- }
3110
- /*
3111
- LittleJS Particle System
3112
- - Spawns particles with randomness from parameters
3113
- - Updates particle physics
3114
- - Fast particle rendering
3115
- */
3116
-
3117
- 'use strict';
3118
-
3119
- /**
3120
- * Particle Emitter - Spawns particles with the given settings
3121
- * @extends EngineObject
3122
- * @example
3123
- * // create a particle emitter
3124
- * let pos = vec2(2,3);
3125
- * let particleEmiter = new ParticleEmitter
3126
- * (
3127
- * pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
3128
- * 0, vec2(16), // tileIndex, tileSize
3129
- * new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
3130
- * new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
3131
- * 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
3132
- * .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
3133
- * .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
3134
- * );
3135
- */
3136
- class ParticleEmitter extends EngineObject
3137
- {
3138
- /** Create a particle system with the given settings
3139
- * @param {Vector2} position - World space position of the emitter
3140
- * @param {Number} [angle=0] - Angle to emit the particles
3141
- * @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
3142
- * @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
3143
- * @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
3144
- * @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
3145
- * @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied
3146
- * @param {Number} [tileSize=tileSizeDefault] - Tile size for particles
3147
- * @param {Color} [colorStartA=new Color(1,1,1)] - Color at start of life 1, randomized between start colors
3148
- * @param {Color} [colorStartB=new Color(1,1,1)] - Color at start of life 2, randomized between start colors
3149
- * @param {Color} [colorEndA=new Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
3150
- * @param {Color} [colorEndB=new Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
3151
- * @param {Number} [particleTime=.5] - How long particles live
3152
- * @param {Number} [sizeStart=.1] - How big are particles at start
3153
- * @param {Number} [sizeEnd=1] - How big are particles at end
3154
- * @param {Number} [speed=.1] - How fast are particles when spawned
3155
- * @param {Number} [angleSpeed=.05] - How fast are particles rotating
3156
- * @param {Number} [damping=1] - How much to dampen particle speed
3157
- * @param {Number} [angleDamping=1] - How much to dampen particle angular speed
3158
- * @param {Number} [gravityScale=0] - How much does gravity effect particles
3159
- * @param {Number} [particleConeAngle=PI] - Cone for start particle angle
3160
- * @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life
3161
- * @param {Number} [randomness=.2] - Apply extra randomness percent
3162
- * @param {Boolean} [collideTiles=0] - Do particles collide against tiles
3163
- * @param {Boolean} [additive=0] - Should particles use addtive blend
3164
- * @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
3165
- * @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
3166
- * @param {Boolean} [localSpace=0] - Should it be in local space of emitter (world space is default)
3167
- */
3168
- constructor
3169
- (
3170
- pos,
3171
- angle,
3172
- emitSize = 0,
3173
- emitTime = 0,
3174
- emitRate = 100,
3175
- emitConeAngle = PI,
3176
- tileIndex = -1,
3177
- tileSize = tileSizeDefault,
3178
- colorStartA = new Color,
3179
- colorStartB = new Color,
3180
- colorEndA = new Color(1,1,1,0),
3181
- colorEndB = new Color(1,1,1,0),
3182
- particleTime = .5,
3183
- sizeStart = .1,
3184
- sizeEnd = 1,
3185
- speed = .1,
3186
- angleSpeed = .05,
3187
- damping = 1,
3188
- angleDamping = 1,
3189
- gravityScale = 0,
3190
- particleConeAngle = PI,
3191
- fadeRate = .1,
3192
- randomness = .2,
3193
- collideTiles,
3194
- additive,
3195
- randomColorLinear = 1,
3196
- renderOrder = additive ? 1e9 : 0,
3197
- localSpace
3198
- )
3199
- {
3200
- super(pos, new Vector2, tileIndex, tileSize, angle, undefined, renderOrder);
3201
-
3202
- // emitter settings
3203
- /** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
3204
- this.emitSize = emitSize
3205
- /** @property {Number} - How long to stay alive (0 is forever) */
3206
- this.emitTime = emitTime;
3207
- /** @property {Number} - How many particles per second to spawn, does not emit if 0 */
3208
- this.emitRate = emitRate;
3209
- /** @property {Number} - Local angle to apply velocity to particles from emitter */
3210
- this.emitConeAngle = emitConeAngle;
3211
-
3212
- // color settings
3213
- /** @property {Color} - Color at start of life 1, randomized between start colors */
3214
- this.colorStartA = colorStartA;
3215
- /** @property {Color} - Color at start of life 2, randomized between start colors */
3216
- this.colorStartB = colorStartB;
3217
- /** @property {Color} - Color at end of life 1, randomized between end colors */
3218
- this.colorEndA = colorEndA;
3219
- /** @property {Color} - Color at end of life 2, randomized between end colors */
3220
- this.colorEndB = colorEndB;
3221
- /** @property {Boolean} - Should color be randomized linearly or across each component */
3222
- this.randomColorLinear = randomColorLinear;
3223
-
3224
- // particle settings
3225
- /** @property {Number} - How long particles live */
3226
- this.particleTime = particleTime;
3227
- /** @property {Number} - How big are particles at start */
3228
- this.sizeStart = sizeStart;
3229
- /** @property {Number} - How big are particles at end */
3230
- this.sizeEnd = sizeEnd;
3231
- /** @property {Number} - How fast are particles when spawned */
3232
- this.speed = speed;
3233
- /** @property {Number} - How fast are particles rotating */
3234
- this.angleSpeed = angleSpeed;
3235
- /** @property {Number} - How much to dampen particle speed */
3236
- this.damping = damping;
3237
- /** @property {Number} - How much to dampen particle angular speed */
3238
- this.angleDamping = angleDamping;
3239
- /** @property {Number} - How much does gravity effect particles */
3240
- this.gravityScale = gravityScale;
3241
- /** @property {Number} - Cone for start particle angle */
3242
- this.particleConeAngle = particleConeAngle;
3243
- /** @property {Number} - How quick to fade in particles at start/end in percent of life */
3244
- this.fadeRate = fadeRate;
3245
- /** @property {Number} - Apply extra randomness percent */
3246
- this.randomness = randomness;
3247
- /** @property {Number} - Do particles collide against tiles */
3248
- this.collideTiles = collideTiles;
3249
- /** @property {Number} - Should particles use addtive blend */
3250
- this.additive = additive;
3251
- /** @property {Boolean} - Should it be in local space of emitter */
3252
- this.localSpace = localSpace;
3253
- /** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */
3254
- this.trailScale = 0;
3255
-
3256
- // internal variables
3257
- this.emitTimeBuffer = 0;
3258
- }
3259
-
3260
- /** Update the emitter to spawn particles, called automatically by engine once each frame */
3261
- update()
3262
- {
3263
- // only do default update to apply parent transforms
3264
- this.parent && super.update();
3265
-
3266
- // update emitter
3267
- if (!this.emitTime || this.getAliveTime() <= this.emitTime)
3268
- {
3269
- // emit particles
3270
- if (this.emitRate * particleEmitRateScale)
3271
- {
3272
- const rate = 1/this.emitRate/particleEmitRateScale;
3273
- for (this.emitTimeBuffer += timeDelta; this.emitTimeBuffer > 0; this.emitTimeBuffer -= rate)
3274
- this.emitParticle();
3275
- }
3276
- }
3277
- else
3278
- this.destroy();
3279
-
3280
- debugParticles && debugRect(this.pos, vec2(this.emitSize), '#0f0', 0, this.angle);
3281
- }
3282
-
3283
- /** Spawn one particle
3284
- * @return {Particle} */
3285
- emitParticle()
3286
- {
3287
- // spawn a particle
3288
- let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
3289
- (new Vector2(rand(-.5,.5), rand(-.5,.5)))
3290
- .multiply(this.emitSize).rotate(this.angle) // box emitter
3291
- : randInCircle(this.emitSize * .5); // circle emitter
3292
- let angle = rand(this.particleConeAngle, -this.particleConeAngle);
3293
- if (!this.localSpace)
3294
- {
3295
- pos = this.pos.add(pos);
3296
- angle += this.angle;
3297
- }
3298
-
3299
- const particle = new Particle(pos, this.tileIndex, this.tileSize, angle);
3300
-
3301
- // randomness scales each paremeter by a percentage
3302
- const randomness = this.randomness;
3303
- const randomizeScale = (v)=> v + v*rand(randomness, -randomness);
3304
-
3305
- // randomize particle settings
3306
- const particleTime = randomizeScale(this.particleTime);
3307
- const sizeStart = randomizeScale(this.sizeStart);
3308
- const sizeEnd = randomizeScale(this.sizeEnd);
3309
- const speed = randomizeScale(this.speed);
3310
- const angleSpeed = randomizeScale(this.angleSpeed) * randSign();
3311
- const coneAngle = rand(this.emitConeAngle, -this.emitConeAngle);
3312
- const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
3313
- const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
3314
- const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
3315
-
3316
- // build particle settings
3317
- particle.colorStart = colorStart;
3318
- particle.colorEndDelta = colorEnd.subtract(colorStart);
3319
- particle.velocity = (new Vector2).setAngle(velocityAngle, speed);
3320
- particle.angleVelocity = angleSpeed;
3321
- particle.lifeTime = particleTime;
3322
- particle.sizeStart = sizeStart;
3323
- particle.sizeEndDelta = sizeEnd - sizeStart;
3324
- particle.fadeRate = this.fadeRate;
3325
- particle.damping = this.damping;
3326
- particle.angleDamping = this.angleDamping;
3327
- particle.elasticity = this.elasticity;
3328
- particle.friction = this.friction;
3329
- particle.gravityScale = this.gravityScale;
3330
- particle.collideTiles = this.collideTiles;
3331
- particle.additive = this.additive;
3332
- particle.renderOrder = this.renderOrder;
3333
- particle.trailScale = this.trailScale;
3334
- particle.mirror = rand()<.5;
3335
- particle.localSpaceEmitter = this.localSpace && this;
3336
-
3337
- // setup callbacks for particles
3338
- particle.destroyCallback = this.particleDestroyCallback;
3339
- this.particleCreateCallback && this.particleCreateCallback(particle);
3340
-
3341
- // return the newly created particle
3342
- return particle;
3343
- }
3344
-
3345
- // Particle emitters are not rendered, only the particles are
3346
- render() {}
3347
- }
3348
-
3349
- ///////////////////////////////////////////////////////////////////////////////
3350
- /**
3351
- * Particle Object - Created automatically by Particle Emitters
3352
- * @extends EngineObject
3353
- */
3354
- class Particle extends EngineObject
3355
- {
3356
- /**
3357
- * Create a particle with the given settings
3358
- * @param {Vector2} position - World space position of the particle
3359
- * @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1
3360
- * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
3361
- * @param {Number} [angle=0] - Angle to rotate the particle
3362
- */
3363
- constructor(pos, tileIndex, tileSize, angle) { super(pos, new Vector2, tileIndex, tileSize, angle); }
3364
-
3365
- /** Render the particle, automatically called each frame, sorted by renderOrder */
3366
- render()
3367
- {
3368
- // modulate size and color
3369
- const p = min((time - this.spawnTime) / this.lifeTime, 1);
3370
- const radius = this.sizeStart + p * this.sizeEndDelta;
3371
- const size = new Vector2(radius, radius);
3372
- const fadeRate = this.fadeRate/2;
3373
- const color = new Color(
3374
- this.colorStart.r + p * this.colorEndDelta.r,
3375
- this.colorStart.g + p * this.colorEndDelta.g,
3376
- this.colorStart.b + p * this.colorEndDelta.b,
3377
- (this.colorStart.a + p * this.colorEndDelta.a) *
3378
- (p < fadeRate ? p/fadeRate : p > 1-fadeRate ? (1-p)/fadeRate : 1)); // fade alpha
3379
-
3380
- // draw the particle
3381
- this.additive && setBlendMode(1);
3382
-
3383
- let pos = this.pos, angle = this.angle;
3384
- if (this.localSpaceEmitter)
3385
- {
3386
- // in local space of emitter
3387
- pos = this.localSpaceEmitter.pos.add(pos.rotate(-this.localSpaceEmitter.angle));
3388
- angle += this.localSpaceEmitter.angle;
3389
- }
3390
- if (this.trailScale)
3391
- {
3392
- // trail style particles
3393
- let velocity = this.velocity;
3394
- if (this.localSpaceEmitter)
3395
- velocity = velocity.rotate(-this.localSpaceEmitter.angle);
3396
- const speed = velocity.length();
3397
- const direction = velocity.scale(1/speed);
3398
- const trailLength = speed * this.trailScale;
3399
- size.y = max(size.x, trailLength);
3400
- angle = direction.angle();
3401
- drawTile(pos.add(direction.multiply(vec2(0,-trailLength/2))), size, this.tileIndex, this.tileSize, color, angle, this.mirror);
3402
- }
3403
- else
3404
- drawTile(pos, size, this.tileIndex, this.tileSize, color, angle, this.mirror);
3405
- this.additive && setBlendMode();
3406
- debugParticles && debugRect(pos, size, '#f005', 0, angle);
3407
-
3408
- if (p == 1)
3409
- {
3410
- // destroy particle when it's time runs out
3411
- this.color = color;
3412
- this.size = size;
3413
- this.destroyCallback && this.destroyCallback(this);
3414
- this.destroyed = 1;
3415
- }
3416
- }
3417
- }
3418
- /**
3419
- * LittleJS Medal System
3420
- * <br> - Tracks and displays medals
3421
- * <br> - Saves medals to local storage
3422
- * <br> - Newgrounds and OS13k integration
3423
- * @namespace Medals
3424
- */
3425
-
3426
- 'use strict';
3427
-
3428
- /** List of all medals
3429
- * @memberof Medals */
3430
- const medals = [];
3431
-
3432
- /** Set to stop medals from being unlockable (like if cheats are enabled)
3433
- * @memberof Medals */
3434
- let medalsPreventUnlock;
3435
-
3436
- // Engine internal variables not exposed to documentation
3437
- let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
3438
-
3439
- ///////////////////////////////////////////////////////////////////////////////
3440
-
3441
- /** Initialize medals with a save name used for storage
3442
- * <br> - Call this after creating all medals
3443
- * <br> - Checks if medals are unlocked
3444
- * @param {String} saveName
3445
- * @memberof Medals */
3446
- function medalsInit(saveName)
3447
- {
3448
- // check if medals are unlocked
3449
- medalsSaveName = saveName;
3450
- debugMedals || medals.forEach(medal=> medal.unlocked = (localStorage[medal.storageKey()] | 0));
3451
- }
3452
-
3453
- /**
3454
- * Medal Object - Tracks an unlockable medal
3455
- * @example
3456
- * // create a medal
3457
- * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
3458
- *
3459
- * // initialize medals
3460
- * medalsInit('Example Game');
3461
- *
3462
- * // unlock the medal
3463
- * medal_example.unlock();
3464
- */
3465
- class Medal
3466
- {
3467
- /** Create an medal object and adds it to the list of medals
3468
- * @param {Number} id - The unique identifier of the medal
3469
- * @param {String} name - Name of the medal
3470
- * @param {String} [description] - Description of the medal
3471
- * @param {String} [icon='🏆'] - Icon for the medal
3472
- * @param {String} [src] - Image location for the medal
3473
- */
3474
- constructor(id, name, description='', icon='🏆', src)
3475
- {
3476
- ASSERT(id >= 0 && !medals[id]);
3477
-
3478
- // save attributes and add to list of medals
3479
- medals[this.id = id] = this;
3480
- this.name = name;
3481
- this.description = description;
3482
- this.icon = icon;
3483
- this.image = new Image();
3484
- if (src)
3485
- this.image.src = src;
3486
- }
3487
-
3488
- /** Unlocks a medal if not already unlocked */
3489
- unlock()
3490
- {
3491
- if (medalsPreventUnlock || this.unlocked)
3492
- return;
3493
-
3494
- // save the medal
3495
- ASSERT(medalsSaveName); // save name must be set
3496
- localStorage[this.storageKey()] = this.unlocked = 1;
3497
- medalsDisplayQueue.push(this);
3498
-
3499
- // save for newgrounds and OS13K
3500
- newgrounds && newgrounds.unlockMedal(this.id);
3501
- localStorage['OS13kTrophy,' + this.icon + ',' + medalsSaveName + ',' + this.name] = this.description;
3502
- }
3503
-
3504
- /** Render a medal
3505
- * @param {Number} [hidePercent=0] - How much to slide the medal off screen
3506
- */
3507
- render(hidePercent=0)
3508
- {
3509
- const context = overlayContext;
3510
- const width = min(medalDisplayWidth, mainCanvas.width);
3511
- const x = overlayCanvas.width - width;
3512
- const y = -medalDisplayHeight*hidePercent;
3513
-
3514
- // draw containing rect and clip to that region
3515
- context.save();
3516
- context.beginPath();
3517
- context.fillStyle = '#ddd'
3518
- context.fill(context.rect(x, y, width, medalDisplayHeight));
3519
- context.strokeStyle = '#000';
3520
- context.lineWidth = 3;
3521
- context.stroke();
3522
- context.clip();
3523
-
3524
- // draw the icon and text
3525
- this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
3526
- context.textAlign = 'left';
3527
- context.font = '38px '+ fontDefault;
3528
- context.fillText(this.name, x+medalDisplayIconSize+30, y+28);
3529
- context.font = '24px '+ fontDefault;
3530
- context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
3531
- context.restore();
3532
- }
3533
-
3534
- /** Render the icon for a medal
3535
- * @param {Number} x - Screen space X position
3536
- * @param {Number} y - Screen space Y position
3537
- * @param {Number} [size=medalDisplayIconSize] - Screen space size
3538
- */
3539
- renderIcon(x, y, size=medalDisplayIconSize)
3540
- {
3541
- // draw the image or icon
3542
- const context = overlayContext;
3543
- context.fillStyle = '#000';
3544
- context.textAlign = 'center';
3545
- context.textBaseline = 'middle';
3546
- context.font = size*.7 + 'px '+ fontDefault;
3547
- if (this.image.src)
3548
- context.drawImage(this.image, x-size/2, y-size/2, size, size);
3549
- else
3550
- context.fillText(this.icon, x, y); // show icon if there is no image
3551
- }
3552
-
3553
- // Get local storage key used by the medal
3554
- storageKey() { return medalsSaveName + '_' + this.id; }
3555
- }
3556
-
3557
- // engine automatically renders medals
3558
- function medalsRender()
3559
- {
3560
- if (!medalsDisplayQueue.length)
3561
- return;
3562
-
3563
- // update first medal in queue
3564
- const medal = medalsDisplayQueue[0];
3565
- const time = timeReal - medalsDisplayTimeLast;
3566
- if (!medalsDisplayTimeLast)
3567
- medalsDisplayTimeLast = timeReal;
3568
- else if (time > medalDisplayTime)
3569
- medalsDisplayQueue.shift(medalsDisplayTimeLast = 0);
3570
- else
3571
- {
3572
- // slide on/off medals
3573
- const slideOffTime = medalDisplayTime - medalDisplaySlideTime;
3574
- const hidePercent =
3575
- time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime :
3576
- time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0;
3577
- medal.render(hidePercent);
3578
- }
3579
- }
3580
-
3581
- ///////////////////////////////////////////////////////////////////////////////
3582
-
3583
- // global Newgrounds object
3584
- let newgrounds;
3585
-
3586
- /** This can used to enable Newgrounds functionality
3587
- * @param {Number} app_id - The newgrounds App ID
3588
- * @param {String} [cipher] - The encryption Key (AES-128/Base64)
3589
- * @memberof Medals */
3590
- function newgroundsInit(app_id, cipher) { newgrounds = new Newgrounds(app_id, cipher); }
3591
-
3592
- /**
3593
- * Newgrounds API wrapper object
3594
- * @example
3595
- * // create a newgrounds object, replace the app id and cipher with your own
3596
- * const app_id = '53123:1ZuSTQ9l';
3597
- * const cipher = 'enF0vGH@Mj/FRASKL23Q==';
3598
- * newgrounds = new Newgrounds(app_id, cipher);
3599
- */
3600
- class Newgrounds
3601
- {
3602
- /** Create a newgrounds object
3603
- * @param {Number} app_id - The newgrounds App ID
3604
- * @param {String} [cipher] - The encryption Key (AES-128/Base64) */
3605
- constructor(app_id, cipher)
3606
- {
3607
- ASSERT(!newgrounds && app_id);
3608
- this.app_id = app_id;
3609
- this.cipher = cipher;
3610
- this.host = location ? location.hostname : '';
3611
-
3612
- // create an instance of CryptoJS for encrypted calls
3613
- cipher && (this.cryptoJS = CryptoJS());
3614
-
3615
- // get session id from url search params
3616
- const url = new URL(location.href);
3617
- this.session_id = url.searchParams.get('ngio_session_id') || 0;
3618
-
3619
- if (this.session_id == 0)
3620
- return; // only use newgrounds when logged in
3621
-
3622
- // get medals
3623
- const medalsResult = this.call('Medal.getList');
3624
- this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
3625
- debugMedals && console.log(this.medals);
3626
- for (const newgroundsMedal of this.medals)
3627
- {
3628
- const medal = medals[newgroundsMedal['id']];
3629
- if (medal)
3630
- {
3631
- // copy newgrounds medal data
3632
- medal.image.src = newgroundsMedal['icon'];
3633
- medal.name = newgroundsMedal['name'];
3634
- medal.description = newgroundsMedal['description'];
3635
- medal.unlocked = newgroundsMedal['unlocked'];
3636
- medal.difficulty = newgroundsMedal['difficulty'];
3637
- medal.value = newgroundsMedal['value'];
3638
-
3639
- if (medal.value)
3640
- medal.description = medal.description + ' (' + medal.value + ')';
3641
- }
3642
- }
3643
-
3644
- // get scoreboards
3645
- const scoreboardResult = this.call('ScoreBoard.getBoards');
3646
- this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
3647
- debugMedals && console.log(this.scoreboards);
3648
-
3649
- const keepAliveMS = 5 * 60 * 1e3;
3650
- setInterval(()=>this.call('Gateway.ping', 0, 1), keepAliveMS);
3651
- }
3652
-
3653
- /** Send message to unlock a medal by id
3654
- * @param {Number} id - The medal id */
3655
- unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, 1); }
3656
-
3657
- /** Send message to post score
3658
- * @param {Number} id - The scoreboard id
3659
- * @param {Number} value - The score value */
3660
- postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
3661
-
3662
- /** Get scores from a scoreboard
3663
- * @param {Number} id - The scoreboard id
3664
- * @param {String} [user=0] - A user's id or name
3665
- * @param {Number} [social=0] - If true, only social scores will be loaded
3666
- * @param {Number} [skip=0] - Number of scores to skip before start
3667
- * @param {Number} [limit=10] - Number of scores to include in the list
3668
- * @return {Object} - The response JSON object
3669
- */
3670
- getScores(id, user=0, social=0, skip=0, limit=10)
3671
- { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
3672
-
3673
- /** Send message to log a view */
3674
- logView() { return this.call('App.logView', {'host':this.host}, 1); }
3675
-
3676
- /** Send a message to call a component of the Newgrounds API
3677
- * @param {String} component - Name of the component
3678
- * @param {Object} [parameters=0] - Parameters to use for call
3679
- * @param {Boolean} [async=0] - If true, don't wait for response before continuing (avoid stall)
3680
- * @return {Object} - The response JSON object
3681
- */
3682
- call(component, parameters=0, async=0)
3683
- {
3684
- const call = {'component':component, 'parameters':parameters};
3685
- if (this.cipher)
3686
- {
3687
- // encrypt using AES-128 Base64 with cryptoJS
3688
- const cryptoJS = this.cryptoJS;
3689
- const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
3690
- const iv = cryptoJS['lib']['WordArray']['random'](16);
3691
- const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
3692
- call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
3693
- call['parameters'] = 0;
3694
- }
3695
-
3696
- // build the input object
3697
- const input =
3698
- {
3699
- 'app_id': this.app_id,
3700
- 'session_id': this.session_id,
3701
- 'call': call
3702
- };
3703
-
3704
- // build post data
3705
- const formData = new FormData();
3706
- formData.append('input', JSON.stringify(input));
3707
-
3708
- // send post data
3709
- const xmlHttp = new XMLHttpRequest();
3710
- const url = 'https://newgrounds.io/gateway_v3.php';
3711
- xmlHttp.open('POST', url, !debugMedals && async);
3712
- xmlHttp.send(formData);
3713
- debugMedals && console.log(xmlHttp.responseText);
3714
- return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
3715
- }
3716
- }
3717
-
3718
- ///////////////////////////////////////////////////////////////////////////////
3719
- // Crypto-JS - https://github.com/brix/crypto-js [The MIT License (MIT)]
3720
- // Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
3721
-
3722
- const CryptoJS=()=>eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",...']charCodeAtUinyxpf',"for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
3723
- /**
3724
- * LittleJS WebGL Interface
3725
- * <br> - All webgl used by the engine is wrapped up here
3726
- * <br> - For normal stuff you won't need to see or call anything in this file
3727
- * <br> - For advanced stuff there are helper functions to create shaders, textures, etc
3728
- * <br> - Can be disabled with glEnable to revert to 2D canvas rendering
3729
- * <br> - Batches sprite rendering on GPU for incredibly fast performance
3730
- * <br> - Sprite transform math is done in the shader where possible
3731
- * @namespace WebGL
3732
- */
3733
-
3734
- 'use strict';
3735
-
3736
- /** The WebGL canvas which appears above the main canvas and below the overlay canvas
3737
- * @type {HTMLCanvasElement}
3738
- * @memberof WebGL */
3739
- let glCanvas;
3740
-
3741
- /** 2d context for glCanvas
3742
- * @type {WebGLRenderingContext}
3743
- * @memberof WebGL */
3744
- let glContext;
3745
-
3746
- /** Main tile sheet texture automatically loaded by engine
3747
- * @type {WebGLTexture}
3748
- * @memberof WebGL */
3749
- let glTileTexture;
3750
-
3751
- // WebGL internal variables not exposed to documentation
3752
- let glActiveTexture, glShader, glArrayBuffer, glVertexData, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
3753
-
3754
- ///////////////////////////////////////////////////////////////////////////////
3755
-
3756
- // Init WebGL, called automatically by the engine
3757
- function glInit()
3758
- {
3759
- // create the canvas and tile texture
3760
- glCanvas = document.createElement('canvas');
3761
- glContext = glCanvas.getContext('webgl', {antialias: false});
3762
- glTileTexture = glCreateTexture(tileImage);
3763
-
3764
- // some browsers are much faster without copying the gl buffer so we just overlay it instead
3765
- glOverlay && document.body.appendChild(glCanvas);
3766
-
3767
- // setup vertex and fragment shaders
3768
- glShader = glCreateProgram(
3769
- 'precision highp float;'+ // use highp for better accuracy
3770
- 'uniform mat4 m;'+ // transform matrix
3771
- 'attribute vec2 p,t;'+ // position, uv
3772
- 'attribute vec4 c,a;'+ // color, additiveColor
3773
- 'varying vec2 v;'+ // return uv
3774
- 'varying vec4 d,e;'+ // return color, additiveColor
3775
- 'void main(){'+ // shader entry point
3776
- 'gl_Position=m*vec4(p,1,1);'+ // transform position
3777
- 'v=t;d=c;e=a;'+ // pass stuff to fragment shader
3778
- '}' // end of shader
3779
- ,
3780
- 'precision highp float;'+ // use highp for better accuracy
3781
- 'varying vec2 v;'+ // uv
3782
- 'varying vec4 d,e;'+ // color, additiveColor
3783
- 'uniform sampler2D s;'+ // texture
3784
- 'void main(){'+ // shader entry point
3785
- 'gl_FragColor=texture2D(s,v)*d+e;'+ // modulate texture by color plus additive
3786
- '}' // end of shader
3787
- );
3788
-
3789
- // init buffers
3790
- glVertexData = new ArrayBuffer(gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE);
3791
- glArrayBuffer = glContext.createBuffer();
3792
- glPositionData = new Float32Array(glVertexData);
3793
- glColorData = new Uint32Array(glVertexData);
3794
- glBatchCount = 0;
3795
- }
3796
-
3797
- /** Set the WebGl blend mode, normally you should call setBlendMode instead
3798
- * @param {Boolean} [additive=0]
3799
- * @memberof WebGL */
3800
- function glSetBlendMode(additive)
3801
- {
3802
- // setup blending
3803
- glAdditive = additive;
3804
- }
3805
-
3806
- /** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
3807
- * <br> - This may also flush the gl buffer resulting in more draw calls and worse performance
3808
- * @param {WebGLTexture} [texture=glTileTexture]
3809
- * @memberof WebGL */
3810
- function glSetTexture(texture=glTileTexture)
3811
- {
3812
- // must flush cache with the old texture to set a new one
3813
- if (texture != glActiveTexture)
3814
- glFlush();
3815
-
3816
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
3817
- }
3818
-
3819
- /** Compile WebGL shader of the given type, will throw errors if in debug mode
3820
- * @param {String} source
3821
- * @param type
3822
- * @return {WebGLShader}
3823
- * @memberof WebGL */
3824
- function glCompileShader(source, type)
3825
- {
3826
- // build the shader
3827
- const shader = glContext.createShader(type);
3828
- glContext.shaderSource(shader, source);
3829
- glContext.compileShader(shader);
3830
-
3831
- // check for errors
3832
- if (debug && !glContext.getShaderParameter(shader, gl_COMPILE_STATUS))
3833
- throw glContext.getShaderInfoLog(shader);
3834
- return shader;
3835
- }
3836
-
3837
- /** Create WebGL program with given shaders
3838
- * @param {WebGLShader} vsSource
3839
- * @param {WebGLShader} fsSource
3840
- * @return {WebGLProgram}
3841
- * @memberof WebGL */
3842
- function glCreateProgram(vsSource, fsSource)
3843
- {
3844
- // build the program
3845
- const program = glContext.createProgram();
3846
- glContext.attachShader(program, glCompileShader(vsSource, gl_VERTEX_SHADER));
3847
- glContext.attachShader(program, glCompileShader(fsSource, gl_FRAGMENT_SHADER));
3848
- glContext.linkProgram(program);
3849
-
3850
- // check for errors
3851
- if (debug && !glContext.getProgramParameter(program, gl_LINK_STATUS))
3852
- throw glContext.getProgramInfoLog(program);
3853
- return program;
3854
- }
3855
-
3856
- /** Create WebGL texture from an image and set the texture settings
3857
- * @param {Image} image
3858
- * @return {WebGLTexture}
3859
- * @memberof WebGL */
3860
- function glCreateTexture(image)
3861
- {
3862
- // build the texture
3863
- const texture = glContext.createTexture();
3864
- glContext.bindTexture(gl_TEXTURE_2D, texture);
3865
- image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
3866
-
3867
- // use point filtering for pixelated rendering
3868
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
3869
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
3870
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
3871
- glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
3872
- return texture;
3873
- }
3874
-
3875
- // called automatically by engine before render
3876
- function glPreRender(width, height, cameraX, cameraY, cameraScale)
3877
- {
3878
- // clear and set to same size as main canvas
3879
- glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
3880
- glContext.clear(gl_COLOR_BUFFER_BIT);
3881
-
3882
- // set up the shader
3883
- glContext.useProgram(glShader);
3884
- glContext.activeTexture(gl_TEXTURE0);
3885
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
3886
- glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
3887
- glContext.bufferData(gl_ARRAY_BUFFER, glVertexData.byteLength, gl_DYNAMIC_DRAW);
3888
- glSetBlendMode();
3889
-
3890
- // set vertex attributes
3891
- let offset = 0;
3892
- const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
3893
- {
3894
- const location = glContext.getAttribLocation(glShader, name);
3895
- glContext.enableVertexAttribArray(location);
3896
- glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
3897
- offset += size*typeSize;
3898
- }
3899
- initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
3900
- initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
3901
- initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
3902
- initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
3903
-
3904
- // build the transform matrix
3905
- const sx = 2 * cameraScale / width;
3906
- const sy = 2 * cameraScale / height;
3907
- glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
3908
- new Float32Array([
3909
- sx, 0, 0, 0,
3910
- 0, sy, 0, 0,
3911
- 1, 1, -1, 1,
3912
- -1-sx*cameraX, -1-sy*cameraY, 0, 0
3913
- ])
3914
- );
3915
- }
3916
-
3917
- /** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
3918
- * @memberof WebGL */
3919
- function glFlush()
3920
- {
3921
- if (!glBatchCount) return;
3922
-
3923
- const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
3924
- glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
3925
- glContext.enable(gl_BLEND);
3926
-
3927
- // draw all the sprites in the batch and reset the buffer
3928
- glContext.bufferSubData(gl_ARRAY_BUFFER, 0,
3929
- glPositionData.subarray(0, glBatchCount * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT));
3930
- glContext.drawArrays(gl_TRIANGLES, 0, glBatchCount * gl_VERTICES_PER_QUAD);
3931
- glBatchCount = 0;
3932
- glBatchAdditive = glAdditive;
3933
- }
3934
-
3935
- /** Draw any sprites still in the buffer, copy to main canvas and clear
3936
- * @param {CanvasRenderingContext2D} context
3937
- * @param {Boolean} [forceDraw=0]
3938
- * @memberof WebGL */
3939
- function glCopyToContext(context, forceDraw)
3940
- {
3941
- if (!glBatchCount && !forceDraw) return;
3942
-
3943
- glFlush();
3944
-
3945
- // do not draw in overlay mode because the canvas is visible
3946
- if (!glOverlay || forceDraw)
3947
- context.drawImage(glCanvas, 0, 0);
3948
- }
3949
-
3950
- /** Add a sprite to the gl draw list, used by all gl draw functions
3951
- * @param x
3952
- * @param y
3953
- * @param sizeX
3954
- * @param sizeY
3955
- * @param angle
3956
- * @param uv0X
3957
- * @param uv0Y
3958
- * @param uv1X
3959
- * @param uv1Y
3960
- * @param [rgba=0xffffffff]
3961
- * @param [rgbaAdditive=0]
3962
- * @memberof WebGL */
3963
- function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffffff, rgbaAdditive=0)
3964
- {
3965
- // flush if there is no room for more verts or if different blend mode
3966
- if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
3967
- glFlush();
3968
-
3969
- // prepare to create the verts from size and angle
3970
- const c = Math.cos(angle)/2, s = Math.sin(angle)/2;
3971
- const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
3972
-
3973
- // setup 2 triangles to form a quad
3974
- let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
3975
-
3976
- // vertex 0
3977
- glPositionData[offset++] = x - cx - sy;
3978
- glPositionData[offset++] = y - cy + sx;
3979
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3980
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3981
-
3982
- // vertex 1
3983
- glPositionData[offset++] = x + cx + sy;
3984
- glPositionData[offset++] = y + cy - sx;
3985
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
3986
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3987
-
3988
- // vertex 2
3989
- glPositionData[offset++] = x - cx + sy;
3990
- glPositionData[offset++] = y + cy + sx;
3991
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
3992
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3993
-
3994
- // vertex 0
3995
- glPositionData[offset++] = x - cx - sy;
3996
- glPositionData[offset++] = y - cy + sx;
3997
- glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
3998
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
3999
-
4000
- // vertex 3
4001
- glPositionData[offset++] = x + cx - sy;
4002
- glPositionData[offset++] = y - cy - sx;
4003
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
4004
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4005
-
4006
- // vertex 1
4007
- glPositionData[offset++] = x + cx + sy;
4008
- glPositionData[offset++] = y + cy - sx;
4009
- glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
4010
- glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
4011
- }
4012
-
4013
- ///////////////////////////////////////////////////////////////////////////////
4014
- // post processing - can be enabled to pass other canvases through a final shader
4015
-
4016
- let glPostShader, glPostArrayBuffer, glPostTexture;
4017
-
4018
- /** Set up a post processing shader
4019
- * @param {String} shaderCode
4020
- * @memberof WebGL */
4021
- function glInitPostProcess(shaderCode)
4022
- {
4023
- ASSERT(!glPostShader); // can only have 1 post effects shader
4024
-
4025
- if (!shaderCode) // default shader
4026
- shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture2D(iChannel0,p/iResolution.xy);}';
4027
-
4028
- // create the shader
4029
- glPostShader = glCreateProgram(
4030
- 'precision highp float;'+ // use highp for better accuracy
4031
- 'attribute vec2 p;'+ // position
4032
- 'void main(){'+ // shader entry point
4033
- 'gl_Position=vec4(p,1,1);'+ // set position
4034
- '}' // end of shader
4035
- ,
4036
- 'precision highp float;'+ // use highp for better accuracy
4037
- 'uniform sampler2D iChannel0;'+ // input texture
4038
- 'uniform vec3 iResolution;'+ // size of output texture
4039
- 'uniform float iTime;'+ // time passed
4040
- '\n' + shaderCode + '\n'+ // insert custom shader code
4041
- 'void main(){'+ // shader entry point
4042
- 'mainImage(gl_FragColor,gl_FragCoord.xy);'+ // call post process function
4043
- 'gl_FragColor.a=1.;'+ // always use full alpha
4044
- '}' // end of shader
4045
- );
4046
-
4047
- // create buffer and texture
4048
- glPostArrayBuffer = glContext.createBuffer();
4049
- glPostTexture = glCreateTexture();
4050
-
4051
- // hide the original 2d canvas
4052
- mainCanvas.style.visibility = 'hidden';
4053
- }
4054
-
4055
- // Render the post processing shader, called automatically by the engine
4056
- function glRenderPostProcess()
4057
- {
4058
- if (!glPostShader)
4059
- return;
4060
-
4061
- // prepare to render post process shader
4062
- const width = mainCanvas.width, height = mainCanvas.height;
4063
- if (glEnable)
4064
- {
4065
- glFlush(); // clear out the buffer
4066
- mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
4067
- }
4068
- else
4069
- glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height); // set viewport
4070
-
4071
- // setup shader program to draw one triangle
4072
- glContext.useProgram(glPostShader);
4073
- glContext.disable(gl_BLEND);
4074
- glContext.bindBuffer(gl_ARRAY_BUFFER, glPostArrayBuffer);
4075
- glContext.bufferData(gl_ARRAY_BUFFER, new Float32Array([-3,1,1,-3,1,1]), gl_STATIC_DRAW);
4076
- glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, true);
4077
-
4078
- // set textures, pass in the 2d canvas and gl canvas in separate texture channels
4079
- glContext.activeTexture(gl_TEXTURE0);
4080
- glContext.bindTexture(gl_TEXTURE_2D, glPostTexture);
4081
- glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, mainCanvas);
4082
-
4083
- // set vertex position attribute
4084
- const vertexByteStride = 8;
4085
- const pLocation = glContext.getAttribLocation(glPostShader, 'p');
4086
- glContext.enableVertexAttribArray(pLocation);
4087
- glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, 0, vertexByteStride, 0);
4088
-
4089
- // set uniforms and draw
4090
- const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
4091
- glContext.uniform1i(uniformLocation('iChannel0'), 0);
4092
- glContext.uniform1f(uniformLocation('iTime'), time);
4093
- glContext.uniform3f(uniformLocation('iResolution'), width, height, 1);
4094
- glContext.drawArrays(gl_TRIANGLES, 0, 3);
4095
- }
4096
-
4097
- ///////////////////////////////////////////////////////////////////////////////
4098
- // store gl constants as integers so their name doesn't use space in minifed
4099
- const
4100
- gl_ONE = 1,
4101
- gl_TRIANGLES = 4,
4102
- gl_SRC_ALPHA = 770,
4103
- gl_ONE_MINUS_SRC_ALPHA = 771,
4104
- gl_BLEND = 3042,
4105
- gl_TEXTURE_2D = 3553,
4106
- gl_UNSIGNED_BYTE = 5121,
4107
- gl_BYTE = 5120,
4108
- gl_FLOAT = 5126,
4109
- gl_RGBA = 6408,
4110
- gl_NEAREST = 9728,
4111
- gl_LINEAR = 9729,
4112
- gl_TEXTURE_MAG_FILTER = 10240,
4113
- gl_TEXTURE_MIN_FILTER = 10241,
4114
- gl_TEXTURE_WRAP_S = 10242,
4115
- gl_TEXTURE_WRAP_T = 10243,
4116
- gl_COLOR_BUFFER_BIT = 16384,
4117
- gl_CLAMP_TO_EDGE = 33071,
4118
- gl_TEXTURE0 = 33984,
4119
- gl_TEXTURE1 = 33985,
4120
- gl_ARRAY_BUFFER = 34962,
4121
- gl_STATIC_DRAW = 35044,
4122
- gl_DYNAMIC_DRAW = 35048,
4123
- gl_FRAGMENT_SHADER = 35632,
4124
- gl_VERTEX_SHADER = 35633,
4125
- gl_COMPILE_STATUS = 35713,
4126
- gl_LINK_STATUS = 35714,
4127
- gl_UNPACK_FLIP_Y_WEBGL = 37440,
4128
-
4129
- // constants for batch rendering
4130
- gl_VERTICES_PER_QUAD = 6,
4131
- gl_INDICIES_PER_VERT = 6,
4132
- gl_MAX_BATCH = 1<<16,
4133
- gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
4134
-
4135
- /**
4136
- * LittleJS Module Export
4137
- * <br> - Export engine as a module with extra functions where necessary
4138
- */
4139
-
4140
- // setters for all variables that devs will need to modify
4141
- const setCameraPos = (v)=> cameraPos = v;
4142
- const setCameraScale = (v)=> cameraScale = v;
4143
- const setRandSeed = (v)=> randSeed = v;
4144
- const setCanvasMaxSize = (v)=> canvasMaxSize = v;
4145
- const setCanvasFixedSize = (v)=> canvasFixedSize = v;
4146
- const setCavasPixelated = (v)=> cavasPixelated = v;
4147
- const setFontDefault = (v)=> fontDefault = v;
4148
- const setTileSizeDefault = (v)=> tileSizeDefault = v;
4149
- const setTileFixBleedScale = (v)=> tileFixBleedScale = v;
4150
- const setObjectDefaultSize = (v)=> objectDefaultSize = v;
4151
- const setEnablePhysicsSolver = (v)=> enablePhysicsSolver = v;
4152
- const setObjectDefaultMass = (v)=> objectDefaultMass = v;
4153
- const setObjectDefaultDamping = (v)=> objectDefaultDamping = v;
4154
- const setObjectDefaultAngleDamping = (v)=> objectDefaultAngleDamping = v;
4155
- const setObjectDefaultElasticity = (v)=> objectDefaultElasticity = v;
4156
- const setObjectDefaultFriction = (v)=> objectDefaultFriction = v;
4157
- const setObjectMaxSpeed = (v)=> objectMaxSpeed = v;
4158
- const setGravity = (v)=> gravity = v;
4159
- const setParticleEmitRateScale = (v)=> particleEmitRateScale = v;
4160
- const setGlEnable = (v)=> glEnable = v;
4161
- const setGlOverlay = (v)=> glOverlay = v;
4162
- const setGamepadsEnable = (v)=> gamepadsEnable = v;
4163
- const setGamepadDirectionEmulateStick = (v)=> gamepadDirectionEmulateStick = v;
4164
- const setInputWASDEmulateDirection = (v)=> inputWASDEmulateDirection = v;
4165
- const setTouchGamepadEnable = (v)=> touchGamepadEnable = v;
4166
- const setTouchGamepadAnalog = (v)=> touchGamepadAnalog = v;
4167
- const setTouchGamepadSize = (v)=> touchGamepadSize = v;
4168
- const setTouchGamepadAlpha = (v)=> touchGamepadAlpha = v;
4169
- const setVibrateEnable = (v)=> vibrateEnable = v;
4170
- const setSoundVolume = (v)=> soundVolume = v;
4171
- const setSoundEnable = (v)=> soundEnable = v;
4172
- const setSoundDefaultRange = (v)=> soundDefaultRange = v;
4173
- const setSoundDefaultTaper = (v)=> soundDefaultTaper = v;
4174
- const setMedalDisplayTime = (v)=> medalDisplayTime = v;
4175
- const setMedalDisplaySlideTime = (v)=> medalDisplaySlideTime = v;
4176
- const setMedalDisplayWidth = (v)=> medalDisplayWidth = v;
4177
- const setMedalDisplayHeight = (v)=> medalDisplayHeight = v;
4178
- const setMedalDisplayIconSize = (v)=> medalDisplayIconSize = v;
4179
- const setMedalsPreventUnlock = (v)=> medalsPreventUnlock = v;
4180
- const setShowWatermark = (v)=> showWatermark = v;
4181
- const setGodMode = (v)=> godMode = v;
4182
-
4183
- export {
4184
- // Custom methods
4185
- setCameraPos,
4186
- setCameraScale,
4187
- setRandSeed,
4188
- setCanvasMaxSize,
4189
- setCanvasFixedSize,
4190
- setCavasPixelated,
4191
- setFontDefault,
4192
- setTileSizeDefault,
4193
- setTileFixBleedScale,
4194
- setObjectDefaultSize,
4195
- setEnablePhysicsSolver,
4196
- setObjectDefaultMass,
4197
- setObjectDefaultDamping,
4198
- setObjectDefaultAngleDamping,
4199
- setObjectDefaultElasticity,
4200
- setObjectDefaultFriction,
4201
- setObjectMaxSpeed,
4202
- setGravity,
4203
- setParticleEmitRateScale,
4204
- setGlEnable,
4205
- setGlOverlay,
4206
- setGamepadsEnable,
4207
- setGamepadDirectionEmulateStick,
4208
- setInputWASDEmulateDirection,
4209
- setTouchGamepadEnable,
4210
- setTouchGamepadAnalog,
4211
- setTouchGamepadSize,
4212
- setTouchGamepadAlpha,
4213
- setVibrateEnable,
4214
- setSoundVolume,
4215
- setSoundEnable,
4216
- setSoundDefaultRange,
4217
- setSoundDefaultTaper,
4218
- setMedalDisplayTime,
4219
- setMedalDisplaySlideTime,
4220
- setMedalDisplayWidth,
4221
- setMedalDisplayHeight,
4222
- setMedalDisplayIconSize,
4223
- setMedalsPreventUnlock,
4224
- setShowWatermark,
4225
- setGodMode,
4226
-
4227
- // Settings
4228
- canvasMaxSize,
4229
- canvasFixedSize,
4230
- cavasPixelated,
4231
- fontDefault,
4232
- tileSizeDefault,
4233
- tileFixBleedScale,
4234
- objectDefaultSize,
4235
- enablePhysicsSolver,
4236
- objectDefaultMass,
4237
- objectDefaultDamping,
4238
- objectDefaultAngleDamping,
4239
- objectDefaultElasticity,
4240
- objectDefaultFriction,
4241
- objectMaxSpeed,
4242
- gravity,
4243
- particleEmitRateScale,
4244
- cameraPos,
4245
- cameraScale,
4246
- glEnable,
4247
- glOverlay,
4248
- gamepadsEnable,
4249
- gamepadDirectionEmulateStick,
4250
- inputWASDEmulateDirection,
4251
- touchGamepadEnable,
4252
- touchGamepadAnalog,
4253
- touchGamepadSize,
4254
- touchGamepadAlpha,
4255
- vibrateEnable,
4256
- soundVolume,
4257
- soundEnable,
4258
- soundDefaultRange,
4259
- soundDefaultTaper,
4260
- medalDisplayTime,
4261
- medalDisplaySlideTime,
4262
- medalDisplayWidth,
4263
- medalDisplayHeight,
4264
- medalDisplayIconSize,
4265
-
4266
- // Globals
4267
- debug,
4268
- showWatermark,
4269
- godMode,
4270
- // Debug
4271
- //debugPrimitives,
4272
- //debugOverlay,
4273
- //debugPhysics,
4274
- //debugRaycast,
4275
- //debugParticles,
4276
- //debugGamepads,
4277
- //debugMedals,
4278
- //debugTakeScreenshot,
4279
- //downloadLink,
4280
- //ASSERT,
4281
- debugRect,
4282
- debugCircle,
4283
- debugPoint,
4284
- debugLine,
4285
- debugAABB,
4286
- debugText,
4287
- debugClear,
4288
- debugSaveCanvas,
4289
- //debugInit,
4290
- //debugUpdate,
4291
- //debugRender,
4292
-
4293
- // Utilities
4294
- PI,
4295
- abs,
4296
- min,
4297
- max,
4298
- sign,
4299
- mod,
4300
- clamp,
4301
- percent,
4302
- lerp,
4303
- smoothStep,
4304
- nearestPowerOfTwo,
4305
- isOverlapping,
4306
- wave,
4307
- formatTime,
4308
-
4309
- // Random
4310
- rand,
4311
- randInt,
4312
- randSign,
4313
- randInCircle,
4314
- randVector,
4315
- randColor,
4316
- randSeed,
4317
- randSeeded,
4318
-
4319
- // Utility Classes
4320
- Vector2,
4321
- Color,
4322
- Timer,
4323
- vec2,
4324
- colorRGBA,
4325
- colorHSLA,
4326
-
4327
- // Base
4328
- EngineObject,
4329
-
4330
- // Draw
4331
- tileImage,
4332
- mainCanvas,
4333
- mainContext,
4334
- overlayCanvas,
4335
- overlayContext,
4336
- mainCanvasSize,
4337
- screenToWorld,
4338
- worldToScreen,
4339
- drawTile,
4340
- drawRect,
4341
- drawTileScreenSpace,
4342
- drawRectScreenSpace,
4343
- drawLine,
4344
- drawCanvas2D,
4345
- setBlendMode,
4346
- drawTextScreen,
4347
- drawText,
4348
- engineFontImage,
4349
- FontImage,
4350
- isFullscreen,
4351
- toggleFullscreen,
4352
-
4353
- // Input
4354
- keyIsDown,
4355
- keyWasPressed,
4356
- keyWasReleased,
4357
- clearInput,
4358
- mouseIsDown,
4359
- mouseWasPressed,
4360
- mouseWasReleased,
4361
- mousePos,
4362
- mousePosScreen,
4363
- mouseWheel,
4364
- isUsingGamepad,
4365
- preventDefaultInput,
4366
- gamepadIsDown,
4367
- gamepadWasPressed,
4368
- gamepadWasReleased,
4369
- gamepadStick,
4370
- //inputData,
4371
- //inputUpdate,
4372
- //inputUpdatePost,
4373
- // onkeydown,
4374
- // onkeyup,
4375
- //remapKeyCode,
4376
- // onmousedown,
4377
- // onmouseup,
4378
- // onmousemove,
4379
- // onwheel,
4380
- // oncontextmenu,
4381
- mouseToScreen,
4382
- //stickData,
4383
- gamepadsUpdate,
4384
- vibrate,
4385
- vibrateStop,
4386
- isTouchDevice,
4387
- //touchGamepadTimer,
4388
- touchGamepadCreate,
4389
- touchGamepadRender,
4390
-
4391
- // Audio
4392
- Sound,
4393
- Music,
4394
- playAudioFile,
4395
- speak,
4396
- speakStop,
4397
- getNoteFrequency,
4398
- audioContext,
4399
- playSamples,
4400
- zzfx,
4401
- //zzfxR,
4402
- //zzfxG,
4403
- //zzfxM,
4404
-
4405
- // Tiles
4406
- tileCollision,
4407
- tileCollisionSize,
4408
- initTileCollision,
4409
- setTileCollisionData,
4410
- getTileCollisionData,
4411
- tileCollisionTest,
4412
- tileCollisionRaycast,
4413
- TileLayerData,
4414
- TileLayer,
4415
-
4416
- // Particles
4417
- ParticleEmitter,
4418
- Particle,
4419
-
4420
- // Medals
4421
- medals,
4422
- medalsPreventUnlock,
4423
- medalsInit,
4424
- newgroundsInit,
4425
- Medal,
4426
- //medalsRender,
4427
- Newgrounds,
4428
- //CryptoJS,
4429
-
4430
- // WebGL
4431
- glCanvas,
4432
- glContext,
4433
- //glInit,
4434
- glSetBlendMode,
4435
- glSetTexture,
4436
- glCompileShader,
4437
- glCreateProgram,
4438
- glCreateTexture,
4439
- //glPreRender,
4440
- //glFlush,
4441
- //glCopyToContext,
4442
- //glDraw,
4443
- glInitPostProcess,
4444
- //glRenderPostProcess,
4445
-
4446
- // Engine
4447
- engineName,
4448
- engineVersion,
4449
- frameRate,
4450
- timeDelta,
4451
- engineObjects,
4452
- //engineObjectsCollide,
4453
- frame,
4454
- time,
4455
- timeReal,
4456
- paused,
4457
- averageFPS,
4458
- drawCount,
4459
- engineInit,
4460
- //enginePreRender,
4461
- //engineObjectsUpdate,
4462
- engineObjectsDestroy,
4463
- engineObjectsCallback,
4464
- };