litecanvas 0.79.4 → 0.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,8 +55,8 @@ function init() {
55
55
  bg = 0
56
56
  color = 3
57
57
  radius = 32
58
- posx = CENTERX
59
- posy = CENTERY
58
+ posx = CX // center X or (canvas width / 2)
59
+ posy = CY // center Y or (canvas width / 2)
60
60
  }
61
61
 
62
62
  // this function detect taps/clicks
package/dist/dist.dev.js CHANGED
@@ -8,7 +8,7 @@
8
8
  };
9
9
 
10
10
  // src/palette.js
11
- var colors = [
11
+ var defaultPalette = [
12
12
  "#111",
13
13
  "#6a7799",
14
14
  "#aec2c2",
@@ -50,38 +50,33 @@
50
50
  animate: true
51
51
  };
52
52
  settings = Object.assign(defaults, settings);
53
- let _initialized = false, _plugins = [], _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _deltaTime = 1 / 60, _accumulated = 0, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _rng_seed = Date.now(), _events = {
54
- init: false,
55
- update: false,
56
- draw: false,
57
- resized: false,
58
- tap: false,
59
- untap: false,
60
- tapping: false,
61
- tapped: false
62
- }, _helpers = {
63
- settings: Object.assign({}, settings),
64
- colors
53
+ let _initialized = false, _plugins = [], _canvas, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _deltaTime = 1 / 60, _accumulated = 0, _rafid, _fontFamily = "sans-serif", _fontSize = 20, _rng_seed = Date.now(), _colors = defaultPalette, _default_sound = [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1], _events = {
54
+ init: null,
55
+ update: null,
56
+ draw: null,
57
+ resized: null,
58
+ tap: null,
59
+ untap: null,
60
+ tapping: null,
61
+ tapped: null
65
62
  };
66
63
  const instance = {
64
+ /** @type {HTMLCanvasElement} */
65
+ CANVAS: null,
67
66
  /** @type {number} */
68
- WIDTH: 0,
67
+ W: 0,
69
68
  /** @type {number} */
70
- HEIGHT: 0,
71
- /** @type {HTMLCanvasElement} */
72
- CANVAS: false,
69
+ H: 0,
73
70
  /** @type {number} */
74
- ELAPSED: 0,
71
+ T: 0,
75
72
  /** @type {number} */
76
- CENTERX: 0,
73
+ CX: 0,
77
74
  /** @type {number} */
78
- CENTERY: 0,
75
+ CY: 0,
79
76
  /** @type {number} */
80
- MOUSEX: -1,
77
+ MX: -1,
81
78
  /** @type {number} */
82
- MOUSEY: -1,
83
- /** @type {number[]} */
84
- DEFAULT_SFX: [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1],
79
+ MY: -1,
85
80
  /** MATH API */
86
81
  /**
87
82
  * Twice the value of the mathematical constant PI (π).
@@ -234,6 +229,15 @@
234
229
  DEV: assert(isNumber(stop), "norm: 3rd param must be a number");
235
230
  return instance.map(value, start, stop, 0, 1);
236
231
  },
232
+ /**
233
+ * Interpolate between 2 values using a periodic function.
234
+ *
235
+ * @param {number} from - the lower bound
236
+ * @param {number} to - the higher bound
237
+ * @param {number} t - the amount
238
+ * @param {(n: number) => number} fn - the periodic function (which default to `Math.sin`)
239
+ */
240
+ wave: (from, to, t, fn = Math.sin) => from + (fn(t) + 1) / 2 * (to - from),
237
241
  /** RNG API */
238
242
  /**
239
243
  * Generates a pseudorandom float between min (inclusive) and max (exclusive)
@@ -273,18 +277,18 @@
273
277
  return math.floor(instance.rand(min, max + 1));
274
278
  },
275
279
  /**
276
- * If a value is passed, initializes the random number generator with an explicit seed value.
277
- * Otherwise, returns the current seed state.
280
+ * Initializes the random number generator with an explicit seed value.
281
+ *
282
+ * Note: The seed should be a integer number greater than or equal to zero.
278
283
  *
279
284
  * @param {number} value
280
- * @returns {number} the seed state
281
285
  */
282
- seed: (value) => {
286
+ rseed(value) {
283
287
  DEV: assert(
284
288
  null == value || isNumber(value) && value >= 0,
285
- "seed: 1st param must be a positive number or zero"
289
+ "rseed: 1st param must be a positive number or zero"
286
290
  );
287
- return null == value ? _rng_seed : _rng_seed = ~~value;
291
+ _rng_seed = ~~value;
288
292
  },
289
293
  /** BASIC GRAPHICS API */
290
294
  /**
@@ -520,7 +524,7 @@
520
524
  "text: 5th param must be a string"
521
525
  );
522
526
  _ctx.font = `${fontStyle} ${_fontSize}px ${_fontFamily}`;
523
- _ctx.fillStyle = instance.getcolor(color);
527
+ _ctx.fillStyle = _colors[~~color % _colors.length];
524
528
  _ctx.fillText(message, ~~x, ~~y);
525
529
  },
526
530
  /**
@@ -547,15 +551,15 @@
547
551
  /**
548
552
  * Sets the alignment used when drawing texts
549
553
  *
550
- * @param {string} align the horizontal alignment. Possible values: "left", "right", "center", "start" or "end"
551
- * @param {string} baseline the vertical alignment. Possible values: "top", "bottom", "middle", "hanging" or "ideographic"
554
+ * @param {CanvasTextAlign} align the horizontal alignment. Possible values: "left", "right", "center", "start" or "end"
555
+ * @param {CanvasTextBaseline} baseline the vertical alignment. Possible values: "top", "bottom", "middle", "hanging" or "ideographic"
552
556
  * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline
553
557
  * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign
554
558
  */
555
559
  textalign(align, baseline) {
556
560
  DEV: assert(
557
561
  null == align || ["left", "right", "center", "start", "end"].includes(align),
558
- "textalign: 1st param must be null or one of the following values: center, left, right, start or end."
562
+ "textalign: 1st param must be null or one of the following strings: center, left, right, start or end."
559
563
  );
560
564
  DEV: assert(
561
565
  null == baseline || [
@@ -566,7 +570,7 @@
566
570
  "alphabetic",
567
571
  "ideographic"
568
572
  ].includes(baseline),
569
- "textalign: 2nd param must be null or one of the following values: middle, top, bottom, hanging, alphabetic or ideographic."
573
+ "textalign: 2nd param must be null or one of the following strings: middle, top, bottom, hanging, alphabetic or ideographic."
570
574
  );
571
575
  if (align) _ctx.textAlign = align;
572
576
  if (baseline) _ctx.textBaseline = baseline;
@@ -592,7 +596,7 @@
592
596
  * @param {string[]|drawCallback} drawing
593
597
  * @param {object} [options]
594
598
  * @param {number} [options.scale=1]
595
- * @param {OffscreenCanvas | HTMLCanvasElement} [options.canvas]
599
+ * @param {OffscreenCanvas} [options.canvas]
596
600
  * @returns {ImageBitmap}
597
601
  * @see https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
598
602
  */
@@ -732,7 +736,7 @@
732
736
  null == path || path instanceof Path2D,
733
737
  "fill: 2nd param must be a Path2D instance"
734
738
  );
735
- _ctx.fillStyle = instance.getcolor(color);
739
+ _ctx.fillStyle = _colors[~~color % _colors.length];
736
740
  if (path) {
737
741
  _ctx.fill(path);
738
742
  } else {
@@ -754,7 +758,7 @@
754
758
  null == path || path instanceof Path2D,
755
759
  "stroke: 2nd param must be a Path2D instance"
756
760
  );
757
- _ctx.strokeStyle = instance.getcolor(color);
761
+ _ctx.strokeStyle = _colors[~~color % _colors.length];
758
762
  if (path) {
759
763
  _ctx.stroke(path);
760
764
  } else {
@@ -799,7 +803,7 @@
799
803
  if (root.zzfxV <= 0 || navigator.userActivation && !navigator.userActivation.hasBeenActive) {
800
804
  return false;
801
805
  }
802
- zzfxParams = zzfxParams || instance.DEFAULT_SFX;
806
+ zzfxParams = zzfxParams || _default_sound;
803
807
  if (pitchSlide !== 0 || volumeFactor !== 1) {
804
808
  zzfxParams = zzfxParams.slice();
805
809
  zzfxParams[0] = volumeFactor * (zzfxParams[0] || 1);
@@ -876,31 +880,32 @@
876
880
  }
877
881
  },
878
882
  /**
879
- * Get a color by index
883
+ * Set or reset the color palette
880
884
  *
881
- * @param {number} [index=0] The color number
882
- * @returns {string} the color code
885
+ * @param {string[]} [colors]
883
886
  */
884
- getcolor: (index) => {
887
+ pal(colors = defaultPalette) {
885
888
  DEV: assert(
886
- null == index || isNumber(index) && index >= 0,
887
- "getcolor: 1st param must be a number"
889
+ Array.isArray(colors) && colors.length > 0,
890
+ "pal: 1st param must be a array of strings"
888
891
  );
889
- return colors[~~index % colors.length];
892
+ _colors = colors;
890
893
  },
891
894
  /**
892
- * Create or update a instance variable
895
+ * Define or update a instance property.
893
896
  *
894
897
  * @param {string} key
895
898
  * @param {*} value
896
899
  */
897
- setvar(key, value) {
900
+ def(key, value) {
898
901
  DEV: assert(
899
902
  "string" === typeof key,
900
- "setvar: 1st param must be a string"
903
+ "def: 1st param must be a string"
901
904
  );
902
- if (null == value) {
903
- console.warn(`setvar: key "${key}" was defined as ${value}`);
905
+ DEV: if (null == value) {
906
+ console.warn(
907
+ `def: key "${key}" was defined as ${value} but now is null`
908
+ );
904
909
  }
905
910
  instance[key] = value;
906
911
  if (settings.global) {
@@ -916,33 +921,81 @@
916
921
  */
917
922
  timescale(value) {
918
923
  DEV: assert(
919
- isNumber(value),
920
- "timescale: 1st param must be a number"
924
+ isNumber(value) && value >= 0,
925
+ "timescale: 1st param must be a positive number or zero"
921
926
  );
922
927
  _timeScale = value;
923
928
  },
924
929
  /**
925
- * Set the target FPS at runtime.
930
+ * Set the target FPS (frames per second).
926
931
  *
927
932
  * @param {number} value
928
933
  */
929
- setfps(value) {
934
+ framerate(value) {
930
935
  DEV: assert(
931
936
  isNumber(value) && value >= 1,
932
- "setfps: 1st param must be a positive number"
937
+ "framerate: 1st param must be a positive number"
933
938
  );
934
939
  _deltaTime = 1 / ~~value;
935
940
  },
941
+ /**
942
+ * Returns information about that engine instance.
943
+ *
944
+ * n = 0: the settings passed to that instance
945
+ * n = 1: returns true if the "init" event has already been emitted
946
+ * n = 2: the current ID returned by last requestAnimationFrame
947
+ * n = 3: the current canvas element scale (not the context 2D scale)
948
+ * n = 4: the attached event callbacks
949
+ * n = 5: the current color palette
950
+ * n = 6: the default sound used by `sfx()`
951
+ * n = 7: the current time scale
952
+ * n = 8: the current volume used by ZzFX
953
+ * n = 9: the current RNG state
954
+ *
955
+ * n = any other value: returns undefined
956
+ *
957
+ * @param {number} n
958
+ * @returns {any}
959
+ */
960
+ stat(n) {
961
+ DEV: assert(
962
+ isNumber(n) && n >= 0,
963
+ "stat: 1st param must be a positive number"
964
+ );
965
+ const list = [
966
+ // 0
967
+ settings,
968
+ // 1
969
+ _initialized,
970
+ // 2
971
+ _rafid,
972
+ // 3
973
+ _scale,
974
+ // 4
975
+ _events,
976
+ // 5
977
+ _colors,
978
+ // 6
979
+ _default_sound,
980
+ // 7
981
+ _timeScale,
982
+ // 8
983
+ root.zzfxV || 1,
984
+ // 9
985
+ _rng_seed
986
+ ];
987
+ return list[n];
988
+ },
936
989
  /**
937
990
  * Stops the litecanvas instance and remove all event listeners.
938
991
  */
939
992
  quit() {
940
993
  cancelAnimationFrame(_rafid);
941
994
  instance.emit("quit");
942
- _events = [];
943
995
  for (const removeListener of _browserEventListeners) {
944
996
  removeListener();
945
997
  }
998
+ _events = {};
946
999
  if (settings.global) {
947
1000
  for (const key in instance) {
948
1001
  delete root[key];
@@ -955,7 +1008,6 @@
955
1008
  instance[k] = math[k];
956
1009
  }
957
1010
  function init() {
958
- _initialized = true;
959
1011
  const source = settings.loop ? settings.loop : root;
960
1012
  for (const event in _events) {
961
1013
  if (source[event]) instance.listen(event, source[event]);
@@ -987,55 +1039,90 @@
987
1039
  tap.y = y;
988
1040
  }, _checkTapped = (tap) => tap && performance.now() - tap.ts <= 300, preventDefault = (ev) => ev.preventDefault();
989
1041
  let _pressingMouse = false;
990
- on(_canvas, "mousedown", (ev) => {
991
- if (ev.button === 0) {
1042
+ on(
1043
+ _canvas,
1044
+ "mousedown",
1045
+ /**
1046
+ * @param {MouseEvent} ev
1047
+ */
1048
+ (ev) => {
1049
+ if (ev.button === 0) {
1050
+ preventDefault(ev);
1051
+ const [x, y] = _getXY(ev.pageX, ev.pageY);
1052
+ instance.emit("tap", x, y, 0);
1053
+ _registerTap(0, x, y);
1054
+ _pressingMouse = true;
1055
+ }
1056
+ }
1057
+ );
1058
+ on(
1059
+ _canvas,
1060
+ "mouseup",
1061
+ /**
1062
+ * @param {MouseEvent} ev
1063
+ */
1064
+ (ev) => {
1065
+ if (ev.button === 0) {
1066
+ preventDefault(ev);
1067
+ const tap = _taps.get(0);
1068
+ const [x, y] = _getXY(ev.pageX, ev.pageY);
1069
+ if (_checkTapped(tap)) {
1070
+ instance.emit("tapped", tap.startX, tap.startY, 0);
1071
+ }
1072
+ instance.emit("untap", x, y, 0);
1073
+ _taps.delete(0);
1074
+ _pressingMouse = false;
1075
+ }
1076
+ }
1077
+ );
1078
+ on(
1079
+ _canvas,
1080
+ "mousemove",
1081
+ /**
1082
+ * @param {MouseEvent} ev
1083
+ */
1084
+ (ev) => {
992
1085
  preventDefault(ev);
993
1086
  const [x, y] = _getXY(ev.pageX, ev.pageY);
994
- instance.emit("tap", x, y, 0);
995
- _registerTap(0, x, y);
996
- _pressingMouse = true;
1087
+ instance.def("MX", x);
1088
+ instance.def("MY", y);
1089
+ if (!_pressingMouse) return;
1090
+ instance.emit("tapping", x, y, 0);
1091
+ _updateTap(0, x, y);
997
1092
  }
998
- });
999
- on(_canvas, "mouseup", (ev) => {
1000
- if (ev.button === 0) {
1093
+ );
1094
+ on(
1095
+ _canvas,
1096
+ "touchstart",
1097
+ /**
1098
+ * @param {TouchEvent} ev
1099
+ */
1100
+ (ev) => {
1001
1101
  preventDefault(ev);
1002
- const tap = _taps.get(0);
1003
- const [x, y] = _getXY(ev.pageX, ev.pageY);
1004
- if (_checkTapped(tap)) {
1005
- instance.emit("tapped", tap.startX, tap.startY, 0);
1102
+ const touches = ev.changedTouches;
1103
+ for (const touch of touches) {
1104
+ const [x, y] = _getXY(touch.pageX, touch.pageY);
1105
+ instance.emit("tap", x, y, touch.identifier + 1);
1106
+ _registerTap(touch.identifier + 1, x, y);
1006
1107
  }
1007
- instance.emit("untap", x, y, 0);
1008
- _taps.delete(0);
1009
- _pressingMouse = false;
1010
1108
  }
1011
- });
1012
- on(_canvas, "mousemove", (ev) => {
1013
- preventDefault(ev);
1014
- const [x, y] = _getXY(ev.pageX, ev.pageY);
1015
- instance.setvar("MOUSEX", x);
1016
- instance.setvar("MOUSEY", y);
1017
- if (!_pressingMouse) return;
1018
- instance.emit("tapping", x, y, 0);
1019
- _updateTap(0, x, y);
1020
- });
1021
- on(_canvas, "touchstart", (ev) => {
1022
- preventDefault(ev);
1023
- const touches = ev.changedTouches;
1024
- for (const touch of touches) {
1025
- const [x, y] = _getXY(touch.pageX, touch.pageY);
1026
- instance.emit("tap", x, y, touch.identifier + 1);
1027
- _registerTap(touch.identifier + 1, x, y);
1028
- }
1029
- });
1030
- on(_canvas, "touchmove", (ev) => {
1031
- preventDefault(ev);
1032
- const touches = ev.changedTouches;
1033
- for (const touch of touches) {
1034
- const [x, y] = _getXY(touch.pageX, touch.pageY);
1035
- instance.emit("tapping", x, y, touch.identifier + 1);
1036
- _updateTap(touch.identifier + 1, x, y);
1109
+ );
1110
+ on(
1111
+ _canvas,
1112
+ "touchmove",
1113
+ /**
1114
+ * @param {TouchEvent} ev
1115
+ */
1116
+ (ev) => {
1117
+ preventDefault(ev);
1118
+ const touches = ev.changedTouches;
1119
+ for (const touch of touches) {
1120
+ const [x, y] = _getXY(touch.pageX, touch.pageY);
1121
+ instance.emit("tapping", x, y, touch.identifier + 1);
1122
+ _updateTap(touch.identifier + 1, x, y);
1123
+ }
1037
1124
  }
1038
- });
1125
+ );
1039
1126
  const _touchEndHandler = (ev) => {
1040
1127
  preventDefault(ev);
1041
1128
  const existing = [];
@@ -1083,7 +1170,7 @@
1083
1170
  });
1084
1171
  on(root, "blur", () => _keysDown.clear());
1085
1172
  instance.listen("after:draw", () => _keysPress.clear());
1086
- instance.setvar(
1173
+ instance.def(
1087
1174
  "iskeydown",
1088
1175
  /**
1089
1176
  * Checks if a which key is pressed (down) on the keyboard.
@@ -1100,7 +1187,7 @@
1100
1187
  return keyCheck(_keysDown, key);
1101
1188
  }
1102
1189
  );
1103
- instance.setvar(
1190
+ instance.def(
1104
1191
  "iskeypressed",
1105
1192
  /**
1106
1193
  * Checks if a which key just got pressed on the keyboard.
@@ -1124,10 +1211,12 @@
1124
1211
  });
1125
1212
  on(root, "focus", () => {
1126
1213
  if (!_rafid) {
1214
+ _accumulated = 0;
1127
1215
  _rafid = raf(drawFrame);
1128
1216
  }
1129
1217
  });
1130
1218
  }
1219
+ _initialized = true;
1131
1220
  instance.emit("init", instance);
1132
1221
  _lastFrameTime = performance.now();
1133
1222
  _rafid = raf(drawFrame);
@@ -1143,10 +1232,7 @@
1143
1232
  _accumulated += frameTime;
1144
1233
  while (_accumulated >= _deltaTime) {
1145
1234
  instance.emit("update", _deltaTime * _timeScale);
1146
- instance.setvar(
1147
- "ELAPSED",
1148
- instance.ELAPSED + _deltaTime * _timeScale
1149
- );
1235
+ instance.def("T", instance.T + _deltaTime * _timeScale);
1150
1236
  updated++;
1151
1237
  _accumulated -= _deltaTime;
1152
1238
  }
@@ -1159,18 +1245,23 @@
1159
1245
  }
1160
1246
  }
1161
1247
  function setupCanvas() {
1162
- _canvas = settings.canvas || document.createElement("canvas");
1163
- _canvas = "string" === typeof _canvas ? document.querySelector(_canvas) : _canvas;
1248
+ if ("string" === typeof settings.canvas) {
1249
+ _canvas = document.querySelector(settings.canvas);
1250
+ } else {
1251
+ _canvas = settings.canvas || document.createElement("canvas");
1252
+ }
1164
1253
  DEV: assert(
1165
1254
  _canvas && _canvas.tagName === "CANVAS",
1166
1255
  "Invalid canvas element"
1167
1256
  );
1168
- instance.setvar("CANVAS", _canvas);
1257
+ instance.def("CANVAS", _canvas);
1169
1258
  _ctx = _canvas.getContext("2d");
1170
1259
  on(_canvas, "click", () => root.focus());
1171
1260
  _canvas.style = "";
1172
1261
  resizeCanvas();
1173
- if (!_canvas.parentNode) document.body.appendChild(_canvas);
1262
+ if (!_canvas.parentNode) {
1263
+ document.body.appendChild(_canvas);
1264
+ }
1174
1265
  }
1175
1266
  function resizeCanvas() {
1176
1267
  DEV: assert(
@@ -1186,28 +1277,29 @@
1186
1277
  `Litecanvas' option "width" is required when the option "height" is defined`
1187
1278
  );
1188
1279
  const width = settings.width || root.innerWidth, height = settings.height || settings.width || root.innerHeight;
1189
- instance.setvar("WIDTH", _canvas.width = width);
1190
- instance.setvar("HEIGHT", _canvas.height = height);
1191
- instance.setvar("CENTERX", instance.WIDTH / 2);
1192
- instance.setvar("CENTERY", instance.HEIGHT / 2);
1280
+ instance.def("W", _canvas.width = width);
1281
+ instance.def("H", _canvas.height = height);
1282
+ instance.def("CX", instance.W / 2);
1283
+ instance.def("CY", instance.H / 2);
1193
1284
  if (settings.autoscale) {
1194
1285
  if (!_canvas.style.display) {
1195
1286
  _canvas.style.display = "block";
1196
1287
  _canvas.style.margin = "auto";
1197
1288
  }
1198
1289
  _scale = math.min(
1199
- root.innerWidth / instance.WIDTH,
1200
- root.innerHeight / instance.HEIGHT
1290
+ root.innerWidth / instance.W,
1291
+ root.innerHeight / instance.H
1201
1292
  );
1202
1293
  _scale = (settings.pixelart ? ~~_scale : _scale) || 1;
1203
- _canvas.style.width = instance.WIDTH * _scale + "px";
1204
- _canvas.style.height = instance.HEIGHT * _scale + "px";
1294
+ _canvas.style.width = instance.W * _scale + "px";
1295
+ _canvas.style.height = instance.H * _scale + "px";
1205
1296
  }
1206
1297
  if (!settings.antialias || settings.pixelart) {
1207
1298
  _ctx.imageSmoothingEnabled = false;
1208
1299
  _canvas.style.imageRendering = "pixelated";
1209
1300
  }
1210
1301
  instance.emit("resized", _scale);
1302
+ instance.cls(0);
1211
1303
  if (!settings.animate) {
1212
1304
  raf(drawFrame);
1213
1305
  }
@@ -1219,13 +1311,13 @@
1219
1311
  }
1220
1312
  }
1221
1313
  function loadPlugin(callback, config) {
1222
- const pluginData = callback(instance, _helpers, config);
1314
+ const pluginData = callback(instance, config);
1223
1315
  DEV: assert(
1224
1316
  null == pluginData || "object" === typeof pluginData,
1225
1317
  "Litecanvas plugins should return an object or nothing"
1226
1318
  );
1227
1319
  for (const key in pluginData) {
1228
- instance.setvar(key, pluginData[key]);
1320
+ instance.def(key, pluginData[key]);
1229
1321
  }
1230
1322
  }
1231
1323
  if (settings.global) {