litecanvas 0.80.0 → 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
@@ -50,39 +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(), _colors = defaultPalette, _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)
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
64
62
  };
65
63
  const instance = {
64
+ /** @type {HTMLCanvasElement} */
65
+ CANVAS: null,
66
66
  /** @type {number} */
67
- WIDTH: 0,
67
+ W: 0,
68
68
  /** @type {number} */
69
- HEIGHT: 0,
70
- /** @type {HTMLCanvasElement} */
71
- CANVAS: false,
69
+ H: 0,
72
70
  /** @type {number} */
73
- ELAPSED: 0,
71
+ T: 0,
74
72
  /** @type {number} */
75
- CENTERX: 0,
73
+ CX: 0,
76
74
  /** @type {number} */
77
- CENTERY: 0,
75
+ CY: 0,
78
76
  /** @type {number} */
79
- MOUSEX: -1,
77
+ MX: -1,
80
78
  /** @type {number} */
81
- MOUSEY: -1,
82
- /** @type {number[]} */
83
- DEFAULT_SFX: [0.5, 0, 1750, , , 0.3, 1, , , , 600, 0.1],
84
- /** @type {string[]} */
85
- COLORS: _colors,
79
+ MY: -1,
86
80
  /** MATH API */
87
81
  /**
88
82
  * Twice the value of the mathematical constant PI (π).
@@ -235,6 +229,15 @@
235
229
  DEV: assert(isNumber(stop), "norm: 3rd param must be a number");
236
230
  return instance.map(value, start, stop, 0, 1);
237
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),
238
241
  /** RNG API */
239
242
  /**
240
243
  * Generates a pseudorandom float between min (inclusive) and max (exclusive)
@@ -274,18 +277,18 @@
274
277
  return math.floor(instance.rand(min, max + 1));
275
278
  },
276
279
  /**
277
- * If a value is passed, initializes the random number generator with an explicit seed value.
278
- * 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.
279
283
  *
280
284
  * @param {number} value
281
- * @returns {number} the seed state
282
285
  */
283
- seed: (value) => {
286
+ rseed(value) {
284
287
  DEV: assert(
285
288
  null == value || isNumber(value) && value >= 0,
286
- "seed: 1st param must be a positive number or zero"
289
+ "rseed: 1st param must be a positive number or zero"
287
290
  );
288
- return null == value ? _rng_seed : _rng_seed = ~~value;
291
+ _rng_seed = ~~value;
289
292
  },
290
293
  /** BASIC GRAPHICS API */
291
294
  /**
@@ -521,7 +524,7 @@
521
524
  "text: 5th param must be a string"
522
525
  );
523
526
  _ctx.font = `${fontStyle} ${_fontSize}px ${_fontFamily}`;
524
- _ctx.fillStyle = instance.getcolor(color);
527
+ _ctx.fillStyle = _colors[~~color % _colors.length];
525
528
  _ctx.fillText(message, ~~x, ~~y);
526
529
  },
527
530
  /**
@@ -548,15 +551,15 @@
548
551
  /**
549
552
  * Sets the alignment used when drawing texts
550
553
  *
551
- * @param {string} align the horizontal alignment. Possible values: "left", "right", "center", "start" or "end"
552
- * @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"
553
556
  * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline
554
557
  * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign
555
558
  */
556
559
  textalign(align, baseline) {
557
560
  DEV: assert(
558
561
  null == align || ["left", "right", "center", "start", "end"].includes(align),
559
- "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."
560
563
  );
561
564
  DEV: assert(
562
565
  null == baseline || [
@@ -567,7 +570,7 @@
567
570
  "alphabetic",
568
571
  "ideographic"
569
572
  ].includes(baseline),
570
- "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."
571
574
  );
572
575
  if (align) _ctx.textAlign = align;
573
576
  if (baseline) _ctx.textBaseline = baseline;
@@ -593,7 +596,7 @@
593
596
  * @param {string[]|drawCallback} drawing
594
597
  * @param {object} [options]
595
598
  * @param {number} [options.scale=1]
596
- * @param {OffscreenCanvas | HTMLCanvasElement} [options.canvas]
599
+ * @param {OffscreenCanvas} [options.canvas]
597
600
  * @returns {ImageBitmap}
598
601
  * @see https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
599
602
  */
@@ -733,7 +736,7 @@
733
736
  null == path || path instanceof Path2D,
734
737
  "fill: 2nd param must be a Path2D instance"
735
738
  );
736
- _ctx.fillStyle = instance.getcolor(color);
739
+ _ctx.fillStyle = _colors[~~color % _colors.length];
737
740
  if (path) {
738
741
  _ctx.fill(path);
739
742
  } else {
@@ -755,7 +758,7 @@
755
758
  null == path || path instanceof Path2D,
756
759
  "stroke: 2nd param must be a Path2D instance"
757
760
  );
758
- _ctx.strokeStyle = instance.getcolor(color);
761
+ _ctx.strokeStyle = _colors[~~color % _colors.length];
759
762
  if (path) {
760
763
  _ctx.stroke(path);
761
764
  } else {
@@ -800,7 +803,7 @@
800
803
  if (root.zzfxV <= 0 || navigator.userActivation && !navigator.userActivation.hasBeenActive) {
801
804
  return false;
802
805
  }
803
- zzfxParams = zzfxParams || instance.DEFAULT_SFX;
806
+ zzfxParams = zzfxParams || _default_sound;
804
807
  if (pitchSlide !== 0 || volumeFactor !== 1) {
805
808
  zzfxParams = zzfxParams.slice();
806
809
  zzfxParams[0] = volumeFactor * (zzfxParams[0] || 1);
@@ -887,34 +890,22 @@
887
890
  "pal: 1st param must be a array of strings"
888
891
  );
889
892
  _colors = colors;
890
- instance.setvar("COLORS", _colors);
891
893
  },
892
894
  /**
893
- * Get a color by index
894
- *
895
- * @param {number} [index=0] The color number
896
- * @returns {string} the color code
897
- */
898
- getcolor: (index) => {
899
- DEV: assert(
900
- null == index || isNumber(index) && index >= 0,
901
- "getcolor: 1st param must be a number"
902
- );
903
- return _colors[~~index % _colors.length];
904
- },
905
- /**
906
- * Create or update a instance variable
895
+ * Define or update a instance property.
907
896
  *
908
897
  * @param {string} key
909
898
  * @param {*} value
910
899
  */
911
- setvar(key, value) {
900
+ def(key, value) {
912
901
  DEV: assert(
913
902
  "string" === typeof key,
914
- "setvar: 1st param must be a string"
903
+ "def: 1st param must be a string"
915
904
  );
916
- if (null == value) {
917
- 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
+ );
918
909
  }
919
910
  instance[key] = value;
920
911
  if (settings.global) {
@@ -930,33 +921,81 @@
930
921
  */
931
922
  timescale(value) {
932
923
  DEV: assert(
933
- isNumber(value),
934
- "timescale: 1st param must be a number"
924
+ isNumber(value) && value >= 0,
925
+ "timescale: 1st param must be a positive number or zero"
935
926
  );
936
927
  _timeScale = value;
937
928
  },
938
929
  /**
939
- * Set the target FPS at runtime.
930
+ * Set the target FPS (frames per second).
940
931
  *
941
932
  * @param {number} value
942
933
  */
943
- setfps(value) {
934
+ framerate(value) {
944
935
  DEV: assert(
945
936
  isNumber(value) && value >= 1,
946
- "setfps: 1st param must be a positive number"
937
+ "framerate: 1st param must be a positive number"
947
938
  );
948
939
  _deltaTime = 1 / ~~value;
949
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
+ },
950
989
  /**
951
990
  * Stops the litecanvas instance and remove all event listeners.
952
991
  */
953
992
  quit() {
954
993
  cancelAnimationFrame(_rafid);
955
994
  instance.emit("quit");
956
- _events = [];
957
995
  for (const removeListener of _browserEventListeners) {
958
996
  removeListener();
959
997
  }
998
+ _events = {};
960
999
  if (settings.global) {
961
1000
  for (const key in instance) {
962
1001
  delete root[key];
@@ -969,7 +1008,6 @@
969
1008
  instance[k] = math[k];
970
1009
  }
971
1010
  function init() {
972
- _initialized = true;
973
1011
  const source = settings.loop ? settings.loop : root;
974
1012
  for (const event in _events) {
975
1013
  if (source[event]) instance.listen(event, source[event]);
@@ -1001,55 +1039,90 @@
1001
1039
  tap.y = y;
1002
1040
  }, _checkTapped = (tap) => tap && performance.now() - tap.ts <= 300, preventDefault = (ev) => ev.preventDefault();
1003
1041
  let _pressingMouse = false;
1004
- on(_canvas, "mousedown", (ev) => {
1005
- 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) => {
1006
1085
  preventDefault(ev);
1007
1086
  const [x, y] = _getXY(ev.pageX, ev.pageY);
1008
- instance.emit("tap", x, y, 0);
1009
- _registerTap(0, x, y);
1010
- _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);
1011
1092
  }
1012
- });
1013
- on(_canvas, "mouseup", (ev) => {
1014
- if (ev.button === 0) {
1093
+ );
1094
+ on(
1095
+ _canvas,
1096
+ "touchstart",
1097
+ /**
1098
+ * @param {TouchEvent} ev
1099
+ */
1100
+ (ev) => {
1015
1101
  preventDefault(ev);
1016
- const tap = _taps.get(0);
1017
- const [x, y] = _getXY(ev.pageX, ev.pageY);
1018
- if (_checkTapped(tap)) {
1019
- 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);
1020
1107
  }
1021
- instance.emit("untap", x, y, 0);
1022
- _taps.delete(0);
1023
- _pressingMouse = false;
1024
- }
1025
- });
1026
- on(_canvas, "mousemove", (ev) => {
1027
- preventDefault(ev);
1028
- const [x, y] = _getXY(ev.pageX, ev.pageY);
1029
- instance.setvar("MOUSEX", x);
1030
- instance.setvar("MOUSEY", y);
1031
- if (!_pressingMouse) return;
1032
- instance.emit("tapping", x, y, 0);
1033
- _updateTap(0, x, y);
1034
- });
1035
- on(_canvas, "touchstart", (ev) => {
1036
- preventDefault(ev);
1037
- const touches = ev.changedTouches;
1038
- for (const touch of touches) {
1039
- const [x, y] = _getXY(touch.pageX, touch.pageY);
1040
- instance.emit("tap", x, y, touch.identifier + 1);
1041
- _registerTap(touch.identifier + 1, x, y);
1042
1108
  }
1043
- });
1044
- on(_canvas, "touchmove", (ev) => {
1045
- preventDefault(ev);
1046
- const touches = ev.changedTouches;
1047
- for (const touch of touches) {
1048
- const [x, y] = _getXY(touch.pageX, touch.pageY);
1049
- instance.emit("tapping", x, y, touch.identifier + 1);
1050
- _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
+ }
1051
1124
  }
1052
- });
1125
+ );
1053
1126
  const _touchEndHandler = (ev) => {
1054
1127
  preventDefault(ev);
1055
1128
  const existing = [];
@@ -1097,7 +1170,7 @@
1097
1170
  });
1098
1171
  on(root, "blur", () => _keysDown.clear());
1099
1172
  instance.listen("after:draw", () => _keysPress.clear());
1100
- instance.setvar(
1173
+ instance.def(
1101
1174
  "iskeydown",
1102
1175
  /**
1103
1176
  * Checks if a which key is pressed (down) on the keyboard.
@@ -1114,7 +1187,7 @@
1114
1187
  return keyCheck(_keysDown, key);
1115
1188
  }
1116
1189
  );
1117
- instance.setvar(
1190
+ instance.def(
1118
1191
  "iskeypressed",
1119
1192
  /**
1120
1193
  * Checks if a which key just got pressed on the keyboard.
@@ -1143,6 +1216,7 @@
1143
1216
  }
1144
1217
  });
1145
1218
  }
1219
+ _initialized = true;
1146
1220
  instance.emit("init", instance);
1147
1221
  _lastFrameTime = performance.now();
1148
1222
  _rafid = raf(drawFrame);
@@ -1158,10 +1232,7 @@
1158
1232
  _accumulated += frameTime;
1159
1233
  while (_accumulated >= _deltaTime) {
1160
1234
  instance.emit("update", _deltaTime * _timeScale);
1161
- instance.setvar(
1162
- "ELAPSED",
1163
- instance.ELAPSED + _deltaTime * _timeScale
1164
- );
1235
+ instance.def("T", instance.T + _deltaTime * _timeScale);
1165
1236
  updated++;
1166
1237
  _accumulated -= _deltaTime;
1167
1238
  }
@@ -1174,18 +1245,23 @@
1174
1245
  }
1175
1246
  }
1176
1247
  function setupCanvas() {
1177
- _canvas = settings.canvas || document.createElement("canvas");
1178
- _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
+ }
1179
1253
  DEV: assert(
1180
1254
  _canvas && _canvas.tagName === "CANVAS",
1181
1255
  "Invalid canvas element"
1182
1256
  );
1183
- instance.setvar("CANVAS", _canvas);
1257
+ instance.def("CANVAS", _canvas);
1184
1258
  _ctx = _canvas.getContext("2d");
1185
1259
  on(_canvas, "click", () => root.focus());
1186
1260
  _canvas.style = "";
1187
1261
  resizeCanvas();
1188
- if (!_canvas.parentNode) document.body.appendChild(_canvas);
1262
+ if (!_canvas.parentNode) {
1263
+ document.body.appendChild(_canvas);
1264
+ }
1189
1265
  }
1190
1266
  function resizeCanvas() {
1191
1267
  DEV: assert(
@@ -1201,28 +1277,29 @@
1201
1277
  `Litecanvas' option "width" is required when the option "height" is defined`
1202
1278
  );
1203
1279
  const width = settings.width || root.innerWidth, height = settings.height || settings.width || root.innerHeight;
1204
- instance.setvar("WIDTH", _canvas.width = width);
1205
- instance.setvar("HEIGHT", _canvas.height = height);
1206
- instance.setvar("CENTERX", instance.WIDTH / 2);
1207
- 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);
1208
1284
  if (settings.autoscale) {
1209
1285
  if (!_canvas.style.display) {
1210
1286
  _canvas.style.display = "block";
1211
1287
  _canvas.style.margin = "auto";
1212
1288
  }
1213
1289
  _scale = math.min(
1214
- root.innerWidth / instance.WIDTH,
1215
- root.innerHeight / instance.HEIGHT
1290
+ root.innerWidth / instance.W,
1291
+ root.innerHeight / instance.H
1216
1292
  );
1217
1293
  _scale = (settings.pixelart ? ~~_scale : _scale) || 1;
1218
- _canvas.style.width = instance.WIDTH * _scale + "px";
1219
- _canvas.style.height = instance.HEIGHT * _scale + "px";
1294
+ _canvas.style.width = instance.W * _scale + "px";
1295
+ _canvas.style.height = instance.H * _scale + "px";
1220
1296
  }
1221
1297
  if (!settings.antialias || settings.pixelart) {
1222
1298
  _ctx.imageSmoothingEnabled = false;
1223
1299
  _canvas.style.imageRendering = "pixelated";
1224
1300
  }
1225
1301
  instance.emit("resized", _scale);
1302
+ instance.cls(0);
1226
1303
  if (!settings.animate) {
1227
1304
  raf(drawFrame);
1228
1305
  }
@@ -1234,13 +1311,13 @@
1234
1311
  }
1235
1312
  }
1236
1313
  function loadPlugin(callback, config) {
1237
- const pluginData = callback(instance, _helpers, config);
1314
+ const pluginData = callback(instance, config);
1238
1315
  DEV: assert(
1239
1316
  null == pluginData || "object" === typeof pluginData,
1240
1317
  "Litecanvas plugins should return an object or nothing"
1241
1318
  );
1242
1319
  for (const key in pluginData) {
1243
- instance.setvar(key, pluginData[key]);
1320
+ instance.def(key, pluginData[key]);
1244
1321
  }
1245
1322
  }
1246
1323
  if (settings.global) {