litecanvas 0.82.0 → 0.83.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,12 +2,15 @@
2
2
 
3
3
  # Litecanvas
4
4
 
5
+ [![NPM Version](https://flat.badgen.net/npm/v/litecanvas?scale=1&label=NPM)](https://www.npmjs.com/package/litecanvas/)
6
+
5
7
  Litecanvas is a lightweight HTML5 canvas 2D engine suitable for small web games, prototypes, game jams, animations, creative coding, learning game programming and game design, etc.
6
8
 
7
9
  :warning: **This project is still under development. All feedback is appreciated!** :warning:
8
10
 
9
- [![Discord Server](https://flat.badgen.net/static/CHAT/ON%20DISCORD/5865f2?scale=1.5)](https://discord.com/invite/r2c3rGsvH3)
10
- [![Discord Server](https://flat.badgen.net/static/FOLLOW/ON%20ITCH.IO/fa5c5c?scale=1.5)](https://bills.itch.io/litecanvas)
11
+ [![Itch](https://flat.badgen.net/static/FOLLOW/ON%20ITCH.IO/fa5c5c?scale=1.25)](https://bills.itch.io/litecanvas)
12
+ [![Discord Server](https://flat.badgen.net/static/CHAT/ON%20DISCORD/5865f2?scale=1.25&icon=discord)](https://discord.com/invite/r2c3rGsvH3)
13
+ [![Playground](https://flat.badgen.net/static/CODE/ON%20PLAYGROUND/5f3dc4?scale=1.25)](https://litecanvas.js.org/)
11
14
 
12
15
  ### Features
13
16
 
@@ -38,8 +41,10 @@ If you prefer, you can manually install via [NPM](https://www.npmjs.com/package/
38
41
  npm install litecanvas
39
42
  ```
40
43
 
44
+ ### Show me the code!
45
+
41
46
  ```js
42
- // import the package or put the CDN script in your HTML
47
+ // import the package or put a script tag in your HTML
43
48
  // CDN: https://unpkg.com/litecanvas/dist/dist.dev.js
44
49
  import litecanvas from 'litecanvas'
45
50
 
@@ -49,43 +54,44 @@ litecanvas({
49
54
  loop: { init, update, draw, tapped },
50
55
  })
51
56
 
57
+ // this function runs once at the beginning
52
58
  function init() {
53
- // this function run once
54
- // before the game starts
55
- bg = 0
56
- color = 3
57
- radius = 32
58
- posx = CX // center X or (canvas width / 2)
59
- posy = CY // center Y or (canvas width / 2)
59
+ bg = 0 // the color #0 (black)
60
+ color = 3 // the color #3 (white)
61
+ radius = W / 10 // the canvas width/10
62
+ posx = CX // center X (or canvas width/2)
63
+ posy = CY // center Y (or canvas width/2)
60
64
  }
61
65
 
62
- // this function detect taps/clicks
63
- // and changes the circle position
66
+ // this function detect clicks/touches
64
67
  function tapped(x, y) {
68
+ // changes the circle position
69
+ // based on the position of the tap
65
70
  posx = x
66
71
  posy = y
67
72
  }
68
73
 
69
- // this function controls the game logic
74
+ // put the game logic in this function
70
75
  function update(dt) {
71
76
  // make the circle falls 100 pixels per second
72
- posy += 100 * dt
77
+ posy += 200 * dt
73
78
  }
74
79
 
75
- // this function render the game scene
80
+ // put the game rendering in this function
76
81
  function draw() {
77
82
  cls(bg) // clear the screen
78
83
  circfill(posx, posy, radius, color) // draw a circle
84
+ text(10, 10, 'Tap anywhere') // draw a text
79
85
  }
80
86
  ```
81
87
 
82
- ## Docs
88
+ [Play with this code in the playground](https://litecanvas.js.org?c=eJx9k91u4yAQhe%2F9FCP1ovauFZL2LlKu9hFaadtLDBNDQwDBeOOoyrsv4KR2V9FasuSfjzNnDgNj8EI8EHArISINHkghoO21xYoxMMiDhaMLuAVF5OOWMaMJBbd%2FeFx9xJULPeOdG2il6Ggekghp28dqpurPCtJlnPNb%2BARtNbUweMkJW5CBn1og7j1KuLTVpalyXVI6wn6wgrSzEAYbwVmBwKkY7DAZtKlO9cVk2bqBqVbXww7WUIQQhDMuwMMa6s5wcWgKMn3cwfM%2F1DPUJ5W8T1TgUg8xYb%2BBwWZWLI3BSUtSbLMuqHdxTOCvtwwJtIQB3qBOmt%2Fop%2BZGnzP9vqDf79KXO4FITOESCKPFITJyg1AY5yymOOuxhfMtkVxFcdtjnBrQQRjMLnRecUM6HtM2ZAU1%2FwS3L%2B9JddnouOzjfLXph2mDen7EtOO9FmljvpufbU4zUEtamDzyAy4d7rkxMSW%2FBq9HTI8%2BBRVROCvn%2Bj938JSIHyDpno%2BAVmJI0%2FIfL3kOv8ZHmFh3fVNCyyegSEUREKeksre9NqbOUbTFQ3sdlXaao7I2awK%2FdlIWEo5Ub9Yt5Pvxlft08M4nhQEflysyVl3%2BAgslJEg%3D)
83
89
 
84
- Check out our [Cheatsheet](https://litecanvas.js.org/about.html).
90
+ https://github.com/user-attachments/assets/854ac6bd-724f-4da8-bb3c-bc04dba5d8c8
85
91
 
86
- ## Basic Demos
92
+ ## Demos
87
93
 
88
- Try some demos in our playground:
94
+ Try some demos in the playground:
89
95
 
90
96
  - [Pong](https://litecanvas.js.org?c=eJy1Vlly20YQ%2FccpOl8ATBACuEiWYslFM5DlKkdMkUwkJpVKwcCQnAoEoIChpdiWr%2BAT%2BC%2BHyHlygVwh3TODTZYU%2F0QsDdELenm9DBMmIA%2FjCzgGf%2BA5BuAf0mdEj2vykkivEa%2BQHg01HbNSkALpKc6bMElaj6vmccHfsZZpYk3STUK8yhovpLeaWjVUmTMWIzmoQimjrGi9m%2FA1K5EcKlKwG0EOta4ICyFfX4dJyQwj4YJFYfo2LK33UuOax2J7BMMKhy3jm604gtFTZNzahrG3B9NdKbIr%2BPnd6SWU2S6NS1hnBYgtL2ETXjHSyQsmBGdFn29SjM%2BIsrQU8GL24%2Fk0QPe%2FOOCAz4b05XpDOgbIcGBAHAcOR54WVB8XJe6%2B0t%2F%2F9duHfSyWk%2FmSXHju2EFUYIxIyddGeAzbXsYHnvYsD%2B3IGz1m%2FsXk9evfZqeni%2Bk8CM7Jj%2B8OfZnOGB14rkdeyQ8eB9KbpPqH7iGe%2B3TSx3N9v3oayPfkOZKnTxka610aCZ6lwFMuLBveN42FfqfB%2BTKYXzYdVvNW0Iez4NXLsyXswajTBqhz8eq75RkK%2FIFx2%2FIhwjxnsXXjwB%2BVJ74G6xvdMhWv20Wi2LGGvb6xJPh2zUIEd0UqydvOnHz8aN1glHLq9mBgd0LZ5XEomBWL%2BwNRRgErJLjAsSmjgrH0ea2pJ%2BAYR6KtTJ0J2VtWPDekKrKukMS2ZRRHnLD2pCesyC05zSpmVVm7fjXOUlOglmi9j2SoDaqY6pCk0WcUjzbvSRHDGWzkPQXHiapQrarqpbAyaouq4r1moZzokrcLVbWKv%2B91eNQqBw2rWhnquw9%2Bp6Tdhrd1MSscVK2kVcizklMBW23aO1bL7IneW08gFq2OVeJVV6zWGCL7BndL1N5W2qfiK5%2FYLnew1o7byCgIP3zQQT2rZW2wVJzH0G%2FSbwKoG%2F22KZpK4UFjcmc%2FaqpOKNqy6HeVT5QlCS9pCq652LYbk1yitGCRUDk6%2BmKpA2g%2Fqdala0qeF7iI7HsCbCWr7pEeXXJfEbUEQElt1SZytXcHOS7C63pvRUlpefaji4W2VJjgurXMiKWCFaYD5hUnCEy7o1Vikla11LoiSy9Gp9qGaGM5%2BQGWM3U1oM2hfbeWqvVPaEKbcAjqNU8S60sw6edBbUdmx4tI6j5Ummp3PJDBfRhIhAiCbZhueLq5g4Hl4%2B1F%2F%2Bbfn%2F%2F8569PYLoFy1koVDa2A6P77bI0fsRqtW8q65Zp4jTJ7rBdzHtBUVl4E3v2XST%2F70piVAO8Xc2Xk%2B8DmP0UzM1ODb4wh%2FvEc5%2BO%2F8tqT6G4mM7mwREl%2B1UpY6f%2FC73Is6Q%3D)
91
97
  - [Bouncing Ball](https://litecanvas.js.org?c=eJxtUstugzAQvPMVewTiBCdtpVYpPVStSu6ReraMiSy5gIyhRUn%2BvQZvg0NzsGTveGdmH0oYqKtGGlmVkEIneLimlNgTkQDsW1Vcmn64HzTr8KpZLtvG%2Fr9%2FDAIljeCs7FgTRkFQtCUfyWQpTRjB0WNBgc%2Fd2z6LNyR7331k%2B2QTbSd2%2FEIRtNDZ42zrnBkR5sbx%2Fhlf%2FcAivajYVwy58fH%2BCu8d7hlzBGhhyvcyPHhKTxJ4raw3AVVRgMgPorFRWUA4OVtgr15gLBtOJ8%2F2EsFnoK6iK0txCsv19kaUrp4exrDXgBS4Yl%2B1p0xwTMQpo1ZkE89zl7Y%2BuPjEzs%2F99KOf%2F9LDyFwKKowC%2FtRyzb5xE7hqQjrOm0vNC6mUb3jiJGj9brYAw27YwnrHpoVpdQnHIWIlfwGU6d0i)
package/dist/dist.dev.js CHANGED
@@ -32,9 +32,7 @@
32
32
  function litecanvas(settings = {}) {
33
33
  const root = globalThis, math = Math, TWO_PI = math.PI * 2, raf = requestAnimationFrame, _browserEventListeners = [], on = (elem, evt, callback) => {
34
34
  elem.addEventListener(evt, callback, false);
35
- _browserEventListeners.push(
36
- () => elem.removeEventListener(evt, callback, false)
37
- );
35
+ _browserEventListeners.push(() => elem.removeEventListener(evt, callback, false));
38
36
  }, isNumber = Number.isFinite, defaults = {
39
37
  width: null,
40
38
  height: null,
@@ -93,7 +91,7 @@
93
91
  *
94
92
  * @type {number}
95
93
  */
96
- HALF_PI: math.PI / 2,
94
+ HALF_PI: TWO_PI / 4,
97
95
  /**
98
96
  * Calculates a linear (interpolation) value over t%.
99
97
  *
@@ -158,16 +156,13 @@
158
156
  * @param {number} max
159
157
  * @returns {number}
160
158
  */
161
- clamp: (value, min, max) => {
159
+ clamp: (value, min2, max2) => {
162
160
  DEV: assert(isNumber(value), "clamp: 1st param must be a number");
163
- DEV: assert(isNumber(min), "clamp: 2nd param must be a number");
164
- DEV: assert(isNumber(max), "clamp: 3rd param must be a number");
165
- DEV: assert(
166
- max > min,
167
- "randi: the 2nd param must be less than the 3rd param"
168
- );
169
- if (value < min) return min;
170
- if (value > max) return max;
161
+ DEV: assert(isNumber(min2), "clamp: 2nd param must be a number");
162
+ DEV: assert(isNumber(max2), "clamp: 3rd param must be a number");
163
+ DEV: assert(max2 > min2, "clamp: the 2nd param must be less than the 3rd param");
164
+ if (value < min2) return min2;
165
+ if (value > max2) return max2;
171
166
  return value;
172
167
  },
173
168
  /**
@@ -178,19 +173,12 @@
178
173
  * @param {number} max
179
174
  * @returns {number}
180
175
  */
181
- wrap: (value, min, max) => {
176
+ wrap: (value, min2, max2) => {
182
177
  DEV: assert(isNumber(value), "wrap: 1st param must be a number");
183
- DEV: assert(isNumber(min), "wrap: 2nd param must be a number");
184
- DEV: assert(isNumber(max), "wrap: 3rd param must be a number");
185
- DEV: assert(
186
- max > min,
187
- "randi: the 2nd param must be less than the 3rd param"
188
- );
189
- DEV: assert(
190
- max !== min,
191
- "randi: the 2nd param must be not equal to the 3rd param"
192
- );
193
- return value - (max - min) * math.floor((value - min) / (max - min));
178
+ DEV: assert(isNumber(min2), "wrap: 2nd param must be a number");
179
+ DEV: assert(isNumber(max2), "wrap: 3rd param must be a number");
180
+ DEV: assert(max2 > min2, "wrap: the 2nd param must be less than the 3rd param");
181
+ return value - (max2 - min2) * math.floor((value - min2) / (max2 - min2));
194
182
  },
195
183
  /**
196
184
  * Re-maps a number from one range to another.
@@ -209,6 +197,7 @@
209
197
  DEV: assert(isNumber(stop1), "map: 3rd param must be a number");
210
198
  DEV: assert(isNumber(start2), "map: 4th param must be a number");
211
199
  DEV: assert(isNumber(stop2), "map: 5th param must be a number");
200
+ DEV: assert(max !== min, "map: the 3rd param must be different than the 2nd param");
212
201
  const result = (value - start1) / (stop1 - start1) * (stop2 - start2) + start2;
213
202
  return withinBounds ? instance.clamp(result, start2, stop2) : result;
214
203
  },
@@ -233,10 +222,19 @@
233
222
  *
234
223
  * @param {number} from - the lower bound
235
224
  * @param {number} to - the higher bound
236
- * @param {number} t - the amount
237
- * @param {(n: number) => number} fn - the periodic function (which default to `Math.sin`)
225
+ * @param {number} t - value passed to the periodic function
226
+ * @param {(n: number) => number} [fn] - the periodic function (which default to `Math.sin`)
238
227
  */
239
- wave: (from, to, t, fn = Math.sin) => from + (fn(t) + 1) / 2 * (to - from),
228
+ wave: (from, to, t, fn = Math.sin) => {
229
+ DEV: assert(isNumber(from), "wave: 1st param must be a number");
230
+ DEV: assert(isNumber(to), "wave: 2nd param must be a number");
231
+ DEV: assert(isNumber(t), "wave: 3rd param must be a number");
232
+ DEV: assert(
233
+ "function" === typeof fn,
234
+ "wave: 4rd param must be a function (n: number) => number"
235
+ );
236
+ return from + (fn(t) + 1) / 2 * (to - from);
237
+ },
240
238
  /** RNG API */
241
239
  /**
242
240
  * Generates a pseudorandom float between min (inclusive) and max (exclusive)
@@ -246,18 +244,15 @@
246
244
  * @param {number} [max=1.0]
247
245
  * @returns {number} the random number
248
246
  */
249
- rand: (min = 0, max = 1) => {
250
- DEV: assert(isNumber(min), "rand: 1st param must be a number");
251
- DEV: assert(isNumber(max), "rand: 2nd param must be a number");
252
- DEV: assert(
253
- max > min,
254
- "rand: the 1st param must be less than the 2nd param"
255
- );
247
+ rand: (min2 = 0, max2 = 1) => {
248
+ DEV: assert(isNumber(min2), "rand: 1st param must be a number");
249
+ DEV: assert(isNumber(max2), "rand: 2nd param must be a number");
250
+ DEV: assert(max2 > min2, "rand: the 1st param must be less than the 2nd param");
256
251
  const a = 1664525;
257
252
  const c = 1013904223;
258
253
  const m = 4294967296;
259
254
  _rngSeed = (a * _rngSeed + c) % m;
260
- return _rngSeed / m * (max - min) + min;
255
+ return _rngSeed / m * (max2 - min2) + min2;
261
256
  },
262
257
  /**
263
258
  * Generates a pseudorandom integer between min (inclusive) and max (inclusive)
@@ -266,14 +261,11 @@
266
261
  * @param {number} [max=1]
267
262
  * @returns {number} the random number
268
263
  */
269
- randi: (min = 0, max = 1) => {
270
- DEV: assert(isNumber(min), "randi: 1st param must be a number");
271
- DEV: assert(isNumber(max), "randi: 2nd param must be a number");
272
- DEV: assert(
273
- max > min,
274
- "randi: the 1st param must be less than the 2nd param"
275
- );
276
- return math.floor(instance.rand(min, max + 1));
264
+ randi: (min2 = 0, max2 = 1) => {
265
+ DEV: assert(isNumber(min2), "randi: 1st param must be a number");
266
+ DEV: assert(isNumber(max2), "randi: 2nd param must be a number");
267
+ DEV: assert(max2 > min2, "randi: the 1st param must be less than the 2nd param");
268
+ return math.floor(instance.rand(min2, max2 + 1));
277
269
  },
278
270
  /**
279
271
  * Initializes the random number generator with an explicit seed value.
@@ -303,13 +295,7 @@
303
295
  if (null == color) {
304
296
  _ctx.clearRect(0, 0, _ctx.canvas.width, _ctx.canvas.height);
305
297
  } else {
306
- instance.rectfill(
307
- 0,
308
- 0,
309
- _ctx.canvas.width,
310
- _ctx.canvas.height,
311
- color
312
- );
298
+ instance.rectfill(0, 0, _ctx.canvas.width, _ctx.canvas.height, color);
313
299
  }
314
300
  },
315
301
  /**
@@ -321,14 +307,13 @@
321
307
  * @param {number} height
322
308
  * @param {number} [color=0] the color index
323
309
  * @param {number|number[]} [radii] A number or list specifying the radii used to draw a rounded-borders rectangle
310
+ *
311
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/roundRect
324
312
  */
325
313
  rect(x, y, width, height, color, radii) {
326
314
  DEV: assert(isNumber(x), "rect: 1st param must be a number");
327
315
  DEV: assert(isNumber(y), "rect: 2nd param must be a number");
328
- DEV: assert(
329
- isNumber(width) && width > 0,
330
- "rect: 3rd param must be a positive number"
331
- );
316
+ DEV: assert(isNumber(width) && width > 0, "rect: 3rd param must be a positive number");
332
317
  DEV: assert(
333
318
  isNumber(height) && height >= 0,
334
319
  "rect: 4th param must be a positive number or zero"
@@ -381,13 +366,7 @@
381
366
  "rectfill: 6th param must be a number or array of at least 2 numbers"
382
367
  );
383
368
  _ctx.beginPath();
384
- _ctx[radii ? "roundRect" : "rect"](
385
- ~~x,
386
- ~~y,
387
- ~~width,
388
- ~~height,
389
- radii
390
- );
369
+ _ctx[radii ? "roundRect" : "rect"](~~x, ~~y, ~~width, ~~height, radii);
391
370
  instance.fill(color);
392
371
  },
393
372
  /**
@@ -448,14 +427,8 @@
448
427
  line(x1, y1, x2, y2, color) {
449
428
  DEV: assert(isNumber(x1), "line: 1st param must be a number");
450
429
  DEV: assert(isNumber(y1), "line: 2nd param must be a number");
451
- DEV: assert(
452
- isNumber(x2),
453
- "line: 3rd param must be a positive number or zero"
454
- );
455
- DEV: assert(
456
- isNumber(y2),
457
- "line: 4th param must be a positive number or zero"
458
- );
430
+ DEV: assert(isNumber(x2), "line: 3rd param must be a positive number or zero");
431
+ DEV: assert(isNumber(y2), "line: 4th param must be a positive number or zero");
459
432
  DEV: assert(
460
433
  null == color || isNumber(color) && color >= 0,
461
434
  "line: 5th param must be a positive number or zero"
@@ -494,10 +467,7 @@
494
467
  Array.isArray(segments) && segments.length > 0,
495
468
  "linedash: 1st param must be an array of numbers"
496
469
  );
497
- DEV: assert(
498
- isNumber(offset),
499
- "linedash: 2nd param must be a number"
500
- );
470
+ DEV: assert(isNumber(offset), "linedash: 2nd param must be a number");
501
471
  _ctx.setLineDash(segments);
502
472
  _ctx.lineDashOffset = offset;
503
473
  },
@@ -518,10 +488,7 @@
518
488
  null == color || isNumber(color) && color >= 0,
519
489
  "text: 4th param must be a positive number or zero"
520
490
  );
521
- DEV: assert(
522
- "string" === typeof fontStyle,
523
- "text: 5th param must be a string"
524
- );
491
+ DEV: assert("string" === typeof fontStyle, "text: 5th param must be a string");
525
492
  _ctx.font = `${fontStyle} ${_fontSize}px ${_fontFamily}`;
526
493
  _ctx.fillStyle = _colors[~~color % _colors.length];
527
494
  _ctx.fillText(message, ~~x, ~~y);
@@ -532,10 +499,7 @@
532
499
  * @param {string} family
533
500
  */
534
501
  textfont(family) {
535
- DEV: assert(
536
- "string" === typeof family,
537
- "textfont: 1st param must be a string"
538
- );
502
+ DEV: assert("string" === typeof family, "textfont: 1st param must be a string");
539
503
  _fontFamily = family;
540
504
  },
541
505
  /**
@@ -561,14 +525,9 @@
561
525
  "textalign: 1st param must be null or one of the following strings: center, left, right, start or end."
562
526
  );
563
527
  DEV: assert(
564
- null == baseline || [
565
- "top",
566
- "bottom",
567
- "middle",
568
- "hanging",
569
- "alphabetic",
570
- "ideographic"
571
- ].includes(baseline),
528
+ null == baseline || ["top", "bottom", "middle", "hanging", "alphabetic", "ideographic"].includes(
529
+ baseline
530
+ ),
572
531
  "textalign: 2nd param must be null or one of the following strings: middle, top, bottom, hanging, alphabetic or ideographic."
573
532
  );
574
533
  if (align) _ctx.textAlign = align;
@@ -679,10 +638,7 @@
679
638
  */
680
639
  scale: (x, y) => {
681
640
  DEV: assert(isNumber(x), "scale: 1st param must be a number");
682
- DEV: assert(
683
- null == y || isNumber(y),
684
- "scale: 2nd param must be a number"
685
- );
641
+ DEV: assert(null == y || isNumber(y), "scale: 2nd param must be a number");
686
642
  return _ctx.scale(x, y || x);
687
643
  },
688
644
  /**
@@ -767,14 +723,13 @@
767
723
  /**
768
724
  * Turn given path into a clipping region.
769
725
  *
726
+ * Note: always call `push()` before and `pop()` after.
727
+ *
770
728
  * @param {Path2D} path
771
729
  * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/clip
772
730
  */
773
731
  clip(path) {
774
- DEV: assert(
775
- path instanceof Path2D,
776
- "clip: 1st param must be a Path2D instance"
777
- );
732
+ DEV: assert(path instanceof Path2D, "clip: 1st param must be a Path2D instance");
778
733
  _ctx.clip(path);
779
734
  },
780
735
  /** SOUND API */
@@ -795,10 +750,7 @@
795
750
  "sfx: 1st param must be an array"
796
751
  );
797
752
  DEV: assert(isNumber(pitchSlide), "sfx: 2nd param must be a number");
798
- DEV: assert(
799
- isNumber(volumeFactor),
800
- "sfx: 3rd param must be a number"
801
- );
753
+ DEV: assert(isNumber(volumeFactor), "sfx: 3rd param must be a number");
802
754
  if (root.zzfxV <= 0 || navigator.userActivation && !navigator.userActivation.hasBeenActive) {
803
755
  return false;
804
756
  }
@@ -828,15 +780,13 @@
828
780
  * @param {pluginCallback} callback
829
781
  */
830
782
  use(callback, config = {}) {
831
- DEV: assert(
832
- "function" === typeof callback,
833
- "use: 1st param must be a function"
834
- );
835
- DEV: assert(
836
- "object" === typeof config,
837
- "use: 2nd param must be an object"
838
- );
839
- _initialized ? loadPlugin(callback, config) : _plugins.push([callback, config]);
783
+ DEV: assert("function" === typeof callback, "use: 1st param must be a function");
784
+ DEV: assert("object" === typeof config, "use: 2nd param must be an object");
785
+ if (_initialized) {
786
+ loadPlugin(callback, config);
787
+ } else {
788
+ _plugins.push([callback, config]);
789
+ }
840
790
  },
841
791
  /**
842
792
  * Add a game event listener
@@ -846,14 +796,9 @@
846
796
  * @returns {Function} a function to remove the listener
847
797
  */
848
798
  listen(eventName, callback) {
849
- DEV: assert(
850
- "string" === typeof eventName,
851
- "listen: 1st param must be a string"
852
- );
853
- DEV: assert(
854
- "function" === typeof callback,
855
- "listen: 2nd param must be a function"
856
- );
799
+ DEV: assert("string" === typeof eventName, "listen: 1st param must be a string");
800
+ DEV: assert("function" === typeof callback, "listen: 2nd param must be a function");
801
+ eventName = eventName.toLowerCase();
857
802
  _events[eventName] = _events[eventName] || /* @__PURE__ */ new Set();
858
803
  _events[eventName].add(callback);
859
804
  return () => _events[eventName].delete(callback);
@@ -868,11 +813,9 @@
868
813
  * @param {*} [arg4] any data to be passed over the listeners
869
814
  */
870
815
  emit(eventName, arg1, arg2, arg3, arg4) {
871
- DEV: assert(
872
- "string" === typeof eventName,
873
- "emit: 1st param must be a string"
874
- );
816
+ DEV: assert("string" === typeof eventName, "emit: 1st param must be a string");
875
817
  if (_initialized) {
818
+ eventName = eventName.toLowerCase();
876
819
  triggerEvent("before:" + eventName, arg1, arg2, arg3, arg4);
877
820
  triggerEvent(eventName, arg1, arg2, arg3, arg4);
878
821
  triggerEvent("after:" + eventName, arg1, arg2, arg3, arg4);
@@ -897,14 +840,9 @@
897
840
  * @param {*} value
898
841
  */
899
842
  def(key, value) {
900
- DEV: assert(
901
- "string" === typeof key,
902
- "def: 1st param must be a string"
903
- );
843
+ DEV: assert("string" === typeof key, "def: 1st param must be a string");
904
844
  DEV: if (null == value) {
905
- console.warn(
906
- `def: key "${key}" was defined as ${value} but now is null`
907
- );
845
+ console.warn(`def: key "${key}" was defined as ${value} but now is null`);
908
846
  }
909
847
  instance[key] = value;
910
848
  if (settings.global) {
@@ -944,10 +882,7 @@
944
882
  * @returns {any}
945
883
  */
946
884
  stat(n) {
947
- DEV: assert(
948
- isNumber(n) && n >= 0,
949
- "stat: 1st param must be a positive number"
950
- );
885
+ DEV: assert(isNumber(n) && n >= 0, "stat: 1st param must be a positive number");
951
886
  const list = [
952
887
  // 0
953
888
  settings,
@@ -974,7 +909,9 @@
974
909
  // 11
975
910
  _fontFamily
976
911
  ];
977
- return list[n];
912
+ const data = { index: n, value: list[n] };
913
+ instance.emit("stat", data);
914
+ return data.value;
978
915
  },
979
916
  /**
980
917
  * Stops the litecanvas instance and remove all event listeners.
@@ -1010,25 +947,50 @@
1010
947
  on(root, "resize", resizeCanvas);
1011
948
  }
1012
949
  if (settings.tapEvents) {
1013
- const _getXY = (pageX, pageY) => [
1014
- (pageX - _canvas.offsetLeft) / _scale,
1015
- (pageY - _canvas.offsetTop) / _scale
1016
- ], _taps = /* @__PURE__ */ new Map(), _registerTap = (id, x, y) => {
1017
- const tap = {
1018
- x,
1019
- y,
1020
- startX: x,
1021
- startY: y,
1022
- // timestamp
1023
- ts: performance.now()
1024
- };
1025
- _taps.set(id, tap);
1026
- return tap;
1027
- }, _updateTap = (id, x, y) => {
1028
- const tap = _taps.get(id) || _registerTap(id);
1029
- tap.x = x;
1030
- tap.y = y;
1031
- }, _checkTapped = (tap) => tap && performance.now() - tap.ts <= 300, preventDefault = (ev) => ev.preventDefault();
950
+ const _getXY = (
951
+ /**
952
+ * @param {number} pageX
953
+ * @param {number} pageY
954
+ */
955
+ (pageX, pageY) => [
956
+ (pageX - _canvas.offsetLeft) / _scale,
957
+ (pageY - _canvas.offsetTop) / _scale
958
+ ]
959
+ ), _taps = /* @__PURE__ */ new Map(), _registerTap = (
960
+ /**
961
+ * @param {number} id
962
+ * @param {number} [x]
963
+ * @param {number} [y]
964
+ */
965
+ (id, x, y) => {
966
+ const tap = {
967
+ x,
968
+ y,
969
+ startX: x,
970
+ startY: y,
971
+ // timestamp
972
+ ts: performance.now()
973
+ };
974
+ _taps.set(id, tap);
975
+ return tap;
976
+ }
977
+ ), _updateTap = (
978
+ /**
979
+ * @param {number} id
980
+ * @param {number} x
981
+ * @param {number} y
982
+ */
983
+ (id, x, y) => {
984
+ const tap = _taps.get(id) || _registerTap(id);
985
+ tap.x = x;
986
+ tap.y = y;
987
+ }
988
+ ), _checkTapped = (
989
+ /**
990
+ * @param {{ts: number}} tap
991
+ */
992
+ (tap) => tap && performance.now() - tap.ts <= 300
993
+ ), preventDefault = (ev) => ev.preventDefault();
1032
994
  let _pressingMouse = false;
1033
995
  on(
1034
996
  _canvas,
@@ -1142,25 +1104,24 @@
1142
1104
  });
1143
1105
  }
1144
1106
  if (settings.keyboardEvents) {
1145
- const toLowerCase = (s) => s.toLowerCase();
1146
1107
  const _keysDown = /* @__PURE__ */ new Set();
1147
1108
  const _keysPress = /* @__PURE__ */ new Set();
1148
- const keyCheck = (keysSet, key) => {
1149
- return !key ? keysSet.size > 0 : keysSet.has(
1150
- "space" === toLowerCase(key) ? " " : toLowerCase(key)
1151
- );
1109
+ const keyCheck = (keySet, key = "") => {
1110
+ key = key.toLowerCase();
1111
+ return !key ? keySet.size > 0 : keySet.has("space" === key ? " " : key);
1152
1112
  };
1153
1113
  on(root, "keydown", (event) => {
1154
- if (!_keysDown.has(toLowerCase(event.key))) {
1155
- _keysDown.add(toLowerCase(event.key));
1156
- _keysPress.add(toLowerCase(event.key));
1114
+ const key = event.key.toLowerCase();
1115
+ if (!_keysDown.has(key)) {
1116
+ _keysDown.add(key);
1117
+ _keysPress.add(key);
1157
1118
  }
1158
1119
  });
1159
1120
  on(root, "keyup", (event) => {
1160
- _keysDown.delete(toLowerCase(event.key));
1121
+ _keysDown.delete(event.key.toLowerCase());
1161
1122
  });
1162
1123
  on(root, "blur", () => _keysDown.clear());
1163
- instance.listen("after:draw", () => _keysPress.clear());
1124
+ instance.listen("after:update", () => _keysPress.clear());
1164
1125
  instance.def(
1165
1126
  "iskeydown",
1166
1127
  /**
@@ -1234,10 +1195,7 @@
1234
1195
  _canvas = document.querySelector(settings.canvas);
1235
1196
  }
1236
1197
  _canvas = _canvas || document.createElement("canvas");
1237
- DEV: assert(
1238
- _canvas && _canvas.tagName === "CANVAS",
1239
- "Invalid canvas element"
1240
- );
1198
+ DEV: assert(_canvas && _canvas.tagName === "CANVAS", "Invalid canvas element");
1241
1199
  instance.def("CANVAS", _canvas);
1242
1200
  _ctx = _canvas.getContext("2d");
1243
1201
  on(_canvas, "click", () => root.focus());
@@ -1270,10 +1228,7 @@
1270
1228
  _canvas.style.display = "block";
1271
1229
  _canvas.style.margin = "auto";
1272
1230
  }
1273
- _scale = math.min(
1274
- root.innerWidth / instance.W,
1275
- root.innerHeight / instance.H
1276
- );
1231
+ _scale = math.min(root.innerWidth / instance.W, root.innerHeight / instance.H);
1277
1232
  _scale = (settings.pixelart ? ~~_scale : _scale) || 1;
1278
1233
  _canvas.style.width = instance.W * _scale + "px";
1279
1234
  _canvas.style.height = instance.H * _scale + "px";