litecanvas 0.77.0 → 0.78.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/dist/dist.dev.js +87 -76
- package/dist/dist.js +73 -57
- package/dist/dist.min.js +1 -1
- package/package.json +2 -2
- package/src/index.js +109 -97
- package/types/index.d.ts +8 -0
- package/types/types.d.ts +8 -0
package/dist/dist.dev.js
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
// src/index.js
|
|
32
32
|
function litecanvas(settings = {}) {
|
|
33
|
-
const root = globalThis, PI =
|
|
33
|
+
const root = globalThis, math = Math, PI = math.PI, TWO_PI = PI * 2, raf = requestAnimationFrame, _browserEventListeners = [], on = (elem, evt, callback) => {
|
|
34
34
|
elem.addEventListener(evt, callback, false);
|
|
35
35
|
_browserEventListeners.push(
|
|
36
36
|
() => elem.removeEventListener(evt, callback, false)
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
animate: true
|
|
51
51
|
};
|
|
52
52
|
settings = Object.assign(defaults, settings);
|
|
53
|
-
let _initialized = false, _plugins = [], _canvas = settings.canvas || document.createElement("canvas"),
|
|
53
|
+
let _initialized = false, _plugins = [], _canvas = settings.canvas || document.createElement("canvas"), _animated = settings.animate, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _deltaTime = 1 / 60, _accumulated = 0, _rafid, _fontFamily = "sans-serif", _fontSize = 18, _rng_seed = Date.now(), _global = settings.global, _events = {
|
|
54
54
|
init: null,
|
|
55
55
|
update: null,
|
|
56
56
|
draw: null,
|
|
@@ -135,6 +135,27 @@
|
|
|
135
135
|
DEV: assert(isFinite(rads), "rad2deg: 1st param must be a number");
|
|
136
136
|
return 180 / PI * rads;
|
|
137
137
|
},
|
|
138
|
+
/**
|
|
139
|
+
* Returns the rounded value of an number to optional precision (number of digits after the decimal point).
|
|
140
|
+
*
|
|
141
|
+
* Note: precision is optional but must be >= 0
|
|
142
|
+
*
|
|
143
|
+
* @param {number} n number to round.
|
|
144
|
+
* @param {number} [precision] number of decimal digits to round to, default is 0.
|
|
145
|
+
* @returns {number} rounded number.
|
|
146
|
+
*/
|
|
147
|
+
round: (n, precision = 0) => {
|
|
148
|
+
DEV: assert(isFinite(n), "round: 1st param must be a number");
|
|
149
|
+
DEV: assert(
|
|
150
|
+
null === precision || isFinite(precision) && precision >= 0,
|
|
151
|
+
"round: 2nd param must be a positive number or zero"
|
|
152
|
+
);
|
|
153
|
+
if (!precision) {
|
|
154
|
+
return math.round(n);
|
|
155
|
+
}
|
|
156
|
+
const multiplier = 10 ** precision;
|
|
157
|
+
return math.round(n * multiplier) / multiplier;
|
|
158
|
+
},
|
|
138
159
|
/**
|
|
139
160
|
* Constrains a number between `min` and `max`.
|
|
140
161
|
*
|
|
@@ -175,7 +196,7 @@
|
|
|
175
196
|
max !== min,
|
|
176
197
|
"randi: the 2nd param must be not equal to the 3rd param"
|
|
177
198
|
);
|
|
178
|
-
return value - (max - min) *
|
|
199
|
+
return value - (max - min) * math.floor((value - min) / (max - min));
|
|
179
200
|
},
|
|
180
201
|
/**
|
|
181
202
|
* Re-maps a number from one range to another.
|
|
@@ -249,7 +270,7 @@
|
|
|
249
270
|
max > min,
|
|
250
271
|
"randi: the 1st param must be less than the 2nd param"
|
|
251
272
|
);
|
|
252
|
-
return
|
|
273
|
+
return math.floor(instance.rand(min, max + 1));
|
|
253
274
|
},
|
|
254
275
|
/**
|
|
255
276
|
* If a value is passed, initializes the random number generator with an explicit seed value.
|
|
@@ -269,12 +290,12 @@
|
|
|
269
290
|
/**
|
|
270
291
|
* Clear the game screen with an optional color
|
|
271
292
|
*
|
|
272
|
-
* @param {number?} color The background color (index) or null (for transparent)
|
|
293
|
+
* @param {number?} color The background color (index) or null/undefined (for transparent)
|
|
273
294
|
*/
|
|
274
295
|
cls(color) {
|
|
275
296
|
DEV: assert(
|
|
276
297
|
null == color || isFinite(color) && color >= 0,
|
|
277
|
-
"cls: 1st param must be a positive number or zero or
|
|
298
|
+
"cls: 1st param must be a positive number or zero or undefined"
|
|
278
299
|
);
|
|
279
300
|
if (null == color) {
|
|
280
301
|
_ctx.clearRect(0, 0, _ctx.canvas.width, _ctx.canvas.height);
|
|
@@ -797,51 +818,6 @@
|
|
|
797
818
|
DEV: assert(isFinite(value), "volume: 1st param must be a number");
|
|
798
819
|
root.zzfxV = value;
|
|
799
820
|
},
|
|
800
|
-
/** UTILS API */
|
|
801
|
-
/**
|
|
802
|
-
* Check a collision between two rectangles
|
|
803
|
-
*
|
|
804
|
-
* @param {number} x1 first rectangle position X
|
|
805
|
-
* @param {number} y1 first rectangle position Y
|
|
806
|
-
* @param {number} w1 first rectangle width
|
|
807
|
-
* @param {number} h1 first rectangle height
|
|
808
|
-
* @param {number} x2 second rectangle position X
|
|
809
|
-
* @param {number} y2 second rectangle position Y
|
|
810
|
-
* @param {number} w2 second rectangle width
|
|
811
|
-
* @param {number} h2 second rectangle height
|
|
812
|
-
* @returns {boolean}
|
|
813
|
-
*/
|
|
814
|
-
colrect: (x1, y1, w1, h1, x2, y2, w2, h2) => {
|
|
815
|
-
DEV: assert(isFinite(x1), "colrect: 1st param must be a number");
|
|
816
|
-
DEV: assert(isFinite(y1), "colrect: 2nd param must be a number");
|
|
817
|
-
DEV: assert(isFinite(w1), "colrect: 3rd param must be a number");
|
|
818
|
-
DEV: assert(isFinite(h1), "colrect: 4th param must be a number");
|
|
819
|
-
DEV: assert(isFinite(x2), "colrect: 5th param must be a number");
|
|
820
|
-
DEV: assert(isFinite(y2), "colrect: 6th param must be a number");
|
|
821
|
-
DEV: assert(isFinite(w2), "colrect: 7th param must be a number");
|
|
822
|
-
DEV: assert(isFinite(h2), "colrect: 8th param must be a number");
|
|
823
|
-
return x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2;
|
|
824
|
-
},
|
|
825
|
-
/**
|
|
826
|
-
* Check a collision between two circles
|
|
827
|
-
*
|
|
828
|
-
* @param {number} x1 first circle position X
|
|
829
|
-
* @param {number} y1 first circle position Y
|
|
830
|
-
* @param {number} r1 first circle position radius
|
|
831
|
-
* @param {number} x2 second circle position X
|
|
832
|
-
* @param {number} y2 second circle position Y
|
|
833
|
-
* @param {number} r2 second circle position radius
|
|
834
|
-
* @returns {boolean}
|
|
835
|
-
*/
|
|
836
|
-
colcirc: (x1, y1, r1, x2, y2, r2) => {
|
|
837
|
-
DEV: assert(isFinite(x1), "colcirc: 1st param must be a number");
|
|
838
|
-
DEV: assert(isFinite(y1), "colcirc: 2nd param must be a number");
|
|
839
|
-
DEV: assert(isFinite(r1), "colcirc: 3rd param must be a number");
|
|
840
|
-
DEV: assert(isFinite(x2), "colcirc: 4th param must be a number");
|
|
841
|
-
DEV: assert(isFinite(y2), "colcirc: 5th param must be a number");
|
|
842
|
-
DEV: assert(isFinite(r2), "colcirc: 6th param must be a number");
|
|
843
|
-
return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) <= (r1 + r2) * (r1 + r2);
|
|
844
|
-
},
|
|
845
821
|
/** PLUGINS API */
|
|
846
822
|
/**
|
|
847
823
|
* Prepares a plugin to be loaded
|
|
@@ -982,22 +958,22 @@
|
|
|
982
958
|
* Stops the litecanvas instance and remove all event listeners.
|
|
983
959
|
*/
|
|
984
960
|
quit() {
|
|
961
|
+
cancelAnimationFrame(_rafid);
|
|
985
962
|
instance.emit("quit");
|
|
963
|
+
_events = [];
|
|
986
964
|
for (const removeListener of _browserEventListeners) {
|
|
987
965
|
removeListener();
|
|
988
966
|
}
|
|
989
|
-
cancelAnimationFrame(_rafid);
|
|
990
|
-
_events = false;
|
|
991
967
|
if (_global) {
|
|
992
968
|
for (const key in instance) {
|
|
993
969
|
delete root[key];
|
|
994
970
|
}
|
|
995
|
-
delete root.
|
|
971
|
+
delete root.ENGINE;
|
|
996
972
|
}
|
|
997
973
|
}
|
|
998
974
|
};
|
|
999
|
-
for (const k of "PI,sin,cos,atan2,hypot,tan,abs,ceil,
|
|
1000
|
-
instance[k] =
|
|
975
|
+
for (const k of "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp".split(",")) {
|
|
976
|
+
instance[k] = math[k];
|
|
1001
977
|
}
|
|
1002
978
|
function init() {
|
|
1003
979
|
_initialized = true;
|
|
@@ -1008,7 +984,7 @@
|
|
|
1008
984
|
for (const [callback, config] of _plugins) {
|
|
1009
985
|
loadPlugin(callback, config);
|
|
1010
986
|
}
|
|
1011
|
-
if (
|
|
987
|
+
if (settings.autoscale) {
|
|
1012
988
|
on(root, "resize", onResize);
|
|
1013
989
|
}
|
|
1014
990
|
if (settings.tapEvents) {
|
|
@@ -1030,7 +1006,7 @@
|
|
|
1030
1006
|
const tap = _taps.get(id) || _registerTap(id);
|
|
1031
1007
|
tap.x = x;
|
|
1032
1008
|
tap.y = y;
|
|
1033
|
-
}, _checkTapped = (tap) => tap && performance.now() - tap.ts <=
|
|
1009
|
+
}, _checkTapped = (tap) => tap && performance.now() - tap.ts <= 300, preventDefault = (ev) => ev.preventDefault();
|
|
1034
1010
|
let _pressingMouse = false;
|
|
1035
1011
|
on(_canvas, "mousedown", (ev) => {
|
|
1036
1012
|
if (ev.button === 0) {
|
|
@@ -1109,23 +1085,59 @@
|
|
|
1109
1085
|
});
|
|
1110
1086
|
}
|
|
1111
1087
|
if (settings.keyboardEvents) {
|
|
1112
|
-
const
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1088
|
+
const toLowerCase = (s) => s.toLowerCase();
|
|
1089
|
+
const _keysDown = /* @__PURE__ */ new Set();
|
|
1090
|
+
const _keysPress = /* @__PURE__ */ new Set();
|
|
1091
|
+
const keyCheck = (keysSet, key) => {
|
|
1092
|
+
return !key ? keysSet.size > 0 : keysSet.has(
|
|
1093
|
+
"space" === toLowerCase(key) ? " " : toLowerCase(key)
|
|
1117
1094
|
);
|
|
1118
|
-
key = key.toLowerCase();
|
|
1119
|
-
return "any" === key ? _keyDownList.size > 0 : _keyDownList.has("space" === key ? " " : key);
|
|
1120
1095
|
};
|
|
1121
|
-
instance.setvar("iskeydown", iskeydown);
|
|
1122
1096
|
on(root, "keydown", (event) => {
|
|
1123
|
-
|
|
1097
|
+
if (!_keysDown.has(toLowerCase(event.key))) {
|
|
1098
|
+
_keysDown.add(toLowerCase(event.key));
|
|
1099
|
+
_keysPress.add(toLowerCase(event.key));
|
|
1100
|
+
}
|
|
1124
1101
|
});
|
|
1125
1102
|
on(root, "keyup", (event) => {
|
|
1126
|
-
|
|
1103
|
+
_keysDown.delete(toLowerCase(event.key));
|
|
1127
1104
|
});
|
|
1128
|
-
on(root, "blur", () =>
|
|
1105
|
+
on(root, "blur", () => _keysDown.clear());
|
|
1106
|
+
instance.listen("after:draw", () => _keysPress.clear());
|
|
1107
|
+
instance.setvar(
|
|
1108
|
+
"iskeydown",
|
|
1109
|
+
/**
|
|
1110
|
+
* Checks if a which key is pressed (down) on the keyboard.
|
|
1111
|
+
* Note: use `iskeydown()` to check for any key.
|
|
1112
|
+
*
|
|
1113
|
+
* @param {string?} key
|
|
1114
|
+
* @returns {boolean}
|
|
1115
|
+
*/
|
|
1116
|
+
(key) => {
|
|
1117
|
+
DEV: assert(
|
|
1118
|
+
null == key || "string" === typeof key,
|
|
1119
|
+
"iskeydown: 1st param must be a string or undefined"
|
|
1120
|
+
);
|
|
1121
|
+
return keyCheck(_keysDown, key);
|
|
1122
|
+
}
|
|
1123
|
+
);
|
|
1124
|
+
instance.setvar(
|
|
1125
|
+
"iskeypressed",
|
|
1126
|
+
/**
|
|
1127
|
+
* Checks if a which key just got pressed on the keyboard.
|
|
1128
|
+
* Note: use `iskeypressed()` to check for any key.
|
|
1129
|
+
*
|
|
1130
|
+
* @param {string?} key
|
|
1131
|
+
* @returns {boolean}
|
|
1132
|
+
*/
|
|
1133
|
+
(key) => {
|
|
1134
|
+
DEV: assert(
|
|
1135
|
+
null == key || "string" === typeof key,
|
|
1136
|
+
"iskeypressed: 1st param must be a string or undefined"
|
|
1137
|
+
);
|
|
1138
|
+
return keyCheck(_keysPress, key);
|
|
1139
|
+
}
|
|
1140
|
+
);
|
|
1129
1141
|
}
|
|
1130
1142
|
if (settings.pauseOnBlur) {
|
|
1131
1143
|
on(root, "blur", () => {
|
|
@@ -1137,7 +1149,6 @@
|
|
|
1137
1149
|
}
|
|
1138
1150
|
});
|
|
1139
1151
|
}
|
|
1140
|
-
instance.setfps(60);
|
|
1141
1152
|
instance.emit("init", instance);
|
|
1142
1153
|
_lastFrameTime = performance.now();
|
|
1143
1154
|
_rafid = raf(drawFrame);
|
|
@@ -1149,7 +1160,7 @@
|
|
|
1149
1160
|
let updated = 0, frameTime = (now - _lastFrameTime) / 1e3;
|
|
1150
1161
|
_lastFrameTime = now;
|
|
1151
1162
|
if (frameTime > _deltaTime * 30) {
|
|
1152
|
-
console.
|
|
1163
|
+
console.warn("skipping too long frame");
|
|
1153
1164
|
} else {
|
|
1154
1165
|
_accumulated += frameTime;
|
|
1155
1166
|
if (!_animated) {
|
|
@@ -1200,12 +1211,12 @@
|
|
|
1200
1211
|
}
|
|
1201
1212
|
function onResize() {
|
|
1202
1213
|
const styles = _canvas.style;
|
|
1203
|
-
if (
|
|
1214
|
+
if (settings.autoscale) {
|
|
1204
1215
|
if (!styles.display) {
|
|
1205
1216
|
styles.display = "block";
|
|
1206
1217
|
styles.margin = "auto";
|
|
1207
1218
|
}
|
|
1208
|
-
_scale =
|
|
1219
|
+
_scale = math.min(
|
|
1209
1220
|
root.innerWidth / instance.WIDTH,
|
|
1210
1221
|
root.innerHeight / instance.HEIGHT
|
|
1211
1222
|
);
|
|
@@ -1239,11 +1250,11 @@
|
|
|
1239
1250
|
}
|
|
1240
1251
|
}
|
|
1241
1252
|
if (_global) {
|
|
1242
|
-
if (root.
|
|
1243
|
-
throw "global litecanvas
|
|
1253
|
+
if (root.ENGINE) {
|
|
1254
|
+
throw "two global litecanvas detected";
|
|
1244
1255
|
}
|
|
1245
1256
|
Object.assign(root, instance);
|
|
1246
|
-
root.
|
|
1257
|
+
root.ENGINE = instance;
|
|
1247
1258
|
}
|
|
1248
1259
|
setupCanvas();
|
|
1249
1260
|
if ("loading" === document.readyState) {
|
package/dist/dist.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
// src/index.js
|
|
27
27
|
function litecanvas(settings = {}) {
|
|
28
|
-
const root = globalThis, PI =
|
|
28
|
+
const root = globalThis, math = Math, PI = math.PI, TWO_PI = PI * 2, raf = requestAnimationFrame, _browserEventListeners = [], on = (elem, evt, callback) => {
|
|
29
29
|
elem.addEventListener(evt, callback, false);
|
|
30
30
|
_browserEventListeners.push(
|
|
31
31
|
() => elem.removeEventListener(evt, callback, false)
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
animate: true
|
|
46
46
|
};
|
|
47
47
|
settings = Object.assign(defaults, settings);
|
|
48
|
-
let _initialized = false, _plugins = [], _canvas = settings.canvas || document.createElement("canvas"),
|
|
48
|
+
let _initialized = false, _plugins = [], _canvas = settings.canvas || document.createElement("canvas"), _animated = settings.animate, _scale = 1, _ctx, _outline_fix = 0.5, _timeScale = 1, _lastFrameTime, _deltaTime = 1 / 60, _accumulated = 0, _rafid, _fontFamily = "sans-serif", _fontSize = 18, _rng_seed = Date.now(), _global = settings.global, _events = {
|
|
49
49
|
init: null,
|
|
50
50
|
update: null,
|
|
51
51
|
draw: null,
|
|
@@ -125,6 +125,22 @@
|
|
|
125
125
|
rad2deg: (rads) => {
|
|
126
126
|
return 180 / PI * rads;
|
|
127
127
|
},
|
|
128
|
+
/**
|
|
129
|
+
* Returns the rounded value of an number to optional precision (number of digits after the decimal point).
|
|
130
|
+
*
|
|
131
|
+
* Note: precision is optional but must be >= 0
|
|
132
|
+
*
|
|
133
|
+
* @param {number} n number to round.
|
|
134
|
+
* @param {number} [precision] number of decimal digits to round to, default is 0.
|
|
135
|
+
* @returns {number} rounded number.
|
|
136
|
+
*/
|
|
137
|
+
round: (n, precision = 0) => {
|
|
138
|
+
if (!precision) {
|
|
139
|
+
return math.round(n);
|
|
140
|
+
}
|
|
141
|
+
const multiplier = 10 ** precision;
|
|
142
|
+
return math.round(n * multiplier) / multiplier;
|
|
143
|
+
},
|
|
128
144
|
/**
|
|
129
145
|
* Constrains a number between `min` and `max`.
|
|
130
146
|
*
|
|
@@ -147,7 +163,7 @@
|
|
|
147
163
|
* @returns {number}
|
|
148
164
|
*/
|
|
149
165
|
wrap: (value, min, max) => {
|
|
150
|
-
return value - (max - min) *
|
|
166
|
+
return value - (max - min) * math.floor((value - min) / (max - min));
|
|
151
167
|
},
|
|
152
168
|
/**
|
|
153
169
|
* Re-maps a number from one range to another.
|
|
@@ -201,7 +217,7 @@
|
|
|
201
217
|
* @returns {number} the random number
|
|
202
218
|
*/
|
|
203
219
|
randi: (min = 0, max = 1) => {
|
|
204
|
-
return
|
|
220
|
+
return math.floor(instance.rand(min, max + 1));
|
|
205
221
|
},
|
|
206
222
|
/**
|
|
207
223
|
* If a value is passed, initializes the random number generator with an explicit seed value.
|
|
@@ -217,7 +233,7 @@
|
|
|
217
233
|
/**
|
|
218
234
|
* Clear the game screen with an optional color
|
|
219
235
|
*
|
|
220
|
-
* @param {number?} color The background color (index) or null (for transparent)
|
|
236
|
+
* @param {number?} color The background color (index) or null/undefined (for transparent)
|
|
221
237
|
*/
|
|
222
238
|
cls(color) {
|
|
223
239
|
if (null == color) {
|
|
@@ -574,37 +590,6 @@
|
|
|
574
590
|
volume(value) {
|
|
575
591
|
root.zzfxV = value;
|
|
576
592
|
},
|
|
577
|
-
/** UTILS API */
|
|
578
|
-
/**
|
|
579
|
-
* Check a collision between two rectangles
|
|
580
|
-
*
|
|
581
|
-
* @param {number} x1 first rectangle position X
|
|
582
|
-
* @param {number} y1 first rectangle position Y
|
|
583
|
-
* @param {number} w1 first rectangle width
|
|
584
|
-
* @param {number} h1 first rectangle height
|
|
585
|
-
* @param {number} x2 second rectangle position X
|
|
586
|
-
* @param {number} y2 second rectangle position Y
|
|
587
|
-
* @param {number} w2 second rectangle width
|
|
588
|
-
* @param {number} h2 second rectangle height
|
|
589
|
-
* @returns {boolean}
|
|
590
|
-
*/
|
|
591
|
-
colrect: (x1, y1, w1, h1, x2, y2, w2, h2) => {
|
|
592
|
-
return x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2;
|
|
593
|
-
},
|
|
594
|
-
/**
|
|
595
|
-
* Check a collision between two circles
|
|
596
|
-
*
|
|
597
|
-
* @param {number} x1 first circle position X
|
|
598
|
-
* @param {number} y1 first circle position Y
|
|
599
|
-
* @param {number} r1 first circle position radius
|
|
600
|
-
* @param {number} x2 second circle position X
|
|
601
|
-
* @param {number} y2 second circle position Y
|
|
602
|
-
* @param {number} r2 second circle position radius
|
|
603
|
-
* @returns {boolean}
|
|
604
|
-
*/
|
|
605
|
-
colcirc: (x1, y1, r1, x2, y2, r2) => {
|
|
606
|
-
return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) <= (r1 + r2) * (r1 + r2);
|
|
607
|
-
},
|
|
608
593
|
/** PLUGINS API */
|
|
609
594
|
/**
|
|
610
595
|
* Prepares a plugin to be loaded
|
|
@@ -700,22 +685,22 @@
|
|
|
700
685
|
* Stops the litecanvas instance and remove all event listeners.
|
|
701
686
|
*/
|
|
702
687
|
quit() {
|
|
688
|
+
cancelAnimationFrame(_rafid);
|
|
703
689
|
instance.emit("quit");
|
|
690
|
+
_events = [];
|
|
704
691
|
for (const removeListener of _browserEventListeners) {
|
|
705
692
|
removeListener();
|
|
706
693
|
}
|
|
707
|
-
cancelAnimationFrame(_rafid);
|
|
708
|
-
_events = false;
|
|
709
694
|
if (_global) {
|
|
710
695
|
for (const key in instance) {
|
|
711
696
|
delete root[key];
|
|
712
697
|
}
|
|
713
|
-
delete root.
|
|
698
|
+
delete root.ENGINE;
|
|
714
699
|
}
|
|
715
700
|
}
|
|
716
701
|
};
|
|
717
|
-
for (const k of "PI,sin,cos,atan2,hypot,tan,abs,ceil,
|
|
718
|
-
instance[k] =
|
|
702
|
+
for (const k of "PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp".split(",")) {
|
|
703
|
+
instance[k] = math[k];
|
|
719
704
|
}
|
|
720
705
|
function init() {
|
|
721
706
|
_initialized = true;
|
|
@@ -726,7 +711,7 @@
|
|
|
726
711
|
for (const [callback, config] of _plugins) {
|
|
727
712
|
loadPlugin(callback, config);
|
|
728
713
|
}
|
|
729
|
-
if (
|
|
714
|
+
if (settings.autoscale) {
|
|
730
715
|
on(root, "resize", onResize);
|
|
731
716
|
}
|
|
732
717
|
if (settings.tapEvents) {
|
|
@@ -748,7 +733,7 @@
|
|
|
748
733
|
const tap = _taps.get(id) || _registerTap(id);
|
|
749
734
|
tap.x = x;
|
|
750
735
|
tap.y = y;
|
|
751
|
-
}, _checkTapped = (tap) => tap && performance.now() - tap.ts <=
|
|
736
|
+
}, _checkTapped = (tap) => tap && performance.now() - tap.ts <= 300, preventDefault = (ev) => ev.preventDefault();
|
|
752
737
|
let _pressingMouse = false;
|
|
753
738
|
on(_canvas, "mousedown", (ev) => {
|
|
754
739
|
if (ev.button === 0) {
|
|
@@ -827,19 +812,51 @@
|
|
|
827
812
|
});
|
|
828
813
|
}
|
|
829
814
|
if (settings.keyboardEvents) {
|
|
830
|
-
const
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
815
|
+
const toLowerCase = (s) => s.toLowerCase();
|
|
816
|
+
const _keysDown = /* @__PURE__ */ new Set();
|
|
817
|
+
const _keysPress = /* @__PURE__ */ new Set();
|
|
818
|
+
const keyCheck = (keysSet, key) => {
|
|
819
|
+
return !key ? keysSet.size > 0 : keysSet.has(
|
|
820
|
+
"space" === toLowerCase(key) ? " " : toLowerCase(key)
|
|
821
|
+
);
|
|
834
822
|
};
|
|
835
|
-
instance.setvar("iskeydown", iskeydown);
|
|
836
823
|
on(root, "keydown", (event) => {
|
|
837
|
-
|
|
824
|
+
if (!_keysDown.has(toLowerCase(event.key))) {
|
|
825
|
+
_keysDown.add(toLowerCase(event.key));
|
|
826
|
+
_keysPress.add(toLowerCase(event.key));
|
|
827
|
+
}
|
|
838
828
|
});
|
|
839
829
|
on(root, "keyup", (event) => {
|
|
840
|
-
|
|
830
|
+
_keysDown.delete(toLowerCase(event.key));
|
|
841
831
|
});
|
|
842
|
-
on(root, "blur", () =>
|
|
832
|
+
on(root, "blur", () => _keysDown.clear());
|
|
833
|
+
instance.listen("after:draw", () => _keysPress.clear());
|
|
834
|
+
instance.setvar(
|
|
835
|
+
"iskeydown",
|
|
836
|
+
/**
|
|
837
|
+
* Checks if a which key is pressed (down) on the keyboard.
|
|
838
|
+
* Note: use `iskeydown()` to check for any key.
|
|
839
|
+
*
|
|
840
|
+
* @param {string?} key
|
|
841
|
+
* @returns {boolean}
|
|
842
|
+
*/
|
|
843
|
+
(key) => {
|
|
844
|
+
return keyCheck(_keysDown, key);
|
|
845
|
+
}
|
|
846
|
+
);
|
|
847
|
+
instance.setvar(
|
|
848
|
+
"iskeypressed",
|
|
849
|
+
/**
|
|
850
|
+
* Checks if a which key just got pressed on the keyboard.
|
|
851
|
+
* Note: use `iskeypressed()` to check for any key.
|
|
852
|
+
*
|
|
853
|
+
* @param {string?} key
|
|
854
|
+
* @returns {boolean}
|
|
855
|
+
*/
|
|
856
|
+
(key) => {
|
|
857
|
+
return keyCheck(_keysPress, key);
|
|
858
|
+
}
|
|
859
|
+
);
|
|
843
860
|
}
|
|
844
861
|
if (settings.pauseOnBlur) {
|
|
845
862
|
on(root, "blur", () => {
|
|
@@ -851,7 +868,6 @@
|
|
|
851
868
|
}
|
|
852
869
|
});
|
|
853
870
|
}
|
|
854
|
-
instance.setfps(60);
|
|
855
871
|
instance.emit("init", instance);
|
|
856
872
|
_lastFrameTime = performance.now();
|
|
857
873
|
_rafid = raf(drawFrame);
|
|
@@ -897,12 +913,12 @@
|
|
|
897
913
|
}
|
|
898
914
|
function onResize() {
|
|
899
915
|
const styles = _canvas.style;
|
|
900
|
-
if (
|
|
916
|
+
if (settings.autoscale) {
|
|
901
917
|
if (!styles.display) {
|
|
902
918
|
styles.display = "block";
|
|
903
919
|
styles.margin = "auto";
|
|
904
920
|
}
|
|
905
|
-
_scale =
|
|
921
|
+
_scale = math.min(
|
|
906
922
|
root.innerWidth / instance.WIDTH,
|
|
907
923
|
root.innerHeight / instance.HEIGHT
|
|
908
924
|
);
|
|
@@ -932,11 +948,11 @@
|
|
|
932
948
|
}
|
|
933
949
|
}
|
|
934
950
|
if (_global) {
|
|
935
|
-
if (root.
|
|
936
|
-
throw "global litecanvas
|
|
951
|
+
if (root.ENGINE) {
|
|
952
|
+
throw "two global litecanvas detected";
|
|
937
953
|
}
|
|
938
954
|
Object.assign(root, instance);
|
|
939
|
-
root.
|
|
955
|
+
root.ENGINE = instance;
|
|
940
956
|
}
|
|
941
957
|
setupCanvas();
|
|
942
958
|
if ("loading" === document.readyState) {
|
package/dist/dist.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{var e=new AudioContext,t=(t=1,a=.05,l=220,n=0,i=0,
|
|
1
|
+
(()=>{var e=new AudioContext,t=(t=1,a=.05,l=220,n=0,i=0,r=.1,o=0,s=1,c=0,f=0,d=0,u=0,p=0,g=0,h=0,m=0,E=0,v=1,b=0,w=0,x=0)=>{let T=Math,y=2*T.PI,H=c*=500*y/44100/44100,I=l*=(1-a+2*a*T.random(a=[]))*y/44100,S=0,D=0,k=0,A=1,C=0,X=0,z=0,N=x<0?-1:1,P=y*N*x*2/44100,L=T.cos(P),O=T.sin,Y=O(P)/4,F=1+Y,W=-2*L/F,G=(1-Y)/F,M=(1+N*L)/2/F,R=-(N+L)/F,B=0,U=0,q=0,V=0;for(n=44100*n+9,b*=44100,i*=44100,r*=44100,E*=44100,f*=500*y/85766121e6,h*=y/44100,d*=y/44100,u*=44100,p=44100*p|0,t*=.3*(globalThis.zzfxV||1),N=n+b+i+r+E|0;k<N;a[k++]=z*t)++X%(100*m|0)||(z=o?1<o?2<o?3<o?O(S*S):T.max(T.min(T.tan(S),1),-1):1-(2*S/y%2+2)%2:1-4*T.abs(T.round(S/y)-S/y):O(S),z=(p?1-w+w*O(y*k/p):1)*(z<0?-1:1)*T.abs(z)**s*(k<n?k/n:k<n+b?1-(k-n)/b*(1-v):k<n+b+i?v:k<N-E?(N-k-E)/r*v:0),z=E?z/2+(E>k?0:(k<N-E?1:(N-k)/E)*a[k-E|0]/2/t):z,x&&(z=V=M*B+R*(B=U)+M*(U=z)-G*q-W*(q=V))),S+=(P=(l+=c+=f)*T.cos(h*D++))+P*g*O(k**5),A&&++A>u&&(l+=d,I+=d,A=0),!p||++C%p||(l=I,c=H,A=A||1);(t=e.createBuffer(1,N,44100)).getChannelData(0).set(a),(l=e.createBufferSource()).buffer=t,l.connect(e.destination),l.start()},a=["#111","#6a7799","#aec2c2","#FFF1E8","#e83b3b","#fabc20","#155fd9","#3cbcfc","#327345","#63c64d","#6c2c1f","#ac7c00"];globalThis.litecanvas=function(e={}){let l=globalThis,n=Math,i=n.PI,r=2*i,o=requestAnimationFrame,s=[],c=(e,t,a)=>{e.addEventListener(t,a,!1),s.push(()=>e.removeEventListener(t,a,!1))};e=Object.assign({width:null,height:null,autoscale:!0,pixelart:!1,antialias:!1,canvas:null,global:!0,loop:null,pauseOnBlur:!0,tapEvents:!0,keyboardEvents:!0,animate:!0},e);let f=!1,d=[],u=e.canvas||document.createElement("canvas"),p=e.animate,g=1,h,m=.5,E=1,v,b=1/60,w=0,x,T="sans-serif",y=18,H=Date.now(),I=e.global,S={init:null,update:null,draw:null,resized:null,tap:null,untap:null,tapping:null,tapped:null},D={settings:Object.assign({},e),colors:a},k={WIDTH:e.width,HEIGHT:e.height||e.width,CANVAS:null,ELAPSED:0,CENTERX:0,CENTERY:0,MOUSEX:-1,MOUSEY:-1,DEFAULT_SFX:[.5,,1675,,.06,.2,1,1.8,,,637,.06],TWO_PI:r,HALF_PI:i/2,lerp:(e,t,a)=>a*(t-e)+e,deg2rad:e=>i/180*e,rad2deg:e=>180/i*e,round:(e,t=0)=>{if(!t)return n.round(e);let a=10**t;return n.round(e*a)/a},clamp:(e,t,a)=>e<t?t:e>a?a:e,wrap:(e,t,a)=>e-(a-t)*n.floor((e-t)/(a-t)),map(e,t,a,l,n,i){let r=(e-t)/(a-t)*(n-l)+l;return i?k.clamp(r,l,n):r},norm:(e,t,a)=>k.map(e,t,a,0,1),rand:(e=0,t=1)=>(H=(1664525*H+0x3c6ef35f)%0x100000000)/0x100000000*(t-e)+e,randi:(e=0,t=1)=>n.floor(k.rand(e,t+1)),seed:e=>null==e?H:H=~~e,cls(e){null==e?h.clearRect(0,0,h.canvas.width,h.canvas.height):k.rectfill(0,0,h.canvas.width,h.canvas.height,e)},rect(e,t,a,l,n,i=null){h.beginPath(),h[i?"roundRect":"rect"](~~e-m,~~t-m,~~a+2*m,~~l+2*m,i),k.stroke(n)},rectfill(e,t,a,l,n,i=null){h.beginPath(),h[i?"roundRect":"rect"](~~e,~~t,~~a,~~l,i),k.fill(n)},circ(e,t,a,l){h.beginPath(),h.arc(~~e,~~t,~~a,0,r),k.stroke(l)},circfill(e,t,a,l){h.beginPath(),h.arc(~~e,~~t,~~a,0,r),k.fill(l)},line(e,t,a,l,n){h.beginPath();let i=.5*(0!==m&&~~e==~~a),r=.5*(0!==m&&~~t==~~l);h.moveTo(~~e+i,~~t+r),h.lineTo(~~a+i,~~l+r),k.stroke(n)},linewidth(e){h.lineWidth=~~e,m=.5*(~~e%2!=0)},linedash(e,t=0){h.setLineDash(e),h.lineDashOffset=t},text(e,t,a,l=3,n="normal"){h.font=`${n} ${y}px ${T}`,h.fillStyle=k.getcolor(l),h.fillText(a,~~e,~~t)},textfont(e){T=e},textsize(e){y=e},textalign(e,t){e&&(h.textAlign=e),t&&(h.textBaseline=t)},image(e,t,a){h.drawImage(a,~~e,~~t)},paint(e,t,a,l={}){let n=l.canvas||new OffscreenCanvas(1,1),i=l.scale||1,r=h;if(n.width=e*i,n.height=t*i,(h=n.getContext("2d")).scale(i,i),a.push){let e=0,t=0;for(let l of(h.imageSmoothingEnabled=!1,a)){for(let a of l)" "!==a&&"."!==a&&k.rectfill(e,t,1,1,parseInt(a,16)),e++;t++,e=0}}else a(h);return h=r,n},ctx:e=>(e&&(h=e),h),push:()=>h.save(),pop:()=>h.restore(),translate:(e,t)=>h.translate(~~e,~~t),scale:(e,t)=>h.scale(e,t||e),rotate:e=>h.rotate(e),alpha(e){h.globalAlpha=k.clamp(e,0,1)},path:e=>new Path2D(e),fill(e,t){h.fillStyle=k.getcolor(e),t?h.fill(t):h.fill()},stroke(e,t){h.strokeStyle=k.getcolor(e),t?h.stroke(t):h.stroke()},clip(e){h.clip(e)},sfx:(e,a=0,n=1)=>!(l.zzfxV<=0)&&(!navigator.userActivation||!!navigator.userActivation.hasBeenActive)&&(e=e||k.DEFAULT_SFX,(0!==a||1!==n)&&((e=e.slice())[0]=n*(e[0]||1),e[10]=~~e[10]+a),t.apply(0,e),e),volume(e){l.zzfxV=e},use(e,t={}){f?N(e,t):d.push([e,t])},listen:(e,t)=>(S[e]=S[e]||new Set,S[e].add(t),()=>S[e].delete(t)),emit(e,t,a,l,n){f&&(z("before:"+e,t,a,l,n),z(e,t,a,l,n),z("after:"+e,t,a,l,n))},getcolor:e=>a[~~e%a.length],setvar(e,t){k[e]=t,I&&(l[e]=t)},resize(e,t){k.setvar("WIDTH",u.width=e),k.setvar("HEIGHT",u.height=t),k.setvar("CENTERX",k.WIDTH/2),k.setvar("CENTERY",k.HEIGHT/2),X()},timescale(e){E=e},setfps(e){b=1/~~e},quit(){for(let e of(cancelAnimationFrame(x),k.emit("quit"),S=[],s))e();if(I){for(let e in k)delete l[e];delete l.ENGINE}}};for(let e of"PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp".split(","))k[e]=n[e];function A(){f=!0;let t=e.loop?e.loop:l;for(let e in S)t[e]&&k.listen(e,t[e]);for(let[e,t]of d)N(e,t);if(e.autoscale&&c(l,"resize",X),e.tapEvents){let e=(e,t)=>[(e-u.offsetLeft)/g,(t-u.offsetTop)/g],t=new Map,a=(e,a,l)=>{let n={x:a,y:l,startX:a,startY:l,ts:performance.now()};return t.set(e,n),n},n=(e,l,n)=>{let i=t.get(e)||a(e);i.x=l,i.y=n},i=e=>e&&performance.now()-e.ts<=300,r=e=>e.preventDefault(),o=!1;c(u,"mousedown",t=>{if(0===t.button){r(t);let[l,n]=e(t.pageX,t.pageY);k.emit("tap",l,n,0),a(0,l,n),o=!0}}),c(u,"mouseup",a=>{if(0===a.button){r(a);let l=t.get(0),[n,s]=e(a.pageX,a.pageY);i(l)&&k.emit("tapped",l.startX,l.startY,0),k.emit("untap",n,s,0),t.delete(0),o=!1}}),c(u,"mousemove",t=>{r(t);let[a,l]=e(t.pageX,t.pageY);k.setvar("MOUSEX",a),k.setvar("MOUSEY",l),o&&(k.emit("tapping",a,l,0),n(0,a,l))}),c(u,"touchstart",t=>{for(let l of(r(t),t.changedTouches)){let[t,n]=e(l.pageX,l.pageY);k.emit("tap",t,n,l.identifier+1),a(l.identifier+1,t,n)}}),c(u,"touchmove",t=>{for(let a of(r(t),t.changedTouches)){let[t,l]=e(a.pageX,a.pageY);k.emit("tapping",t,l,a.identifier+1),n(a.identifier+1,t,l)}});let s=e=>{r(e);let a=[];if(e.targetTouches.length>0)for(let t of e.targetTouches)a.push(t.identifier+1);for(let[e,l]of t)a.includes(e)||(i(l)&&k.emit("tapped",l.startX,l.startY,e),k.emit("untap",l.x,l.y,e),t.delete(e))};c(u,"touchend",s),c(u,"touchcancel",s),c(l,"blur",()=>{for(let[e,a]of(o=!1,t))k.emit("untap",a.x,a.y,e),t.delete(e)})}if(e.keyboardEvents){let e=e=>e.toLowerCase(),t=new Set,a=new Set,n=(t,a)=>a?t.has("space"===e(a)?" ":e(a)):t.size>0;c(l,"keydown",l=>{t.has(e(l.key))||(t.add(e(l.key)),a.add(e(l.key)))}),c(l,"keyup",a=>{t.delete(e(a.key))}),c(l,"blur",()=>t.clear()),k.listen("after:draw",()=>a.clear()),k.setvar("iskeydown",e=>n(t,e)),k.setvar("iskeypressed",e=>n(a,e))}e.pauseOnBlur&&(c(l,"blur",()=>{x=cancelAnimationFrame(x)}),c(l,"focus",()=>{x||(x=o(C))})),k.emit("init",k),v=performance.now(),x=o(C)}function C(e){p&&(x=o(C));let t=0,a=(e-v)/1e3;if(v=e,a>30*b);else for(w+=a,p||(w=b);w>=b;w-=b)k.emit("update",b*E),k.setvar("ELAPSED",k.ELAPSED+b*E),t++;(t||!p)&&(k.textalign("start","top"),k.emit("draw"))}function X(){let t=u.style;e.autoscale&&(t.display||(t.display="block",t.margin="auto"),g=n.min(l.innerWidth/k.WIDTH,l.innerHeight/k.HEIGHT),g=(e.pixelart?~~g:g)||1,t.width=k.WIDTH*g+"px",t.height=k.HEIGHT*g+"px"),(!e.antialias||e.pixelart)&&(h.imageSmoothingEnabled=!1,t.imageRendering="pixelated"),k.emit("resized",g),p||o(C)}function z(e,t,a,l,n){if(S[e])for(let i of S[e])i(t,a,l,n)}function N(e,t){let a=e(k,D,t);for(let e in a)k.setvar(e,a[e])}if(I){if(l.ENGINE)throw"two global litecanvas detected";Object.assign(l,k),l.ENGINE=k}return u="string"==typeof u?document.querySelector(u):u,k.setvar("CANVAS",u),h=u.getContext("2d"),c(u,"click",()=>l.focus()),u.style="",k.WIDTH||(k.WIDTH=l.innerWidth,k.HEIGHT=l.innerHeight),k.resize(k.WIDTH,k.HEIGHT,!1),u.parentNode||document.body.appendChild(u),"loading"===document.readyState?c(l,"DOMContentLoaded",()=>o(A)):o(A),k}})();
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "litecanvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.78.1",
|
|
4
4
|
"description": "Lightweight HTML5 canvas game engine suitable for small projects and creative coding. Inspired by PICO-8 and P5/Processing.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Luiz Bills <luizbills@pm.me>",
|
|
7
7
|
"contributors": [],
|
|
8
8
|
"devDependencies": {
|
|
9
|
-
"@swc/core": "^1.11.
|
|
9
|
+
"@swc/core": "^1.11.9",
|
|
10
10
|
"ava": "^6.2.0",
|
|
11
11
|
"esbuild": "^0.25.1",
|
|
12
12
|
"gzip-size": "^7.0.0",
|
package/src/index.js
CHANGED
|
@@ -11,7 +11,8 @@ import './types.js'
|
|
|
11
11
|
*/
|
|
12
12
|
export default function litecanvas(settings = {}) {
|
|
13
13
|
const root = globalThis,
|
|
14
|
-
|
|
14
|
+
math = Math,
|
|
15
|
+
PI = math.PI,
|
|
15
16
|
TWO_PI = PI * 2,
|
|
16
17
|
raf = requestAnimationFrame,
|
|
17
18
|
/** @type {Function[]} */
|
|
@@ -50,8 +51,6 @@ export default function litecanvas(settings = {}) {
|
|
|
50
51
|
/** @type {HTMLCanvasElement|string} _canvas */
|
|
51
52
|
_canvas = settings.canvas || document.createElement('canvas'),
|
|
52
53
|
/** @type {boolean} */
|
|
53
|
-
_autoscale = settings.autoscale,
|
|
54
|
-
/** @type {boolean} */
|
|
55
54
|
_animated = settings.animate,
|
|
56
55
|
/** @type {number} */
|
|
57
56
|
_scale = 1,
|
|
@@ -64,7 +63,7 @@ export default function litecanvas(settings = {}) {
|
|
|
64
63
|
/** @type {number} */
|
|
65
64
|
_lastFrameTime,
|
|
66
65
|
/** @type {number} */
|
|
67
|
-
_deltaTime,
|
|
66
|
+
_deltaTime = 1 / 60,
|
|
68
67
|
/** @type {number} */
|
|
69
68
|
_accumulated = 0,
|
|
70
69
|
/** @type {number} */
|
|
@@ -72,7 +71,7 @@ export default function litecanvas(settings = {}) {
|
|
|
72
71
|
/** @type {string} */
|
|
73
72
|
_fontFamily = 'sans-serif',
|
|
74
73
|
/** @type {number} */
|
|
75
|
-
_fontSize =
|
|
74
|
+
_fontSize = 18,
|
|
76
75
|
/** @type {number} */
|
|
77
76
|
_rng_seed = Date.now(),
|
|
78
77
|
/** @type {boolean} */
|
|
@@ -191,6 +190,28 @@ export default function litecanvas(settings = {}) {
|
|
|
191
190
|
return (180 / PI) * rads
|
|
192
191
|
},
|
|
193
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Returns the rounded value of an number to optional precision (number of digits after the decimal point).
|
|
195
|
+
*
|
|
196
|
+
* Note: precision is optional but must be >= 0
|
|
197
|
+
*
|
|
198
|
+
* @param {number} n number to round.
|
|
199
|
+
* @param {number} [precision] number of decimal digits to round to, default is 0.
|
|
200
|
+
* @returns {number} rounded number.
|
|
201
|
+
*/
|
|
202
|
+
round: (n, precision = 0) => {
|
|
203
|
+
DEV: assert(isFinite(n), 'round: 1st param must be a number')
|
|
204
|
+
DEV: assert(
|
|
205
|
+
null === precision || (isFinite(precision) && precision >= 0),
|
|
206
|
+
'round: 2nd param must be a positive number or zero'
|
|
207
|
+
)
|
|
208
|
+
if (!precision) {
|
|
209
|
+
return math.round(n)
|
|
210
|
+
}
|
|
211
|
+
const multiplier = 10 ** precision
|
|
212
|
+
return math.round(n * multiplier) / multiplier
|
|
213
|
+
},
|
|
214
|
+
|
|
194
215
|
/**
|
|
195
216
|
* Constrains a number between `min` and `max`.
|
|
196
217
|
*
|
|
@@ -234,7 +255,7 @@ export default function litecanvas(settings = {}) {
|
|
|
234
255
|
'randi: the 2nd param must be not equal to the 3rd param'
|
|
235
256
|
)
|
|
236
257
|
|
|
237
|
-
return value - (max - min) *
|
|
258
|
+
return value - (max - min) * math.floor((value - min) / (max - min))
|
|
238
259
|
},
|
|
239
260
|
|
|
240
261
|
/**
|
|
@@ -319,7 +340,7 @@ export default function litecanvas(settings = {}) {
|
|
|
319
340
|
'randi: the 1st param must be less than the 2nd param'
|
|
320
341
|
)
|
|
321
342
|
|
|
322
|
-
return
|
|
343
|
+
return math.floor(instance.rand(min, max + 1))
|
|
323
344
|
},
|
|
324
345
|
|
|
325
346
|
/**
|
|
@@ -342,12 +363,12 @@ export default function litecanvas(settings = {}) {
|
|
|
342
363
|
/**
|
|
343
364
|
* Clear the game screen with an optional color
|
|
344
365
|
*
|
|
345
|
-
* @param {number?} color The background color (index) or null (for transparent)
|
|
366
|
+
* @param {number?} color The background color (index) or null/undefined (for transparent)
|
|
346
367
|
*/
|
|
347
368
|
cls(color) {
|
|
348
369
|
DEV: assert(
|
|
349
370
|
null == color || (isFinite(color) && color >= 0),
|
|
350
|
-
'cls: 1st param must be a positive number or zero or
|
|
371
|
+
'cls: 1st param must be a positive number or zero or undefined'
|
|
351
372
|
)
|
|
352
373
|
|
|
353
374
|
if (null == color) {
|
|
@@ -955,58 +976,6 @@ export default function litecanvas(settings = {}) {
|
|
|
955
976
|
root.zzfxV = value
|
|
956
977
|
},
|
|
957
978
|
|
|
958
|
-
/** UTILS API */
|
|
959
|
-
/**
|
|
960
|
-
* Check a collision between two rectangles
|
|
961
|
-
*
|
|
962
|
-
* @param {number} x1 first rectangle position X
|
|
963
|
-
* @param {number} y1 first rectangle position Y
|
|
964
|
-
* @param {number} w1 first rectangle width
|
|
965
|
-
* @param {number} h1 first rectangle height
|
|
966
|
-
* @param {number} x2 second rectangle position X
|
|
967
|
-
* @param {number} y2 second rectangle position Y
|
|
968
|
-
* @param {number} w2 second rectangle width
|
|
969
|
-
* @param {number} h2 second rectangle height
|
|
970
|
-
* @returns {boolean}
|
|
971
|
-
*/
|
|
972
|
-
colrect: (x1, y1, w1, h1, x2, y2, w2, h2) => {
|
|
973
|
-
DEV: assert(isFinite(x1), 'colrect: 1st param must be a number')
|
|
974
|
-
DEV: assert(isFinite(y1), 'colrect: 2nd param must be a number')
|
|
975
|
-
DEV: assert(isFinite(w1), 'colrect: 3rd param must be a number')
|
|
976
|
-
DEV: assert(isFinite(h1), 'colrect: 4th param must be a number')
|
|
977
|
-
DEV: assert(isFinite(x2), 'colrect: 5th param must be a number')
|
|
978
|
-
DEV: assert(isFinite(y2), 'colrect: 6th param must be a number')
|
|
979
|
-
DEV: assert(isFinite(w2), 'colrect: 7th param must be a number')
|
|
980
|
-
DEV: assert(isFinite(h2), 'colrect: 8th param must be a number')
|
|
981
|
-
|
|
982
|
-
return x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2
|
|
983
|
-
},
|
|
984
|
-
|
|
985
|
-
/**
|
|
986
|
-
* Check a collision between two circles
|
|
987
|
-
*
|
|
988
|
-
* @param {number} x1 first circle position X
|
|
989
|
-
* @param {number} y1 first circle position Y
|
|
990
|
-
* @param {number} r1 first circle position radius
|
|
991
|
-
* @param {number} x2 second circle position X
|
|
992
|
-
* @param {number} y2 second circle position Y
|
|
993
|
-
* @param {number} r2 second circle position radius
|
|
994
|
-
* @returns {boolean}
|
|
995
|
-
*/
|
|
996
|
-
colcirc: (x1, y1, r1, x2, y2, r2) => {
|
|
997
|
-
DEV: assert(isFinite(x1), 'colcirc: 1st param must be a number')
|
|
998
|
-
DEV: assert(isFinite(y1), 'colcirc: 2nd param must be a number')
|
|
999
|
-
DEV: assert(isFinite(r1), 'colcirc: 3rd param must be a number')
|
|
1000
|
-
DEV: assert(isFinite(x2), 'colcirc: 4th param must be a number')
|
|
1001
|
-
DEV: assert(isFinite(y2), 'colcirc: 5th param must be a number')
|
|
1002
|
-
DEV: assert(isFinite(r2), 'colcirc: 6th param must be a number')
|
|
1003
|
-
|
|
1004
|
-
return (
|
|
1005
|
-
(x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) <=
|
|
1006
|
-
(r1 + r2) * (r1 + r2)
|
|
1007
|
-
)
|
|
1008
|
-
},
|
|
1009
|
-
|
|
1010
979
|
/** PLUGINS API */
|
|
1011
980
|
/**
|
|
1012
981
|
* Prepares a plugin to be loaded
|
|
@@ -1168,25 +1137,34 @@ export default function litecanvas(settings = {}) {
|
|
|
1168
1137
|
* Stops the litecanvas instance and remove all event listeners.
|
|
1169
1138
|
*/
|
|
1170
1139
|
quit() {
|
|
1140
|
+
// stop the renderer
|
|
1141
|
+
cancelAnimationFrame(_rafid)
|
|
1142
|
+
|
|
1143
|
+
// emit "quit" event to manual clean ups
|
|
1171
1144
|
instance.emit('quit')
|
|
1145
|
+
|
|
1146
|
+
// clear all engine events
|
|
1147
|
+
_events = []
|
|
1148
|
+
|
|
1149
|
+
// clear all browser events
|
|
1172
1150
|
for (const removeListener of _browserEventListeners) {
|
|
1173
1151
|
removeListener()
|
|
1174
1152
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1153
|
+
|
|
1154
|
+
// maybe clear global context
|
|
1177
1155
|
if (_global) {
|
|
1178
1156
|
for (const key in instance) {
|
|
1179
1157
|
delete root[key]
|
|
1180
1158
|
}
|
|
1181
|
-
delete root.
|
|
1159
|
+
delete root.ENGINE
|
|
1182
1160
|
}
|
|
1183
1161
|
},
|
|
1184
1162
|
}
|
|
1185
1163
|
|
|
1186
1164
|
// prettier-ignore
|
|
1187
|
-
for (const k of 'PI,sin,cos,atan2,hypot,tan,abs,ceil,
|
|
1165
|
+
for (const k of 'PI,sin,cos,atan2,hypot,tan,abs,ceil,floor,trunc,min,max,pow,sqrt,sign,exp'.split(',')) {
|
|
1188
1166
|
// import native Math functions
|
|
1189
|
-
instance[k] =
|
|
1167
|
+
instance[k] = math[k]
|
|
1190
1168
|
}
|
|
1191
1169
|
|
|
1192
1170
|
function init() {
|
|
@@ -1204,7 +1182,7 @@ export default function litecanvas(settings = {}) {
|
|
|
1204
1182
|
}
|
|
1205
1183
|
|
|
1206
1184
|
// listen window resize event when "autoscale" is enabled
|
|
1207
|
-
if (
|
|
1185
|
+
if (settings.autoscale) {
|
|
1208
1186
|
on(root, 'resize', onResize)
|
|
1209
1187
|
}
|
|
1210
1188
|
|
|
@@ -1233,7 +1211,7 @@ export default function litecanvas(settings = {}) {
|
|
|
1233
1211
|
tap.y = y
|
|
1234
1212
|
},
|
|
1235
1213
|
_checkTapped = (tap) =>
|
|
1236
|
-
tap && performance.now() - tap.ts <=
|
|
1214
|
+
tap && performance.now() - tap.ts <= 300,
|
|
1237
1215
|
preventDefault = (ev) => ev.preventDefault()
|
|
1238
1216
|
|
|
1239
1217
|
let _pressingMouse = false
|
|
@@ -1330,40 +1308,76 @@ export default function litecanvas(settings = {}) {
|
|
|
1330
1308
|
}
|
|
1331
1309
|
|
|
1332
1310
|
if (settings.keyboardEvents) {
|
|
1311
|
+
const toLowerCase = (s) => s.toLowerCase()
|
|
1312
|
+
|
|
1333
1313
|
/** @type {Set<string>} */
|
|
1334
|
-
const
|
|
1314
|
+
const _keysDown = new Set()
|
|
1315
|
+
|
|
1316
|
+
/** @type {Set<string>} */
|
|
1317
|
+
const _keysPress = new Set()
|
|
1335
1318
|
|
|
1336
1319
|
/**
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1340
|
-
* @param {string} key
|
|
1320
|
+
* @param {Set<string>} keysSet
|
|
1321
|
+
* @param {string?} key
|
|
1341
1322
|
* @returns {boolean}
|
|
1342
1323
|
*/
|
|
1343
|
-
const
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
key = key.toLowerCase()
|
|
1350
|
-
|
|
1351
|
-
return 'any' === key
|
|
1352
|
-
? _keyDownList.size > 0
|
|
1353
|
-
: _keyDownList.has('space' === key ? ' ' : key)
|
|
1324
|
+
const keyCheck = (keysSet, key) => {
|
|
1325
|
+
return !key
|
|
1326
|
+
? keysSet.size > 0
|
|
1327
|
+
: keysSet.has(
|
|
1328
|
+
'space' === toLowerCase(key) ? ' ' : toLowerCase(key)
|
|
1329
|
+
)
|
|
1354
1330
|
}
|
|
1355
1331
|
|
|
1356
|
-
instance.setvar('iskeydown', iskeydown)
|
|
1357
|
-
|
|
1358
1332
|
on(root, 'keydown', (/** @type {KeyboardEvent} */ event) => {
|
|
1359
|
-
|
|
1333
|
+
if (!_keysDown.has(toLowerCase(event.key))) {
|
|
1334
|
+
_keysDown.add(toLowerCase(event.key))
|
|
1335
|
+
_keysPress.add(toLowerCase(event.key))
|
|
1336
|
+
}
|
|
1360
1337
|
})
|
|
1361
1338
|
|
|
1362
1339
|
on(root, 'keyup', (/** @type {KeyboardEvent} */ event) => {
|
|
1363
|
-
|
|
1340
|
+
_keysDown.delete(toLowerCase(event.key))
|
|
1364
1341
|
})
|
|
1365
1342
|
|
|
1366
|
-
on(root, 'blur', () =>
|
|
1343
|
+
on(root, 'blur', () => _keysDown.clear())
|
|
1344
|
+
instance.listen('after:draw', () => _keysPress.clear())
|
|
1345
|
+
|
|
1346
|
+
instance.setvar(
|
|
1347
|
+
'iskeydown',
|
|
1348
|
+
/**
|
|
1349
|
+
* Checks if a which key is pressed (down) on the keyboard.
|
|
1350
|
+
* Note: use `iskeydown()` to check for any key.
|
|
1351
|
+
*
|
|
1352
|
+
* @param {string?} key
|
|
1353
|
+
* @returns {boolean}
|
|
1354
|
+
*/
|
|
1355
|
+
(key) => {
|
|
1356
|
+
DEV: assert(
|
|
1357
|
+
null == key || 'string' === typeof key,
|
|
1358
|
+
'iskeydown: 1st param must be a string or undefined'
|
|
1359
|
+
)
|
|
1360
|
+
return keyCheck(_keysDown, key)
|
|
1361
|
+
}
|
|
1362
|
+
)
|
|
1363
|
+
|
|
1364
|
+
instance.setvar(
|
|
1365
|
+
'iskeypressed',
|
|
1366
|
+
/**
|
|
1367
|
+
* Checks if a which key just got pressed on the keyboard.
|
|
1368
|
+
* Note: use `iskeypressed()` to check for any key.
|
|
1369
|
+
*
|
|
1370
|
+
* @param {string?} key
|
|
1371
|
+
* @returns {boolean}
|
|
1372
|
+
*/
|
|
1373
|
+
(key) => {
|
|
1374
|
+
DEV: assert(
|
|
1375
|
+
null == key || 'string' === typeof key,
|
|
1376
|
+
'iskeypressed: 1st param must be a string or undefined'
|
|
1377
|
+
)
|
|
1378
|
+
return keyCheck(_keysPress, key)
|
|
1379
|
+
}
|
|
1380
|
+
)
|
|
1367
1381
|
}
|
|
1368
1382
|
|
|
1369
1383
|
// listen browser focus/blur events and pause the update/draw loop
|
|
@@ -1379,8 +1393,6 @@ export default function litecanvas(settings = {}) {
|
|
|
1379
1393
|
})
|
|
1380
1394
|
}
|
|
1381
1395
|
|
|
1382
|
-
instance.setfps(60)
|
|
1383
|
-
|
|
1384
1396
|
// start the game loop
|
|
1385
1397
|
instance.emit('init', instance)
|
|
1386
1398
|
|
|
@@ -1402,7 +1414,7 @@ export default function litecanvas(settings = {}) {
|
|
|
1402
1414
|
_lastFrameTime = now
|
|
1403
1415
|
|
|
1404
1416
|
if (frameTime > _deltaTime * 30) {
|
|
1405
|
-
console.
|
|
1417
|
+
console.warn('skipping too long frame')
|
|
1406
1418
|
} else {
|
|
1407
1419
|
_accumulated += frameTime
|
|
1408
1420
|
|
|
@@ -1472,13 +1484,13 @@ export default function litecanvas(settings = {}) {
|
|
|
1472
1484
|
function onResize() {
|
|
1473
1485
|
const styles = _canvas.style
|
|
1474
1486
|
|
|
1475
|
-
if (
|
|
1487
|
+
if (settings.autoscale) {
|
|
1476
1488
|
if (!styles.display) {
|
|
1477
1489
|
styles.display = 'block'
|
|
1478
1490
|
styles.margin = 'auto'
|
|
1479
1491
|
}
|
|
1480
1492
|
|
|
1481
|
-
_scale =
|
|
1493
|
+
_scale = math.min(
|
|
1482
1494
|
root.innerWidth / instance.WIDTH,
|
|
1483
1495
|
root.innerHeight / instance.HEIGHT
|
|
1484
1496
|
)
|
|
@@ -1526,11 +1538,11 @@ export default function litecanvas(settings = {}) {
|
|
|
1526
1538
|
}
|
|
1527
1539
|
|
|
1528
1540
|
if (_global) {
|
|
1529
|
-
if (root.
|
|
1530
|
-
throw 'global litecanvas
|
|
1541
|
+
if (root.ENGINE) {
|
|
1542
|
+
throw 'two global litecanvas detected'
|
|
1531
1543
|
}
|
|
1532
1544
|
Object.assign(root, instance)
|
|
1533
|
-
root.
|
|
1545
|
+
root.ENGINE = instance
|
|
1534
1546
|
}
|
|
1535
1547
|
|
|
1536
1548
|
setupCanvas()
|
package/types/index.d.ts
CHANGED
|
@@ -72,6 +72,14 @@ declare global {
|
|
|
72
72
|
* @returns the value in degrees
|
|
73
73
|
*/
|
|
74
74
|
function rad2deg(rads: number): number
|
|
75
|
+
/**
|
|
76
|
+
* Returns the rounded value of an number to optional precision (number of digits after the decimal point).
|
|
77
|
+
*
|
|
78
|
+
* @param n number to round.
|
|
79
|
+
* @param [precision] number of decimal digits to round to, default is 0.
|
|
80
|
+
* @returns the rounded number.
|
|
81
|
+
*/
|
|
82
|
+
function round(n: number, precision?: number): number
|
|
75
83
|
/**
|
|
76
84
|
* Constrains a number between `min` and `max`.
|
|
77
85
|
*
|
package/types/types.d.ts
CHANGED
|
@@ -61,6 +61,14 @@ type LitecanvasInstance = {
|
|
|
61
61
|
* @returns the value in degrees
|
|
62
62
|
*/
|
|
63
63
|
rad2deg(rads: number): number
|
|
64
|
+
/**
|
|
65
|
+
* Returns the rounded value of an number to optional precision (number of digits after the decimal point).
|
|
66
|
+
*
|
|
67
|
+
* @param n number to round.
|
|
68
|
+
* @param [precision] number of decimal digits to round to, default is 0.
|
|
69
|
+
* @returns the rounded number.
|
|
70
|
+
*/
|
|
71
|
+
round(n: number, precision?: number): number
|
|
64
72
|
/**
|
|
65
73
|
* Constrains a number between `min` and `max`.
|
|
66
74
|
*
|