littlejsengine 1.18.7 → 1.18.8
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/littlejs.d.ts +9 -5
- package/dist/littlejs.esm.js +72 -51
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +71 -50
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +65 -44
- package/package.json +1 -1
- package/plugins/tweenSystem.js +1 -1
- package/plugins/uiSystem.js +36 -14
- package/src/engine.js +1 -1
- package/src/engineAudio.js +4 -7
- package/src/engineDebug.js +6 -6
- package/src/engineDraw.js +5 -5
- package/src/engineExport.js +1 -1
- package/src/engineInput.js +7 -7
- package/src/engineMath.js +5 -3
- package/src/engineUtilities.js +6 -6
- package/plugins/tween.js +0 -509
package/dist/littlejs.release.js
CHANGED
|
@@ -35,7 +35,7 @@ const engineName = 'LittleJS';
|
|
|
35
35
|
* @type {string}
|
|
36
36
|
* @default
|
|
37
37
|
* @memberof Engine */
|
|
38
|
-
const engineVersion = '1.18.
|
|
38
|
+
const engineVersion = '1.18.8';
|
|
39
39
|
|
|
40
40
|
/** Frames per second to update
|
|
41
41
|
* @type {number}
|
|
@@ -900,13 +900,15 @@ function oscillate(frequency=1, amplitude=1, t=time, offset=0, type=0)
|
|
|
900
900
|
function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
901
901
|
|
|
902
902
|
/**
|
|
903
|
-
* Check if
|
|
903
|
+
* Check if a value is stringifiable — i.e. it has a toString that returns
|
|
904
|
+
* a string. Use this for ASSERTs and inputs that will be coerced to text;
|
|
905
|
+
* use `typeof x === 'string'` inline if you need strict-string semantics.
|
|
904
906
|
* - Returns true for strings, numbers, and most objects
|
|
905
907
|
* - Returns false for null and undefined
|
|
906
908
|
* @param {any} s
|
|
907
909
|
* @return {boolean}
|
|
908
910
|
* @memberof Math */
|
|
909
|
-
function
|
|
911
|
+
function isStringLike(s) { return s != null && typeof s?.toString() === 'string'; }
|
|
910
912
|
|
|
911
913
|
/**
|
|
912
914
|
* Check if object is an array
|
|
@@ -1650,7 +1652,7 @@ class Color
|
|
|
1650
1652
|
* @return {Color} */
|
|
1651
1653
|
setHex(hex)
|
|
1652
1654
|
{
|
|
1653
|
-
ASSERT(
|
|
1655
|
+
ASSERT(isStringLike(hex), 'Color hex code must be a string');
|
|
1654
1656
|
ASSERT(hex[0] === '#', 'Color hex code must start with #');
|
|
1655
1657
|
ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
|
|
1656
1658
|
|
|
@@ -1914,8 +1916,8 @@ function saveCanvas(canvas, filename='screenshot', type='image/png')
|
|
|
1914
1916
|
* @memberof Utilities */
|
|
1915
1917
|
function saveDataURL(url, filename='download', revokeTime)
|
|
1916
1918
|
{
|
|
1917
|
-
ASSERT(
|
|
1918
|
-
ASSERT(
|
|
1919
|
+
ASSERT(isStringLike(url), 'saveDataURL requires url string');
|
|
1920
|
+
ASSERT(isStringLike(filename), 'saveDataURL requires filename string');
|
|
1919
1921
|
|
|
1920
1922
|
// create link for saving screenshots
|
|
1921
1923
|
const link = document.createElement('a');
|
|
@@ -1933,8 +1935,8 @@ function saveDataURL(url, filename='download', revokeTime)
|
|
|
1933
1935
|
* @memberof Utilities */
|
|
1934
1936
|
function shareURL(title, url, callback)
|
|
1935
1937
|
{
|
|
1936
|
-
ASSERT(
|
|
1937
|
-
ASSERT(
|
|
1938
|
+
ASSERT(isStringLike(title), 'shareURL requires title string');
|
|
1939
|
+
ASSERT(isStringLike(url), 'shareURL requires url string');
|
|
1938
1940
|
navigator.share?.({title, url}).then(()=>callback?.());
|
|
1939
1941
|
}
|
|
1940
1942
|
|
|
@@ -1947,7 +1949,7 @@ function shareURL(title, url, callback)
|
|
|
1947
1949
|
* @memberof Utilities */
|
|
1948
1950
|
function readSaveData(saveName, defaultSaveData)
|
|
1949
1951
|
{
|
|
1950
|
-
ASSERT(
|
|
1952
|
+
ASSERT(isStringLike(saveName), 'loadData requires saveName string');
|
|
1951
1953
|
|
|
1952
1954
|
// replace undefined values with defaults; tolerate corrupt JSON
|
|
1953
1955
|
const data = localStorage[saveName];
|
|
@@ -1966,7 +1968,7 @@ function readSaveData(saveName, defaultSaveData)
|
|
|
1966
1968
|
* @memberof Utilities */
|
|
1967
1969
|
function writeSaveData(saveName, saveData)
|
|
1968
1970
|
{
|
|
1969
|
-
ASSERT(
|
|
1971
|
+
ASSERT(isStringLike(saveName), 'saveData requires saveName string');
|
|
1970
1972
|
localStorage[saveName] = JSON.stringify(saveData);
|
|
1971
1973
|
}
|
|
1972
1974
|
/**
|
|
@@ -3912,15 +3914,15 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
3912
3914
|
* @memberof Draw */
|
|
3913
3915
|
function drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
|
|
3914
3916
|
{
|
|
3915
|
-
ASSERT(
|
|
3917
|
+
ASSERT(isStringLike(text), 'text must be a string');
|
|
3916
3918
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
3917
3919
|
ASSERT(isNumber(size), 'size must be a number');
|
|
3918
3920
|
ASSERT(isColor(color), 'color must be a color');
|
|
3919
3921
|
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
3920
3922
|
ASSERT(isColor(lineColor), 'lineColor must be a color');
|
|
3921
3923
|
ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
|
|
3922
|
-
ASSERT(
|
|
3923
|
-
ASSERT(
|
|
3924
|
+
ASSERT(isStringLike(font), 'font must be a string');
|
|
3925
|
+
ASSERT(isStringLike(fontStyle), 'fontStyle must be a string');
|
|
3924
3926
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
3925
3927
|
|
|
3926
3928
|
context.fillStyle = color.toString();
|
|
@@ -3957,7 +3959,7 @@ async function loadTexture(textureIndex, src)
|
|
|
3957
3959
|
{
|
|
3958
3960
|
ASSERT(isNumber(textureIndex), 'textureIndex must be a number');
|
|
3959
3961
|
ASSERT(!textureInfos[textureIndex], 'textureIndex is already loaded!');
|
|
3960
|
-
ASSERT(!src ||
|
|
3962
|
+
ASSERT(!src || isStringLike(src), 'image src must be a string');
|
|
3961
3963
|
|
|
3962
3964
|
const image = new Image;
|
|
3963
3965
|
if (src)
|
|
@@ -4345,7 +4347,7 @@ class FontImage
|
|
|
4345
4347
|
*/
|
|
4346
4348
|
drawTextScreen(text, pos, size, center=true, color=WHITE, useWebGL=glEnable, context)
|
|
4347
4349
|
{
|
|
4348
|
-
ASSERT(
|
|
4350
|
+
ASSERT(isStringLike(text), 'text must be a string');
|
|
4349
4351
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
4350
4352
|
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
4351
4353
|
ASSERT(isColor(color), 'color must be a color');
|
|
@@ -4506,7 +4508,7 @@ function inputClear()
|
|
|
4506
4508
|
* @memberof Input */
|
|
4507
4509
|
function keyIsDown(key, device=0)
|
|
4508
4510
|
{
|
|
4509
|
-
ASSERT(
|
|
4511
|
+
ASSERT(isStringLike(key), 'key must be a number or string');
|
|
4510
4512
|
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
4511
4513
|
return !!(inputData[device]?.[key] & 1);
|
|
4512
4514
|
}
|
|
@@ -4518,7 +4520,7 @@ function keyIsDown(key, device=0)
|
|
|
4518
4520
|
* @memberof Input */
|
|
4519
4521
|
function keyWasPressed(key, device=0)
|
|
4520
4522
|
{
|
|
4521
|
-
ASSERT(
|
|
4523
|
+
ASSERT(isStringLike(key), 'key must be a number or string');
|
|
4522
4524
|
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
4523
4525
|
return !!(inputData[device]?.[key] & 2);
|
|
4524
4526
|
}
|
|
@@ -4530,7 +4532,7 @@ function keyWasPressed(key, device=0)
|
|
|
4530
4532
|
* @memberof Input */
|
|
4531
4533
|
function keyWasReleased(key, device=0)
|
|
4532
4534
|
{
|
|
4533
|
-
ASSERT(
|
|
4535
|
+
ASSERT(isStringLike(key), 'key must be a number or string');
|
|
4534
4536
|
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
4535
4537
|
return !!(inputData[device]?.[key] & 4);
|
|
4536
4538
|
}
|
|
@@ -4544,10 +4546,10 @@ function keyWasReleased(key, device=0)
|
|
|
4544
4546
|
* @memberof Input */
|
|
4545
4547
|
function keyDirection(up='ArrowUp', down='ArrowDown', left='ArrowLeft', right='ArrowRight')
|
|
4546
4548
|
{
|
|
4547
|
-
ASSERT(
|
|
4548
|
-
ASSERT(
|
|
4549
|
-
ASSERT(
|
|
4550
|
-
ASSERT(
|
|
4549
|
+
ASSERT(isStringLike(up), 'up key must be a string');
|
|
4550
|
+
ASSERT(isStringLike(down), 'down key must be a string');
|
|
4551
|
+
ASSERT(isStringLike(left), 'left key must be a string');
|
|
4552
|
+
ASSERT(isStringLike(right), 'right key must be a string');
|
|
4551
4553
|
const k = (key)=> keyIsDown(key) ? 1 : 0;
|
|
4552
4554
|
return vec2(k(right) - k(left), k(up) - k(down));
|
|
4553
4555
|
}
|
|
@@ -5265,12 +5267,10 @@ function touchGamepadButtonCenter()
|
|
|
5265
5267
|
* @namespace Audio
|
|
5266
5268
|
*/
|
|
5267
5269
|
|
|
5268
|
-
/** Audio context used by the engine
|
|
5269
|
-
* browser autoplay warnings about constructing an AudioContext before any
|
|
5270
|
-
* user gesture.
|
|
5270
|
+
/** Audio context used by the engine
|
|
5271
5271
|
* @type {AudioContext}
|
|
5272
5272
|
* @memberof Audio */
|
|
5273
|
-
let audioContext;
|
|
5273
|
+
let audioContext = new AudioContext;
|
|
5274
5274
|
|
|
5275
5275
|
/** Master gain node for all audio to pass through
|
|
5276
5276
|
* @type {GainNode}
|
|
@@ -5286,13 +5286,12 @@ const audioDefaultSampleRate = 44100;
|
|
|
5286
5286
|
* @return {boolean} - True if the audio context is running
|
|
5287
5287
|
* @memberof Audio */
|
|
5288
5288
|
function audioIsRunning()
|
|
5289
|
-
{ return audioContext
|
|
5289
|
+
{ return audioContext.state === 'running'; }
|
|
5290
5290
|
|
|
5291
5291
|
function audioInit()
|
|
5292
5292
|
{
|
|
5293
5293
|
if (!soundEnable || headlessMode) return;
|
|
5294
5294
|
|
|
5295
|
-
audioContext = new AudioContext;
|
|
5296
5295
|
audioMasterGain = audioContext.createGain();
|
|
5297
5296
|
audioMasterGain.connect(audioContext.destination);
|
|
5298
5297
|
audioMasterGain.gain.value = soundVolume; // set starting value
|
|
@@ -5338,7 +5337,7 @@ class Sound
|
|
|
5338
5337
|
{
|
|
5339
5338
|
if (!soundEnable || headlessMode) return;
|
|
5340
5339
|
|
|
5341
|
-
ASSERT(!asset || isArray(asset) ||
|
|
5340
|
+
ASSERT(!asset || isArray(asset) || isStringLike(asset), 'asset must be a file name or zzfx array');
|
|
5342
5341
|
ASSERT(randomness === undefined || isNumber(randomness), 'randomness must be a number');
|
|
5343
5342
|
ASSERT(randomness === undefined || randomness >= 0 && randomness <=1, 'randomness must be between 0 and 1');
|
|
5344
5343
|
ASSERT(isNumber(range), 'range must be a number');
|
|
@@ -9097,12 +9096,31 @@ class UISystemPlugin
|
|
|
9097
9096
|
|
|
9098
9097
|
engineAddPlugin(uiUpdate, uiRender);
|
|
9099
9098
|
|
|
9100
|
-
// set object position
|
|
9099
|
+
// set object position based on anchor target (parent box, or canvas for roots),
|
|
9100
|
+
// self-pivot, and localPos offset
|
|
9101
9101
|
function updateTransforms(o)
|
|
9102
9102
|
{
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9103
|
+
let targetPos, targetSize;
|
|
9104
|
+
if (o.parent)
|
|
9105
|
+
{
|
|
9106
|
+
targetPos = o.parent.pos;
|
|
9107
|
+
targetSize = o.parent.size;
|
|
9108
|
+
}
|
|
9109
|
+
else
|
|
9110
|
+
{
|
|
9111
|
+
// anchor to canvas in native coords (handles nativeHeight if set)
|
|
9112
|
+
targetPos = uiSystem.screenToNative(mainCanvasSize.scale(.5));
|
|
9113
|
+
targetSize = uiSystem.nativeHeight
|
|
9114
|
+
? vec2(mainCanvasSize.x * uiSystem.nativeHeight / mainCanvasSize.y,
|
|
9115
|
+
uiSystem.nativeHeight)
|
|
9116
|
+
: mainCanvasSize;
|
|
9117
|
+
}
|
|
9118
|
+
|
|
9119
|
+
const a = o.anchor;
|
|
9120
|
+
o.pos = targetPos
|
|
9121
|
+
.add(targetSize.multiply(a).scale(.5)) // anchor point on target
|
|
9122
|
+
.subtract(o.size.multiply(a).scale(.5)) // pivot shift on self
|
|
9123
|
+
.add(o.localPos); // user offset
|
|
9106
9124
|
}
|
|
9107
9125
|
|
|
9108
9126
|
// setup recursive update and render
|
|
@@ -9569,9 +9587,8 @@ class UISystemPlugin
|
|
|
9569
9587
|
// confirm menu
|
|
9570
9588
|
const confirmMenu = new UIObject(vec2(), size);
|
|
9571
9589
|
uiSystem.confirmDialog = confirmMenu;
|
|
9572
|
-
confirmMenu.onRender = ()=>
|
|
9590
|
+
confirmMenu.onRender = ()=>
|
|
9573
9591
|
{
|
|
9574
|
-
confirmMenu.pos = uiSystem.screenToNative(mainCanvasSize.scale(.5));
|
|
9575
9592
|
const backgroundColor = hsl(0,0,0,.7);
|
|
9576
9593
|
uiSystem.drawRect(vec2(), vec2(1e9), backgroundColor);
|
|
9577
9594
|
}
|
|
@@ -9704,7 +9721,11 @@ class UIObject
|
|
|
9704
9721
|
this.navigationIndex = undefined;
|
|
9705
9722
|
/** @property {boolean} - Should this be auto selected by navigation? Must also have valid navigation index. */
|
|
9706
9723
|
this.navigationAutoSelect = false;
|
|
9707
|
-
|
|
9724
|
+
/** @property {Vector2} - Where on parent (or canvas if no parent) this object is anchored.
|
|
9725
|
+
* Components in [-1, 1]: (0,0)=center, (-1,-1)=top-left, (1,1)=bottom-right.
|
|
9726
|
+
* Also acts as self-pivot — e.g. (1,-1) puts your top-right corner at the anchor point. */
|
|
9727
|
+
this.anchor = vec2();
|
|
9728
|
+
|
|
9708
9729
|
uiSystem.uiObjects.push(this);
|
|
9709
9730
|
}
|
|
9710
9731
|
|
|
@@ -9957,9 +9978,9 @@ class UIText extends UIObject
|
|
|
9957
9978
|
{
|
|
9958
9979
|
super(pos, size);
|
|
9959
9980
|
|
|
9960
|
-
ASSERT(
|
|
9981
|
+
ASSERT(isStringLike(text), 'ui text must be a string');
|
|
9961
9982
|
ASSERT(['left','center','right'].includes(align), 'ui text align must be left, center, or right');
|
|
9962
|
-
ASSERT(
|
|
9983
|
+
ASSERT(isStringLike(font), 'ui text font must be a string');
|
|
9963
9984
|
|
|
9964
9985
|
// set properties
|
|
9965
9986
|
this.text = text;
|
|
@@ -10007,7 +10028,7 @@ class UITextInput extends UIObject
|
|
|
10007
10028
|
{
|
|
10008
10029
|
super(pos, size);
|
|
10009
10030
|
|
|
10010
|
-
ASSERT(
|
|
10031
|
+
ASSERT(isStringLike(text), 'ui text must be a string');
|
|
10011
10032
|
|
|
10012
10033
|
/** @property {number} - Max length of input (0 = no limit) */
|
|
10013
10034
|
this.maxLength = 0;
|
|
@@ -10144,7 +10165,7 @@ class UIButton extends UIObject
|
|
|
10144
10165
|
{
|
|
10145
10166
|
super(pos, size);
|
|
10146
10167
|
|
|
10147
|
-
ASSERT(
|
|
10168
|
+
ASSERT(isStringLike(text), 'ui button must be a string');
|
|
10148
10169
|
ASSERT(isColor(color), 'ui button color must be a color');
|
|
10149
10170
|
|
|
10150
10171
|
/** @property {Vector2} - Text offset for the button */
|
|
@@ -10185,7 +10206,7 @@ class UICheckbox extends UIObject
|
|
|
10185
10206
|
{
|
|
10186
10207
|
super(pos, size);
|
|
10187
10208
|
|
|
10188
|
-
ASSERT(
|
|
10209
|
+
ASSERT(isStringLike(text), 'ui checkbox must be a string');
|
|
10189
10210
|
ASSERT(isColor(color), 'ui checkbox color must be a color');
|
|
10190
10211
|
|
|
10191
10212
|
/** @property {boolean} - Current percentage value of this slider 0-1 */
|
|
@@ -10242,7 +10263,7 @@ class UISlider extends UIObject
|
|
|
10242
10263
|
super(pos, size);
|
|
10243
10264
|
|
|
10244
10265
|
ASSERT(isNumber(value), 'ui slider value must be a number');
|
|
10245
|
-
ASSERT(
|
|
10266
|
+
ASSERT(isStringLike(text), 'ui slider must be a string');
|
|
10246
10267
|
ASSERT(isColor(color), 'ui slider color must be a color');
|
|
10247
10268
|
ASSERT(isColor(handleColor), 'ui slider handleColor must be a color');
|
|
10248
10269
|
|
|
@@ -10360,7 +10381,7 @@ class UIVideo extends UIObject
|
|
|
10360
10381
|
{
|
|
10361
10382
|
super(pos, size || vec2());
|
|
10362
10383
|
|
|
10363
|
-
ASSERT(
|
|
10384
|
+
ASSERT(isStringLike(src), 'video src must be a string');
|
|
10364
10385
|
ASSERT(isNumber(volume), 'video volume must be a number');
|
|
10365
10386
|
|
|
10366
10387
|
this.color = BLACK; // default to black background
|
|
@@ -13210,7 +13231,7 @@ const Ease =
|
|
|
13210
13231
|
function tweenProperty(target, propertyPath, start, end, duration = 1, options = {})
|
|
13211
13232
|
{
|
|
13212
13233
|
ASSERT(target != null && typeof target === 'object', 'tweenProperty target must be an object');
|
|
13213
|
-
ASSERT(
|
|
13234
|
+
ASSERT(isStringLike(propertyPath) && propertyPath.length > 0, 'tweenProperty propertyPath must be a non-empty string');
|
|
13214
13235
|
|
|
13215
13236
|
const parts = propertyPath.split('.');
|
|
13216
13237
|
const lastKey = parts.pop();
|
package/package.json
CHANGED
package/plugins/tweenSystem.js
CHANGED
|
@@ -409,7 +409,7 @@ const Ease =
|
|
|
409
409
|
function tweenProperty(target, propertyPath, start, end, duration = 1, options = {})
|
|
410
410
|
{
|
|
411
411
|
ASSERT(target != null && typeof target === 'object', 'tweenProperty target must be an object');
|
|
412
|
-
ASSERT(
|
|
412
|
+
ASSERT(isStringLike(propertyPath) && propertyPath.length > 0, 'tweenProperty propertyPath must be a non-empty string');
|
|
413
413
|
|
|
414
414
|
const parts = propertyPath.split('.');
|
|
415
415
|
const lastKey = parts.pop();
|
package/plugins/uiSystem.js
CHANGED
|
@@ -125,12 +125,31 @@ class UISystemPlugin
|
|
|
125
125
|
|
|
126
126
|
engineAddPlugin(uiUpdate, uiRender);
|
|
127
127
|
|
|
128
|
-
// set object position
|
|
128
|
+
// set object position based on anchor target (parent box, or canvas for roots),
|
|
129
|
+
// self-pivot, and localPos offset
|
|
129
130
|
function updateTransforms(o)
|
|
130
131
|
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
let targetPos, targetSize;
|
|
133
|
+
if (o.parent)
|
|
134
|
+
{
|
|
135
|
+
targetPos = o.parent.pos;
|
|
136
|
+
targetSize = o.parent.size;
|
|
137
|
+
}
|
|
138
|
+
else
|
|
139
|
+
{
|
|
140
|
+
// anchor to canvas in native coords (handles nativeHeight if set)
|
|
141
|
+
targetPos = uiSystem.screenToNative(mainCanvasSize.scale(.5));
|
|
142
|
+
targetSize = uiSystem.nativeHeight
|
|
143
|
+
? vec2(mainCanvasSize.x * uiSystem.nativeHeight / mainCanvasSize.y,
|
|
144
|
+
uiSystem.nativeHeight)
|
|
145
|
+
: mainCanvasSize;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const a = o.anchor;
|
|
149
|
+
o.pos = targetPos
|
|
150
|
+
.add(targetSize.multiply(a).scale(.5)) // anchor point on target
|
|
151
|
+
.subtract(o.size.multiply(a).scale(.5)) // pivot shift on self
|
|
152
|
+
.add(o.localPos); // user offset
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
// setup recursive update and render
|
|
@@ -597,9 +616,8 @@ class UISystemPlugin
|
|
|
597
616
|
// confirm menu
|
|
598
617
|
const confirmMenu = new UIObject(vec2(), size);
|
|
599
618
|
uiSystem.confirmDialog = confirmMenu;
|
|
600
|
-
confirmMenu.onRender = ()=>
|
|
619
|
+
confirmMenu.onRender = ()=>
|
|
601
620
|
{
|
|
602
|
-
confirmMenu.pos = uiSystem.screenToNative(mainCanvasSize.scale(.5));
|
|
603
621
|
const backgroundColor = hsl(0,0,0,.7);
|
|
604
622
|
uiSystem.drawRect(vec2(), vec2(1e9), backgroundColor);
|
|
605
623
|
}
|
|
@@ -732,7 +750,11 @@ class UIObject
|
|
|
732
750
|
this.navigationIndex = undefined;
|
|
733
751
|
/** @property {boolean} - Should this be auto selected by navigation? Must also have valid navigation index. */
|
|
734
752
|
this.navigationAutoSelect = false;
|
|
735
|
-
|
|
753
|
+
/** @property {Vector2} - Where on parent (or canvas if no parent) this object is anchored.
|
|
754
|
+
* Components in [-1, 1]: (0,0)=center, (-1,-1)=top-left, (1,1)=bottom-right.
|
|
755
|
+
* Also acts as self-pivot — e.g. (1,-1) puts your top-right corner at the anchor point. */
|
|
756
|
+
this.anchor = vec2();
|
|
757
|
+
|
|
736
758
|
uiSystem.uiObjects.push(this);
|
|
737
759
|
}
|
|
738
760
|
|
|
@@ -985,9 +1007,9 @@ class UIText extends UIObject
|
|
|
985
1007
|
{
|
|
986
1008
|
super(pos, size);
|
|
987
1009
|
|
|
988
|
-
ASSERT(
|
|
1010
|
+
ASSERT(isStringLike(text), 'ui text must be a string');
|
|
989
1011
|
ASSERT(['left','center','right'].includes(align), 'ui text align must be left, center, or right');
|
|
990
|
-
ASSERT(
|
|
1012
|
+
ASSERT(isStringLike(font), 'ui text font must be a string');
|
|
991
1013
|
|
|
992
1014
|
// set properties
|
|
993
1015
|
this.text = text;
|
|
@@ -1035,7 +1057,7 @@ class UITextInput extends UIObject
|
|
|
1035
1057
|
{
|
|
1036
1058
|
super(pos, size);
|
|
1037
1059
|
|
|
1038
|
-
ASSERT(
|
|
1060
|
+
ASSERT(isStringLike(text), 'ui text must be a string');
|
|
1039
1061
|
|
|
1040
1062
|
/** @property {number} - Max length of input (0 = no limit) */
|
|
1041
1063
|
this.maxLength = 0;
|
|
@@ -1172,7 +1194,7 @@ class UIButton extends UIObject
|
|
|
1172
1194
|
{
|
|
1173
1195
|
super(pos, size);
|
|
1174
1196
|
|
|
1175
|
-
ASSERT(
|
|
1197
|
+
ASSERT(isStringLike(text), 'ui button must be a string');
|
|
1176
1198
|
ASSERT(isColor(color), 'ui button color must be a color');
|
|
1177
1199
|
|
|
1178
1200
|
/** @property {Vector2} - Text offset for the button */
|
|
@@ -1213,7 +1235,7 @@ class UICheckbox extends UIObject
|
|
|
1213
1235
|
{
|
|
1214
1236
|
super(pos, size);
|
|
1215
1237
|
|
|
1216
|
-
ASSERT(
|
|
1238
|
+
ASSERT(isStringLike(text), 'ui checkbox must be a string');
|
|
1217
1239
|
ASSERT(isColor(color), 'ui checkbox color must be a color');
|
|
1218
1240
|
|
|
1219
1241
|
/** @property {boolean} - Current percentage value of this slider 0-1 */
|
|
@@ -1270,7 +1292,7 @@ class UISlider extends UIObject
|
|
|
1270
1292
|
super(pos, size);
|
|
1271
1293
|
|
|
1272
1294
|
ASSERT(isNumber(value), 'ui slider value must be a number');
|
|
1273
|
-
ASSERT(
|
|
1295
|
+
ASSERT(isStringLike(text), 'ui slider must be a string');
|
|
1274
1296
|
ASSERT(isColor(color), 'ui slider color must be a color');
|
|
1275
1297
|
ASSERT(isColor(handleColor), 'ui slider handleColor must be a color');
|
|
1276
1298
|
|
|
@@ -1388,7 +1410,7 @@ class UIVideo extends UIObject
|
|
|
1388
1410
|
{
|
|
1389
1411
|
super(pos, size || vec2());
|
|
1390
1412
|
|
|
1391
|
-
ASSERT(
|
|
1413
|
+
ASSERT(isStringLike(src), 'video src must be a string');
|
|
1392
1414
|
ASSERT(isNumber(volume), 'video volume must be a number');
|
|
1393
1415
|
|
|
1394
1416
|
this.color = BLACK; // default to black background
|
package/src/engine.js
CHANGED
package/src/engineAudio.js
CHANGED
|
@@ -14,12 +14,10 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
/** Audio context used by the engine
|
|
18
|
-
* browser autoplay warnings about constructing an AudioContext before any
|
|
19
|
-
* user gesture.
|
|
17
|
+
/** Audio context used by the engine
|
|
20
18
|
* @type {AudioContext}
|
|
21
19
|
* @memberof Audio */
|
|
22
|
-
let audioContext;
|
|
20
|
+
let audioContext = new AudioContext;
|
|
23
21
|
|
|
24
22
|
/** Master gain node for all audio to pass through
|
|
25
23
|
* @type {GainNode}
|
|
@@ -35,13 +33,12 @@ const audioDefaultSampleRate = 44100;
|
|
|
35
33
|
* @return {boolean} - True if the audio context is running
|
|
36
34
|
* @memberof Audio */
|
|
37
35
|
function audioIsRunning()
|
|
38
|
-
{ return audioContext
|
|
36
|
+
{ return audioContext.state === 'running'; }
|
|
39
37
|
|
|
40
38
|
function audioInit()
|
|
41
39
|
{
|
|
42
40
|
if (!soundEnable || headlessMode) return;
|
|
43
41
|
|
|
44
|
-
audioContext = new AudioContext;
|
|
45
42
|
audioMasterGain = audioContext.createGain();
|
|
46
43
|
audioMasterGain.connect(audioContext.destination);
|
|
47
44
|
audioMasterGain.gain.value = soundVolume; // set starting value
|
|
@@ -87,7 +84,7 @@ class Sound
|
|
|
87
84
|
{
|
|
88
85
|
if (!soundEnable || headlessMode) return;
|
|
89
86
|
|
|
90
|
-
ASSERT(!asset || isArray(asset) ||
|
|
87
|
+
ASSERT(!asset || isArray(asset) || isStringLike(asset), 'asset must be a file name or zzfx array');
|
|
91
88
|
ASSERT(randomness === undefined || isNumber(randomness), 'randomness must be a number');
|
|
92
89
|
ASSERT(randomness === undefined || randomness >= 0 && randomness <=1, 'randomness must be between 0 and 1');
|
|
93
90
|
ASSERT(isNumber(range), 'range must be a number');
|
package/src/engineDebug.js
CHANGED
|
@@ -79,7 +79,7 @@ function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false, s
|
|
|
79
79
|
{
|
|
80
80
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
81
81
|
ASSERT(isVector2(size), 'size must be a vec2');
|
|
82
|
-
ASSERT(
|
|
82
|
+
ASSERT(isStringLike(color) || isColor(color), 'color is invalid');
|
|
83
83
|
ASSERT(isNumber(time), 'time must be a number');
|
|
84
84
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
85
85
|
|
|
@@ -106,7 +106,7 @@ function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false, screen
|
|
|
106
106
|
{
|
|
107
107
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
108
108
|
ASSERT(isArray(points), 'points must be an array');
|
|
109
|
-
ASSERT(
|
|
109
|
+
ASSERT(isStringLike(color) || isColor(color), 'color is invalid');
|
|
110
110
|
ASSERT(isNumber(time), 'time must be a number');
|
|
111
111
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
112
112
|
|
|
@@ -130,7 +130,7 @@ function debugCircle(pos, size=0, color=WHITE, time=0, fill=false, screenSpace=f
|
|
|
130
130
|
{
|
|
131
131
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
132
132
|
ASSERT(isNumber(size), 'size must be a number');
|
|
133
|
-
ASSERT(
|
|
133
|
+
ASSERT(isStringLike(color) || isColor(color), 'color is invalid');
|
|
134
134
|
ASSERT(isNumber(time), 'time must be a number');
|
|
135
135
|
|
|
136
136
|
if (isColor(color))
|
|
@@ -208,13 +208,13 @@ function debugOverlap(posA, sizeA, posB, sizeB, color, time, screenSpace=false)
|
|
|
208
208
|
* @memberof Debug */
|
|
209
209
|
function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monospace', screenSpace=false)
|
|
210
210
|
{
|
|
211
|
-
ASSERT(
|
|
211
|
+
ASSERT(isStringLike(text), 'text must be a string');
|
|
212
212
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
213
213
|
ASSERT(isNumber(size), 'size must be a number');
|
|
214
|
-
ASSERT(
|
|
214
|
+
ASSERT(isStringLike(color) || isColor(color), 'color is invalid');
|
|
215
215
|
ASSERT(isNumber(time), 'time must be a number');
|
|
216
216
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
217
|
-
ASSERT(
|
|
217
|
+
ASSERT(isStringLike(font), 'font must be a string');
|
|
218
218
|
|
|
219
219
|
if (isColor(color))
|
|
220
220
|
color = color.toString();
|
package/src/engineDraw.js
CHANGED
|
@@ -810,15 +810,15 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
810
810
|
* @memberof Draw */
|
|
811
811
|
function drawTextScreen(text, pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, textAlign='center', font=fontDefault, fontStyle='', maxWidth, angle=0, context=drawContext)
|
|
812
812
|
{
|
|
813
|
-
ASSERT(
|
|
813
|
+
ASSERT(isStringLike(text), 'text must be a string');
|
|
814
814
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
815
815
|
ASSERT(isNumber(size), 'size must be a number');
|
|
816
816
|
ASSERT(isColor(color), 'color must be a color');
|
|
817
817
|
ASSERT(isNumber(lineWidth), 'lineWidth must be a number');
|
|
818
818
|
ASSERT(isColor(lineColor), 'lineColor must be a color');
|
|
819
819
|
ASSERT(['left','center','right'].includes(textAlign), 'align must be left, center, or right');
|
|
820
|
-
ASSERT(
|
|
821
|
-
ASSERT(
|
|
820
|
+
ASSERT(isStringLike(font), 'font must be a string');
|
|
821
|
+
ASSERT(isStringLike(fontStyle), 'fontStyle must be a string');
|
|
822
822
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
823
823
|
|
|
824
824
|
context.fillStyle = color.toString();
|
|
@@ -855,7 +855,7 @@ async function loadTexture(textureIndex, src)
|
|
|
855
855
|
{
|
|
856
856
|
ASSERT(isNumber(textureIndex), 'textureIndex must be a number');
|
|
857
857
|
ASSERT(!textureInfos[textureIndex], 'textureIndex is already loaded!');
|
|
858
|
-
ASSERT(!src ||
|
|
858
|
+
ASSERT(!src || isStringLike(src), 'image src must be a string');
|
|
859
859
|
|
|
860
860
|
const image = new Image;
|
|
861
861
|
if (src)
|
|
@@ -1243,7 +1243,7 @@ class FontImage
|
|
|
1243
1243
|
*/
|
|
1244
1244
|
drawTextScreen(text, pos, size, center=true, color=WHITE, useWebGL=glEnable, context)
|
|
1245
1245
|
{
|
|
1246
|
-
ASSERT(
|
|
1246
|
+
ASSERT(isStringLike(text), 'text must be a string');
|
|
1247
1247
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
1248
1248
|
ASSERT(isVector2(size) || typeof size === 'number', 'size must be a vec2 or number');
|
|
1249
1249
|
ASSERT(isColor(color), 'color must be a color');
|
package/src/engineExport.js
CHANGED
package/src/engineInput.js
CHANGED
|
@@ -103,7 +103,7 @@ function inputClear()
|
|
|
103
103
|
* @memberof Input */
|
|
104
104
|
function keyIsDown(key, device=0)
|
|
105
105
|
{
|
|
106
|
-
ASSERT(
|
|
106
|
+
ASSERT(isStringLike(key), 'key must be a number or string');
|
|
107
107
|
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
108
108
|
return !!(inputData[device]?.[key] & 1);
|
|
109
109
|
}
|
|
@@ -115,7 +115,7 @@ function keyIsDown(key, device=0)
|
|
|
115
115
|
* @memberof Input */
|
|
116
116
|
function keyWasPressed(key, device=0)
|
|
117
117
|
{
|
|
118
|
-
ASSERT(
|
|
118
|
+
ASSERT(isStringLike(key), 'key must be a number or string');
|
|
119
119
|
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
120
120
|
return !!(inputData[device]?.[key] & 2);
|
|
121
121
|
}
|
|
@@ -127,7 +127,7 @@ function keyWasPressed(key, device=0)
|
|
|
127
127
|
* @memberof Input */
|
|
128
128
|
function keyWasReleased(key, device=0)
|
|
129
129
|
{
|
|
130
|
-
ASSERT(
|
|
130
|
+
ASSERT(isStringLike(key), 'key must be a number or string');
|
|
131
131
|
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
132
132
|
return !!(inputData[device]?.[key] & 4);
|
|
133
133
|
}
|
|
@@ -141,10 +141,10 @@ function keyWasReleased(key, device=0)
|
|
|
141
141
|
* @memberof Input */
|
|
142
142
|
function keyDirection(up='ArrowUp', down='ArrowDown', left='ArrowLeft', right='ArrowRight')
|
|
143
143
|
{
|
|
144
|
-
ASSERT(
|
|
145
|
-
ASSERT(
|
|
146
|
-
ASSERT(
|
|
147
|
-
ASSERT(
|
|
144
|
+
ASSERT(isStringLike(up), 'up key must be a string');
|
|
145
|
+
ASSERT(isStringLike(down), 'down key must be a string');
|
|
146
|
+
ASSERT(isStringLike(left), 'left key must be a string');
|
|
147
|
+
ASSERT(isStringLike(right), 'right key must be a string');
|
|
148
148
|
const k = (key)=> keyIsDown(key) ? 1 : 0;
|
|
149
149
|
return vec2(k(right) - k(left), k(up) - k(down));
|
|
150
150
|
}
|
package/src/engineMath.js
CHANGED
|
@@ -292,13 +292,15 @@ function oscillate(frequency=1, amplitude=1, t=time, offset=0, type=0)
|
|
|
292
292
|
function isNumber(n) { return typeof n === 'number' && !isNaN(n); }
|
|
293
293
|
|
|
294
294
|
/**
|
|
295
|
-
* Check if
|
|
295
|
+
* Check if a value is stringifiable — i.e. it has a toString that returns
|
|
296
|
+
* a string. Use this for ASSERTs and inputs that will be coerced to text;
|
|
297
|
+
* use `typeof x === 'string'` inline if you need strict-string semantics.
|
|
296
298
|
* - Returns true for strings, numbers, and most objects
|
|
297
299
|
* - Returns false for null and undefined
|
|
298
300
|
* @param {any} s
|
|
299
301
|
* @return {boolean}
|
|
300
302
|
* @memberof Math */
|
|
301
|
-
function
|
|
303
|
+
function isStringLike(s) { return s != null && typeof s?.toString() === 'string'; }
|
|
302
304
|
|
|
303
305
|
/**
|
|
304
306
|
* Check if object is an array
|
|
@@ -1042,7 +1044,7 @@ class Color
|
|
|
1042
1044
|
* @return {Color} */
|
|
1043
1045
|
setHex(hex)
|
|
1044
1046
|
{
|
|
1045
|
-
ASSERT(
|
|
1047
|
+
ASSERT(isStringLike(hex), 'Color hex code must be a string');
|
|
1046
1048
|
ASSERT(hex[0] === '#', 'Color hex code must start with #');
|
|
1047
1049
|
ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
|
|
1048
1050
|
|