littlejsengine 1.18.15 → 1.18.18
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 +4 -4
- package/dist/littlejs.d.ts +277 -32
- package/dist/littlejs.esm.js +1395 -304
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +1367 -303
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +1341 -287
- package/package.json +1 -1
- package/plugins/box2d.js +9 -13
- package/plugins/drawUtilities.js +84 -10
- package/plugins/lightSystem.js +330 -0
- package/plugins/medalSystem.js +36 -5
- package/plugins/pluginExport.js +8 -0
- package/plugins/uiSystem.js +35 -25
- package/src/engine.js +1 -1
- package/src/engineAudio.js +21 -3
- package/src/engineBuild.mjs +1 -0
- package/src/engineDebug.js +27 -16
- package/src/engineDraw.js +68 -10
- package/src/engineExport.js +20 -1
- package/src/engineInput.js +597 -191
- package/src/engineMath.js +15 -0
- package/src/engineObject.js +5 -2
- package/src/engineParticles.js +23 -19
- package/src/engineRelease.js +1 -0
- package/src/engineSettings.js +114 -10
- package/src/engineTileLayer.js +5 -0
package/plugins/uiSystem.js
CHANGED
|
@@ -132,7 +132,7 @@ class UISystemPlugin
|
|
|
132
132
|
let targetPos, targetSize;
|
|
133
133
|
if (o.parent)
|
|
134
134
|
{
|
|
135
|
-
targetPos = o.parent.
|
|
135
|
+
targetPos = o.parent.nativePos;
|
|
136
136
|
targetSize = o.parent.size;
|
|
137
137
|
}
|
|
138
138
|
else
|
|
@@ -146,7 +146,7 @@ class UISystemPlugin
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
const a = o.anchor;
|
|
149
|
-
o.
|
|
149
|
+
o.nativePos = targetPos
|
|
150
150
|
.add(targetSize.multiply(a).scale(.5)) // anchor point on target
|
|
151
151
|
.subtract(o.size.multiply(a).scale(.5)) // pivot shift on self
|
|
152
152
|
.add(o.localPos); // user offset
|
|
@@ -252,13 +252,18 @@ class UISystemPlugin
|
|
|
252
252
|
|
|
253
253
|
function updateObject(o)
|
|
254
254
|
{
|
|
255
|
-
if (!o.visible) return;
|
|
255
|
+
if (o.destroyed || !o.visible) return;
|
|
256
256
|
|
|
257
257
|
// update in reverse order to detect mouse enter/leave
|
|
258
258
|
updateTransforms(o);
|
|
259
259
|
for (let i=o.children.length; i--;)
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
{
|
|
261
|
+
// a child may destroy siblings mid-update (e.g. dialog close)
|
|
262
|
+
const child = o.children[i];
|
|
263
|
+
child && updateObject(child);
|
|
264
|
+
}
|
|
265
|
+
if (!o.destroyed)
|
|
266
|
+
o.update();
|
|
262
267
|
}
|
|
263
268
|
}
|
|
264
269
|
function uiRender()
|
|
@@ -682,10 +687,15 @@ class UIObject
|
|
|
682
687
|
ASSERT(isVector2(pos), 'ui object pos must be a vec2');
|
|
683
688
|
ASSERT(isVector2(size), 'ui object size must be a vec2');
|
|
684
689
|
|
|
685
|
-
/** @property {Vector2} -
|
|
690
|
+
/** @property {Vector2} - Position you set: an offset from this object's
|
|
691
|
+
* anchor point (the parent box, or the canvas for roots). This is the
|
|
692
|
+
* input that controls placement — set this, not nativePos. */
|
|
686
693
|
this.localPos = pos.copy();
|
|
687
|
-
/** @property {Vector2} -
|
|
688
|
-
|
|
694
|
+
/** @property {Vector2} - Resolved position in native UI space, recomputed
|
|
695
|
+
* every frame from localPos + anchor (and nativeHeight, if set). This is a
|
|
696
|
+
* derived output used for drawing and hit-testing; assigning to it has no
|
|
697
|
+
* effect since it is overwritten each frame. Set localPos instead. */
|
|
698
|
+
this.nativePos = pos.copy();
|
|
689
699
|
/** @property {Vector2} - Screen space size of the object */
|
|
690
700
|
this.size = size.copy();
|
|
691
701
|
/** @property {Color} - Color of the object */
|
|
@@ -820,7 +830,7 @@ class UIObject
|
|
|
820
830
|
const size = !isTouchDevice ? this.size :
|
|
821
831
|
this.size.add(vec2(this.extraTouchSize || 0));
|
|
822
832
|
const pos = uiSystem.screenToNative(mousePosScreen);
|
|
823
|
-
return isOverlapping(this.
|
|
833
|
+
return isOverlapping(this.nativePos, size, pos);
|
|
824
834
|
}
|
|
825
835
|
|
|
826
836
|
/** Update the object, called automatically by plugin once each frame */
|
|
@@ -910,7 +920,7 @@ class UIObject
|
|
|
910
920
|
this.color : this.color;
|
|
911
921
|
const lineWidth = this.lineWidth * (isNavigationObject ? 1.5 : 1);
|
|
912
922
|
|
|
913
|
-
uiSystem.drawRect(this.
|
|
923
|
+
uiSystem.drawRect(this.nativePos, this.size, color, lineWidth, lineColor, this.cornerRadius, this.gradientColor, this.shadowColor, this.shadowBlur, this.shadowOffset);
|
|
914
924
|
}
|
|
915
925
|
|
|
916
926
|
/** Get the size for text with overrides and scale
|
|
@@ -947,8 +957,8 @@ class UIObject
|
|
|
947
957
|
let text = 'type = ' + this.constructor.name;
|
|
948
958
|
if (this.text)
|
|
949
959
|
text += '\ntext = ' + this.text;
|
|
950
|
-
if (this.
|
|
951
|
-
text += '\
|
|
960
|
+
if (this.nativePos.x || this.nativePos.y)
|
|
961
|
+
text += '\nnativePos = ' + this.nativePos;
|
|
952
962
|
if (this.localPos.x || this.localPos.y)
|
|
953
963
|
text += '\nlocalPos = ' + this.localPos;
|
|
954
964
|
if (this.size.x || this.size.y)
|
|
@@ -968,7 +978,7 @@ class UIObject
|
|
|
968
978
|
this.isHoverObject() ? YELLOW :
|
|
969
979
|
this.disabled ? PURPLE :
|
|
970
980
|
this.interactive ? RED : BLUE;
|
|
971
|
-
uiSystem.drawRect(this.
|
|
981
|
+
uiSystem.drawRect(this.nativePos, this.size, CLEAR_BLACK, 4, color);
|
|
972
982
|
}
|
|
973
983
|
|
|
974
984
|
/** Internal function called when object is clicked
|
|
@@ -1051,7 +1061,7 @@ class UIText extends UIObject
|
|
|
1051
1061
|
|
|
1052
1062
|
// render the text
|
|
1053
1063
|
const textSize = this.getTextSize();
|
|
1054
|
-
uiSystem.drawText(this.text, this.
|
|
1064
|
+
uiSystem.drawText(this.text, this.nativePos, textSize, this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow, this.shadowColor, this.shadowBlur, this.shadowOffset);
|
|
1055
1065
|
}
|
|
1056
1066
|
}
|
|
1057
1067
|
|
|
@@ -1146,7 +1156,7 @@ class UITextInput extends UIObject
|
|
|
1146
1156
|
let text = this.text;
|
|
1147
1157
|
if (this.isKeyInputObject()) // add a cursor to end of text
|
|
1148
1158
|
text += timeReal%1 < .5 ? '█' : '░';
|
|
1149
|
-
uiSystem.drawText(text, this.
|
|
1159
|
+
uiSystem.drawText(text, this.nativePos, textSize,
|
|
1150
1160
|
this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
1151
1161
|
}
|
|
1152
1162
|
}
|
|
@@ -1189,7 +1199,7 @@ class UITile extends UIObject
|
|
|
1189
1199
|
}
|
|
1190
1200
|
render()
|
|
1191
1201
|
{
|
|
1192
|
-
uiSystem.drawTile(this.
|
|
1202
|
+
uiSystem.drawTile(this.nativePos, this.size, this.tileInfo, this.color, this.angle, this.mirror, this.shadowColor, this.shadowBlur, this.shadowOffset);
|
|
1193
1203
|
}
|
|
1194
1204
|
}
|
|
1195
1205
|
|
|
@@ -1228,7 +1238,7 @@ class UIButton extends UIObject
|
|
|
1228
1238
|
|
|
1229
1239
|
// draw the text scaled to fit
|
|
1230
1240
|
const textSize = this.getTextSize();
|
|
1231
|
-
uiSystem.drawText(this.text, this.
|
|
1241
|
+
uiSystem.drawText(this.text, this.nativePos.add(this.textOffset), textSize,
|
|
1232
1242
|
this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
1233
1243
|
}
|
|
1234
1244
|
}
|
|
@@ -1276,13 +1286,13 @@ class UICheckbox extends UIObject
|
|
|
1276
1286
|
const p = this.cornerRadius / min(this.size.x, this.size.y) * 2;
|
|
1277
1287
|
const length = lerp(1, 2**.5/2, p) / 2;
|
|
1278
1288
|
let s = this.size.scale(length);
|
|
1279
|
-
uiSystem.drawLine(this.
|
|
1280
|
-
uiSystem.drawLine(this.
|
|
1289
|
+
uiSystem.drawLine(this.nativePos.add(s.multiply(vec2(-1))), this.nativePos.add(s.multiply(vec2(1))), this.lineWidth, this.lineColor);
|
|
1290
|
+
uiSystem.drawLine(this.nativePos.add(s.multiply(vec2(-1,1))), this.nativePos.add(s.multiply(vec2(1,-1))), this.lineWidth, this.lineColor);
|
|
1281
1291
|
}
|
|
1282
1292
|
|
|
1283
1293
|
// draw the text next to the checkbox
|
|
1284
1294
|
const textSize = this.getTextSize();
|
|
1285
|
-
const pos = this.
|
|
1295
|
+
const pos = this.nativePos.add(vec2(this.size.x,0));
|
|
1286
1296
|
uiSystem.drawText(this.text, pos, textSize,
|
|
1287
1297
|
this.textColor, this.textLineWidth, this.textLineColor, 'left', this.font, this.fontStyle, false, this.textShadow);
|
|
1288
1298
|
}
|
|
@@ -1338,7 +1348,7 @@ class UISlider extends UIObject
|
|
|
1338
1348
|
const isHorizontal = this.size.x > this.size.y;
|
|
1339
1349
|
const handleSize = isHorizontal ? this.size.y : this.size.x;
|
|
1340
1350
|
const barSize = isHorizontal ? this.size.x : this.size.y;
|
|
1341
|
-
const centerPos = isHorizontal ? this.
|
|
1351
|
+
const centerPos = isHorizontal ? this.nativePos.x : this.nativePos.y;
|
|
1342
1352
|
|
|
1343
1353
|
// check if value changed
|
|
1344
1354
|
const handleWidth = barSize - handleSize;
|
|
@@ -1372,7 +1382,7 @@ class UISlider extends UIObject
|
|
|
1372
1382
|
const minWidth = min(handleWidth, this.cornerRadius * 2);
|
|
1373
1383
|
const progressWidth = lerp(minWidth, barWidth, this.value);
|
|
1374
1384
|
const p = (progressWidth - barWidth) * (isHorizontal ? .5 : -.5);
|
|
1375
|
-
const pos = this.
|
|
1385
|
+
const pos = this.nativePos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
|
|
1376
1386
|
const color = this.disabled ? this.disabledColor : this.handleColor;
|
|
1377
1387
|
const drawSize = isHorizontal ?
|
|
1378
1388
|
vec2(progressWidth, this.size.y) : vec2(this.size.x, progressWidth);
|
|
@@ -1383,7 +1393,7 @@ class UISlider extends UIObject
|
|
|
1383
1393
|
// draw the slider handle
|
|
1384
1394
|
const value = clamp(isHorizontal ? this.value : 1 - this.value);
|
|
1385
1395
|
const p = (barWidth - handleWidth) * (value - .5);
|
|
1386
|
-
const pos = this.
|
|
1396
|
+
const pos = this.nativePos.add(isHorizontal ? vec2(p, 0) : vec2(0, p));
|
|
1387
1397
|
const color = this.disabled ? this.disabledColor : this.handleColor;
|
|
1388
1398
|
const drawSize = vec2(handleWidth);
|
|
1389
1399
|
uiSystem.drawRect(pos, drawSize, color, this.lineWidth, this.lineColor, this.cornerRadius, this.gradientColor);
|
|
@@ -1391,7 +1401,7 @@ class UISlider extends UIObject
|
|
|
1391
1401
|
|
|
1392
1402
|
// draw the text scaled to fit on the slider
|
|
1393
1403
|
const textSize = this.getTextSize();
|
|
1394
|
-
uiSystem.drawText(this.text, this.
|
|
1404
|
+
uiSystem.drawText(this.text, this.nativePos, textSize,
|
|
1395
1405
|
this.textColor, this.textLineWidth, this.textLineColor, this.align, this.font, this.fontStyle, true, this.textShadow);
|
|
1396
1406
|
}
|
|
1397
1407
|
navigatePressed()
|
|
@@ -1530,7 +1540,7 @@ class UIVideo extends UIObject
|
|
|
1530
1540
|
const context = uiSystem.uiContext;
|
|
1531
1541
|
const s = this.size;
|
|
1532
1542
|
context.save();
|
|
1533
|
-
context.translate(this.
|
|
1543
|
+
context.translate(this.nativePos.x, this.nativePos.y);
|
|
1534
1544
|
context.drawImage(this.video, -s.x/2, -s.y/2, s.x, s.y);
|
|
1535
1545
|
context.restore();
|
|
1536
1546
|
}
|
package/src/engine.js
CHANGED
package/src/engineAudio.js
CHANGED
|
@@ -135,7 +135,7 @@ class Sound
|
|
|
135
135
|
* @param {number} [randomnessScale] - How much to scale pitch randomness
|
|
136
136
|
* @param {boolean} [loop] - Should the sound loop?
|
|
137
137
|
* @param {boolean} [paused] - Should the sound start paused
|
|
138
|
-
* @return {SoundInstance} - The
|
|
138
|
+
* @return {SoundInstance} - The sound instance, or undefined if sound is disabled, not loaded, or running in headless mode
|
|
139
139
|
*/
|
|
140
140
|
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false, paused=false)
|
|
141
141
|
{
|
|
@@ -166,9 +166,23 @@ class Sound
|
|
|
166
166
|
pan = worldToScreen(pos).x * 2/mainCanvas.width - 1;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
// Create
|
|
169
|
+
// Create sound instance
|
|
170
170
|
const rate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
|
|
171
|
-
|
|
171
|
+
const instance = new SoundInstance(this, volume, rate, pan, loop, paused);
|
|
172
|
+
|
|
173
|
+
if (debug && debugSound && pos)
|
|
174
|
+
{
|
|
175
|
+
// visualize where positioned sounds play and their falloff range
|
|
176
|
+
debugCircle(pos, .5, '#0ff', .5, true);
|
|
177
|
+
if (this.range)
|
|
178
|
+
{
|
|
179
|
+
debugCircle(pos, 2*this.range, '#0ff', .5); // silent radius
|
|
180
|
+
debugCircle(pos, 2*this.range*this.taper, '#0ff', .5); // full volume radius
|
|
181
|
+
}
|
|
182
|
+
debugText('vol '+volume.toFixed(2)+' pitch '+rate.toFixed(2), pos, .5, '#0ff', .5);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return instance;
|
|
172
186
|
}
|
|
173
187
|
|
|
174
188
|
/** Play a music track that loops by default
|
|
@@ -534,6 +548,10 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
534
548
|
// play and return sound
|
|
535
549
|
const startOffset = offset * rate;
|
|
536
550
|
source.start(0, startOffset);
|
|
551
|
+
|
|
552
|
+
if (debug && debugSound)
|
|
553
|
+
LOG('sound', 'vol', volume.toFixed(2), 'rate', rate.toFixed(2), 'pan', pan.toFixed(2), loop ? 'loop' : '');
|
|
554
|
+
|
|
537
555
|
return source;
|
|
538
556
|
}
|
|
539
557
|
|
package/src/engineBuild.mjs
CHANGED
|
@@ -41,6 +41,7 @@ const enginePluginFiles =
|
|
|
41
41
|
`${PLUGIN_FOLDER}/medalSystem.js`,
|
|
42
42
|
`${PLUGIN_FOLDER}/newgrounds.js`,
|
|
43
43
|
`${PLUGIN_FOLDER}/postProcess.js`,
|
|
44
|
+
`${PLUGIN_FOLDER}/lightSystem.js`,
|
|
44
45
|
`${PLUGIN_FOLDER}/zzfxm.js`,
|
|
45
46
|
`${PLUGIN_FOLDER}/uiSystem.js`,
|
|
46
47
|
`${PLUGIN_FOLDER}/box2d.js`,
|
package/src/engineDebug.js
CHANGED
|
@@ -44,7 +44,7 @@ let debugKey = 'Escape';
|
|
|
44
44
|
let debugOverlay = false;
|
|
45
45
|
|
|
46
46
|
// Engine internal variables not exposed to documentation
|
|
47
|
-
let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugTakeScreenshot;
|
|
47
|
+
let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugSound = false, debugTakeScreenshot;
|
|
48
48
|
|
|
49
49
|
///////////////////////////////////////////////////////////////////////////////
|
|
50
50
|
// Debug helper functions
|
|
@@ -284,6 +284,8 @@ function debugUpdate()
|
|
|
284
284
|
debugRaycast = !debugRaycast;
|
|
285
285
|
if (keyWasPressed('Digit5'))
|
|
286
286
|
debugScreenshot();
|
|
287
|
+
if (keyWasPressed('Digit7'))
|
|
288
|
+
debugSound = !debugSound;
|
|
287
289
|
}
|
|
288
290
|
if (debugVideoCaptureIsActive())
|
|
289
291
|
{
|
|
@@ -303,6 +305,9 @@ function debugRender()
|
|
|
303
305
|
// flush any gl sprites before drawing debug info
|
|
304
306
|
glFlush();
|
|
305
307
|
|
|
308
|
+
const savedDrawCount = drawCount;
|
|
309
|
+
const savedPrimitiveCount = primitiveCount;
|
|
310
|
+
|
|
306
311
|
if (debugTakeScreenshot)
|
|
307
312
|
{
|
|
308
313
|
// combine canvases, remove alpha and save
|
|
@@ -336,6 +341,8 @@ function debugRender()
|
|
|
336
341
|
const stickCount = gamepadStickData[i].length;
|
|
337
342
|
for (let j = 0; j < stickCount; j++)
|
|
338
343
|
{
|
|
344
|
+
if (!(j in gamepadStickData[i]))
|
|
345
|
+
continue; // skip sticks that are not present (eg a disabled touch left stick)
|
|
339
346
|
const stick = gamepadStick(j, i);
|
|
340
347
|
const drawPos = cornerPos.add(vec2(j*stickScale*2, 0));
|
|
341
348
|
const stickPos = drawPos.add(stick.scale(stickScale));
|
|
@@ -514,6 +521,8 @@ function debugRender()
|
|
|
514
521
|
debugContext.fillStyle = '#fff';
|
|
515
522
|
debugContext.fillText('5: Save Screenshot', x, y += h);
|
|
516
523
|
debugContext.fillText('6: Toggle Video Capture', x, y += h);
|
|
524
|
+
debugContext.fillStyle = debugSound ? '#f00' : '#fff';
|
|
525
|
+
debugContext.fillText('7: Debug Sound', x, y += h);
|
|
517
526
|
|
|
518
527
|
let keysPressed = '';
|
|
519
528
|
let mousePressed = '';
|
|
@@ -548,10 +557,27 @@ function debugRender()
|
|
|
548
557
|
debugContext.fillText(debugParticles ? 'Debug Particles' : '', x, y += h);
|
|
549
558
|
debugContext.fillText(debugRaycast ? 'Debug Raycasts' : '', x, y += h);
|
|
550
559
|
debugContext.fillText(debugGamepads ? 'Debug Gamepads' : '', x, y += h);
|
|
560
|
+
debugContext.fillText(debugSound ? 'Debug Sound' : '', x, y += h);
|
|
551
561
|
}
|
|
552
562
|
|
|
553
563
|
debugContext.restore();
|
|
554
564
|
}
|
|
565
|
+
|
|
566
|
+
if (debugWatermark || debugOverlay)
|
|
567
|
+
{
|
|
568
|
+
// show fps stats display
|
|
569
|
+
mainContext.textAlign = 'right';
|
|
570
|
+
mainContext.textBaseline = 'top';
|
|
571
|
+
mainContext.font = '1em monospace';
|
|
572
|
+
mainContext.fillStyle = '#000';
|
|
573
|
+
const text = engineName + ' v' + engineVersion + ' / '
|
|
574
|
+
+ savedDrawCount + ' / ' + savedPrimitiveCount + ' / '
|
|
575
|
+
+ engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
576
|
+
+ (glEnable ? ' GL' : ' 2D') ;
|
|
577
|
+
mainContext.fillText(text, mainCanvas.width-3, 3);
|
|
578
|
+
mainContext.fillStyle = '#fff';
|
|
579
|
+
mainContext.fillText(text, mainCanvas.width-2, 2);
|
|
580
|
+
}
|
|
555
581
|
}
|
|
556
582
|
|
|
557
583
|
function debugRenderPost()
|
|
@@ -561,21 +587,6 @@ function debugRenderPost()
|
|
|
561
587
|
debugVideoCaptureUpdate();
|
|
562
588
|
return;
|
|
563
589
|
}
|
|
564
|
-
|
|
565
|
-
if (!debugWatermark && !debugOverlay) return;
|
|
566
|
-
|
|
567
|
-
// update fps display
|
|
568
|
-
mainContext.textAlign = 'right';
|
|
569
|
-
mainContext.textBaseline = 'top';
|
|
570
|
-
mainContext.font = '1em monospace';
|
|
571
|
-
mainContext.fillStyle = '#000';
|
|
572
|
-
const text = engineName + ' v' + engineVersion + ' / '
|
|
573
|
-
+ drawCount + ' / ' + primitiveCount + ' / '
|
|
574
|
-
+ engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
575
|
-
+ (glEnable ? ' GL' : ' 2D') ;
|
|
576
|
-
mainContext.fillText(text, mainCanvas.width-3, 3);
|
|
577
|
-
mainContext.fillStyle = '#fff';
|
|
578
|
-
mainContext.fillText(text, mainCanvas.width-2, 2);
|
|
579
590
|
}
|
|
580
591
|
|
|
581
592
|
///////////////////////////////////////////////////////////////////////////////
|
package/src/engineDraw.js
CHANGED
|
@@ -329,11 +329,12 @@ function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
|
329
329
|
// normal canvas 2D rendering method (slower)
|
|
330
330
|
++drawCount;
|
|
331
331
|
++primitiveCount;
|
|
332
|
-
size = new Vector2(size.x, -size.y); // flip upside down sprites
|
|
333
332
|
drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
334
333
|
{
|
|
335
334
|
if (textureInfo)
|
|
336
335
|
{
|
|
336
|
+
// un-flip Y so the image renders right-side up under drawCanvas2D's Y flip
|
|
337
|
+
context.scale(1, -1);
|
|
337
338
|
// calculate uvs and render
|
|
338
339
|
const x = tileInfo.pos.x, y = tileInfo.pos.y;
|
|
339
340
|
const w = tileInfo.size.x, h = tileInfo.size.y;
|
|
@@ -341,7 +342,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
|
341
342
|
}
|
|
342
343
|
else
|
|
343
344
|
{
|
|
344
|
-
// if no tile info, use untextured rect
|
|
345
|
+
// if no tile info, use untextured rect (Y-symmetric, no compensation needed)
|
|
345
346
|
const c = additiveColor ? color.add(additiveColor) : color;
|
|
346
347
|
context.fillStyle = c.toString();
|
|
347
348
|
context.fillRect(-.5, -.5, 1, 1);
|
|
@@ -415,11 +416,10 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=CLEAR_WHITE, an
|
|
|
415
416
|
// normal canvas 2D rendering method (slower)
|
|
416
417
|
++drawCount;
|
|
417
418
|
++primitiveCount;
|
|
418
|
-
size = new Vector2(size.x, -size.y); // fix upside down sprites
|
|
419
419
|
drawCanvas2D(pos, size, angle, false, (context)=>
|
|
420
420
|
{
|
|
421
|
-
//
|
|
422
|
-
const gradient = context.createLinearGradient(0,
|
|
421
|
+
// gradient endpoints are flipped to match the Y flip inside drawCanvas2D
|
|
422
|
+
const gradient = context.createLinearGradient(0, .5, 0, -.5);
|
|
423
423
|
gradient.addColorStop(0, colorTop.toString());
|
|
424
424
|
gradient.addColorStop(1, colorBottom.toString());
|
|
425
425
|
context.fillStyle = gradient;
|
|
@@ -838,7 +838,10 @@ function drawCircleGradient(pos, size=1, colorInner=WHITE, colorOuter=CLEAR_WHIT
|
|
|
838
838
|
* @memberof Draw
|
|
839
839
|
*/
|
|
840
840
|
|
|
841
|
-
/** Draw directly to a 2d canvas context in world space
|
|
841
|
+
/** Draw directly to a 2d canvas context in world space.
|
|
842
|
+
* The Y axis is flipped so world-Y-up coordinates render right-side up
|
|
843
|
+
* (matches the WebGL path). Callers whose drawing depends on Y direction
|
|
844
|
+
* (e.g. linear gradients) should flip their own Y endpoints accordingly.
|
|
842
845
|
* @param {Vector2} pos
|
|
843
846
|
* @param {Vector2} size
|
|
844
847
|
* @param {number} angle
|
|
@@ -1084,6 +1087,62 @@ function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
|
|
|
1084
1087
|
* @memberof Draw */
|
|
1085
1088
|
function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
|
|
1086
1089
|
|
|
1090
|
+
/** Fit the camera to a rectangle in world space by setting cameraPos and cameraScale
|
|
1091
|
+
* - worldMargin pads the content rectangle in world units, so the gap scales with the content on resize
|
|
1092
|
+
* - screenInset reserves space in screen pixels on each viewport edge (for example a HUD band) and
|
|
1093
|
+
* re-centers the content away from that edge, so the reserved band stays a fixed pixel size on resize
|
|
1094
|
+
* - worldMargin and screenInset may each be a number for all sides, a Vector2 (x=left/right, y=top/bottom),
|
|
1095
|
+
* or an object with any of {top, right, bottom, left}
|
|
1096
|
+
* @param {Vector2} center - Center of the rectangle in world space
|
|
1097
|
+
* @param {Vector2} size - Size of the rectangle in world space
|
|
1098
|
+
* @param {number|Vector2|Object} [worldMargin] - World space padding added around the content rectangle
|
|
1099
|
+
* @param {number|Vector2|Object} [screenInset] - Screen space padding in pixels reserved on each viewport edge
|
|
1100
|
+
* @return {number} - The new camera scale
|
|
1101
|
+
* @memberof Draw */
|
|
1102
|
+
function cameraFit(center, size, worldMargin, screenInset)
|
|
1103
|
+
{
|
|
1104
|
+
ASSERT(isVector2(center), 'center must be a vec2');
|
|
1105
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
1106
|
+
|
|
1107
|
+
// pad the content
|
|
1108
|
+
const margin = padSides(worldMargin);
|
|
1109
|
+
const inset = padSides(screenInset);
|
|
1110
|
+
const worldW = size.x + margin.left + margin.right;
|
|
1111
|
+
const worldH = size.y + margin.top + margin.bottom;
|
|
1112
|
+
const viewW = mainCanvasSize.x - inset.left - inset.right;
|
|
1113
|
+
const viewH = mainCanvasSize.y - inset.top - inset.bottom;
|
|
1114
|
+
|
|
1115
|
+
// bail on a degenerate rect or viewport rather than NaN the camera
|
|
1116
|
+
if (!(worldW > 0 && worldH > 0 && viewW > 0 && viewH > 0))
|
|
1117
|
+
return cameraScale;
|
|
1118
|
+
|
|
1119
|
+
// scale to fit the padded content
|
|
1120
|
+
cameraScale = min(viewW / worldW, viewH / worldH);
|
|
1121
|
+
|
|
1122
|
+
// calculate offset vectors
|
|
1123
|
+
const marginVector = vec2(margin.right - margin.left, margin.top - margin.bottom).scale(.5);
|
|
1124
|
+
const insetVector = vec2(inset.right - inset.left, inset.top - inset.bottom).scale(.5 / cameraScale);
|
|
1125
|
+
|
|
1126
|
+
// apply the offsets and return camera scale
|
|
1127
|
+
cameraPos = center.add(marginVector).add(insetVector);
|
|
1128
|
+
return cameraScale;
|
|
1129
|
+
|
|
1130
|
+
function padSides(p)
|
|
1131
|
+
{
|
|
1132
|
+
// normalize a padding option to {top, right, bottom, left}
|
|
1133
|
+
if (p === undefined || isNumber(p))
|
|
1134
|
+
p = vec2(p);
|
|
1135
|
+
if (isVector2(p))
|
|
1136
|
+
return { top: p.y, right: p.x, bottom: p.y, left: p.x };
|
|
1137
|
+
return {
|
|
1138
|
+
top: p.top || 0,
|
|
1139
|
+
right: p.right || 0,
|
|
1140
|
+
bottom: p.bottom || 0,
|
|
1141
|
+
left: p.left || 0,
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1087
1146
|
/** Check if a box, point, or circle is on screen with a circle test
|
|
1088
1147
|
* If size is a Vector2, uses the length as diameter
|
|
1089
1148
|
* This can be used to cull offscreen objects from render or update
|
|
@@ -1122,14 +1181,13 @@ function isOnScreen(pos, size=0)
|
|
|
1122
1181
|
y + size > -h && y - size < h;
|
|
1123
1182
|
}
|
|
1124
1183
|
|
|
1125
|
-
/** Enable
|
|
1184
|
+
/** Enable additive blending
|
|
1126
1185
|
* @param {boolean} [additive]
|
|
1127
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
1128
1186
|
* @memberof Draw */
|
|
1129
|
-
function
|
|
1187
|
+
function setAdditiveBlendMode(additive=true)
|
|
1130
1188
|
{
|
|
1131
1189
|
glAdditive = additive;
|
|
1132
|
-
|
|
1190
|
+
drawContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
1133
1191
|
}
|
|
1134
1192
|
|
|
1135
1193
|
/** Combines LittleJS canvases onto the main canvas
|
package/src/engineExport.js
CHANGED
|
@@ -85,12 +85,18 @@ export
|
|
|
85
85
|
inputWASDEmulateDirection,
|
|
86
86
|
touchInputEnable,
|
|
87
87
|
touchGamepadEnable,
|
|
88
|
+
touchGamepadPassthrough,
|
|
88
89
|
touchGamepadCenterButtonSize,
|
|
89
90
|
touchGamepadButtonCount,
|
|
91
|
+
touchGamepadLeftStick,
|
|
92
|
+
touchGamepadLeftButtonCount,
|
|
93
|
+
touchGamepadRightStick,
|
|
90
94
|
touchGamepadAnalog,
|
|
95
|
+
touchGamepadFloating,
|
|
91
96
|
touchGamepadSize,
|
|
92
97
|
touchGamepadAlpha,
|
|
93
98
|
touchGamepadDisplayTime,
|
|
99
|
+
touchGamepadVibration,
|
|
94
100
|
vibrateEnable,
|
|
95
101
|
soundEnable,
|
|
96
102
|
soundVolume,
|
|
@@ -133,12 +139,18 @@ export
|
|
|
133
139
|
setGamepadDirectionEmulateStick,
|
|
134
140
|
setInputWASDEmulateDirection,
|
|
135
141
|
setTouchGamepadEnable,
|
|
142
|
+
setTouchGamepadPassthrough,
|
|
136
143
|
setTouchGamepadCenterButtonSize,
|
|
137
144
|
setTouchGamepadButtonCount,
|
|
145
|
+
setTouchGamepadLeftStick,
|
|
146
|
+
setTouchGamepadLeftButtonCount,
|
|
147
|
+
setTouchGamepadRightStick,
|
|
138
148
|
setTouchGamepadAnalog,
|
|
149
|
+
setTouchGamepadFloating,
|
|
139
150
|
setTouchGamepadSize,
|
|
140
151
|
setTouchGamepadAlpha,
|
|
141
152
|
setTouchGamepadDisplayTime,
|
|
153
|
+
setTouchGamepadVibration,
|
|
142
154
|
setVibrateEnable,
|
|
143
155
|
setSoundEnable,
|
|
144
156
|
setSoundVolume,
|
|
@@ -264,7 +276,7 @@ export
|
|
|
264
276
|
drawCanvas2D,
|
|
265
277
|
drawText,
|
|
266
278
|
drawTextScreen,
|
|
267
|
-
|
|
279
|
+
setAdditiveBlendMode,
|
|
268
280
|
combineCanvases,
|
|
269
281
|
engineImageFont,
|
|
270
282
|
ImageFont,
|
|
@@ -272,6 +284,7 @@ export
|
|
|
272
284
|
toggleFullscreen,
|
|
273
285
|
setCursor,
|
|
274
286
|
getCameraSize,
|
|
287
|
+
cameraFit,
|
|
275
288
|
isOnScreen,
|
|
276
289
|
|
|
277
290
|
// WebGL
|
|
@@ -315,10 +328,16 @@ export
|
|
|
315
328
|
mouseWheel,
|
|
316
329
|
mouseInWindow,
|
|
317
330
|
isUsingGamepad,
|
|
331
|
+
lastInputDevice,
|
|
332
|
+
inputMouseMoveThreshold,
|
|
318
333
|
inputPreventDefault,
|
|
319
334
|
gamepadPrimary,
|
|
320
335
|
isTouchDevice,
|
|
321
336
|
setInputPreventDefault,
|
|
337
|
+
setInputMouseMoveThreshold,
|
|
338
|
+
usingMouseInput,
|
|
339
|
+
usingKeyboardInput,
|
|
340
|
+
usingGamepadInput,
|
|
322
341
|
gamepadIsDown,
|
|
323
342
|
gamepadWasPressed,
|
|
324
343
|
gamepadWasReleased,
|