littlejsengine 1.14.28 → 1.15.3
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 +117 -19
- package/dist/littlejs.esm.js +339 -79
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +338 -79
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +302 -57
- package/examples/electron/index.html +2 -2
- package/examples/index.html +1 -0
- package/examples/shorts/base.html +2 -2
- package/examples/shorts/box2dCar.js +1 -1
- package/examples/shorts/music.js +1 -0
- package/examples/shorts/musicPlayer.js +1 -0
- package/examples/shorts/sequencer.js +2 -0
- package/examples/shorts/song.mp3 +0 -0
- package/examples/shorts/sound.js +1 -0
- package/examples/shorts/timers.js +1 -0
- package/examples/shorts/uiSystem.js +1 -0
- package/examples/shorts/video.webm +0 -0
- package/examples/shorts/videoPlayer.js +34 -0
- package/examples/starter/index.html +3 -3
- package/examples/uiSystem/game.js +1 -0
- package/package.json +1 -1
- package/plugins/pluginExport.js +1 -0
- package/plugins/uiSystem.js +204 -3
- package/src/engine.js +5 -5
- package/src/engineAudio.js +0 -1
- package/src/engineDebug.js +36 -22
- package/src/engineDraw.js +57 -33
- package/src/engineInput.js +1 -1
- package/src/engineObject.js +3 -1
- package/src/engineParticles.js +3 -0
- package/src/engineTileLayer.js +1 -1
- package/src/engineUtilities.js +28 -12
package/src/engineDraw.js
CHANGED
|
@@ -250,15 +250,11 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
250
250
|
|
|
251
251
|
const textureInfo = tileInfo && tileInfo.textureInfo;
|
|
252
252
|
const bleedScale = tileInfo ? tileInfo.bleedScale : 0;
|
|
253
|
-
if (useWebGL)
|
|
253
|
+
if (useWebGL && glEnable)
|
|
254
254
|
{
|
|
255
255
|
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
256
256
|
if (screenSpace)
|
|
257
|
-
|
|
258
|
-
// convert to world space
|
|
259
|
-
pos = screenToWorld(pos);
|
|
260
|
-
size = size.scale(1/cameraScale);
|
|
261
|
-
}
|
|
257
|
+
[pos, size, angle] = screenToWorldTransform(pos, size, angle);
|
|
262
258
|
if (textureInfo)
|
|
263
259
|
{
|
|
264
260
|
// calculate uvs and render
|
|
@@ -294,7 +290,7 @@ function drawTile(pos, size=new Vector2(1), tileInfo, color=WHITE,
|
|
|
294
290
|
{
|
|
295
291
|
// normal canvas 2D rendering method (slower)
|
|
296
292
|
++drawCount;
|
|
297
|
-
size = new Vector2(size.x, -size.y); //
|
|
293
|
+
size = new Vector2(size.x, -size.y); // flip upside down sprites
|
|
298
294
|
drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
299
295
|
{
|
|
300
296
|
if (textureInfo)
|
|
@@ -346,7 +342,8 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
346
342
|
ASSERT(isColor(colorTop) && isColor(colorBottom), 'color is invalid');
|
|
347
343
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
348
344
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
349
|
-
|
|
345
|
+
|
|
346
|
+
if (useWebGL && glEnable)
|
|
350
347
|
{
|
|
351
348
|
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
352
349
|
if (screenSpace)
|
|
@@ -354,6 +351,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
354
351
|
// convert to world space
|
|
355
352
|
pos = screenToWorld(pos);
|
|
356
353
|
size = size.scale(1/cameraScale);
|
|
354
|
+
angle += cameraAngle;
|
|
357
355
|
}
|
|
358
356
|
// build 4 corner points for the rectangle
|
|
359
357
|
const points = [], colors = [];
|
|
@@ -409,17 +407,14 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
409
407
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
410
408
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
411
409
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
412
|
-
|
|
410
|
+
|
|
411
|
+
if (useWebGL && glEnable)
|
|
413
412
|
{
|
|
414
413
|
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
415
|
-
let
|
|
414
|
+
let size = vec2(1);
|
|
416
415
|
if (screenSpace)
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
pos = screenToWorld(pos);
|
|
420
|
-
scale = 1/cameraScale;
|
|
421
|
-
}
|
|
422
|
-
glDrawOutlineTransform(points, color.rgbaInt(), width, pos.x, pos.y, scale, scale, angle, wrap);
|
|
416
|
+
[pos, size, angle] = screenToWorldTransform(pos, size, angle);
|
|
417
|
+
glDrawOutlineTransform(points, color.rgbaInt(), width, pos.x, pos.y, size.x, size.y, angle, wrap);
|
|
423
418
|
}
|
|
424
419
|
else
|
|
425
420
|
{
|
|
@@ -515,19 +510,15 @@ function drawPoly(points, color=WHITE, lineWidth=0, lineColor=BLACK, pos=vec2(),
|
|
|
515
510
|
ASSERT(isNumber(angle), 'angle must be a number');
|
|
516
511
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
517
512
|
|
|
518
|
-
if (useWebGL)
|
|
513
|
+
if (useWebGL && glEnable)
|
|
519
514
|
{
|
|
520
515
|
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
521
|
-
let
|
|
516
|
+
let size = vec2(1);
|
|
522
517
|
if (screenSpace)
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
pos = screenToWorld(pos);
|
|
526
|
-
scale = 1/cameraScale;
|
|
527
|
-
}
|
|
528
|
-
glDrawPointsTransform(points, color.rgbaInt(), pos.x, pos.y, scale, scale, angle);
|
|
518
|
+
[pos, size, angle] = screenToWorldTransform(pos, size, angle);
|
|
519
|
+
glDrawPointsTransform(points, color.rgbaInt(), pos.x, pos.y, size.x, size.y, angle);
|
|
529
520
|
if (lineWidth > 0)
|
|
530
|
-
glDrawOutlineTransform(points, lineColor.rgbaInt(), lineWidth, pos.x, pos.y,
|
|
521
|
+
glDrawOutlineTransform(points, lineColor.rgbaInt(), lineWidth, pos.x, pos.y, size.x, size.y, angle);
|
|
531
522
|
}
|
|
532
523
|
else
|
|
533
524
|
{
|
|
@@ -570,7 +561,7 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
|
|
|
570
561
|
ASSERT(lineWidth >= 0 && lineWidth < size.x && lineWidth < size.y, 'invalid lineWidth');
|
|
571
562
|
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
572
563
|
|
|
573
|
-
if (useWebGL)
|
|
564
|
+
if (useWebGL && glEnable)
|
|
574
565
|
{
|
|
575
566
|
// draw as a regular polygon
|
|
576
567
|
const sides = glCircleSides;
|
|
@@ -633,14 +624,10 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
633
624
|
ASSERT(typeof drawFunction === 'function', 'drawFunction must be a function');
|
|
634
625
|
|
|
635
626
|
if (!screenSpace)
|
|
636
|
-
|
|
637
|
-
// transform from world space to screen space
|
|
638
|
-
pos = worldToScreen(pos);
|
|
639
|
-
size = size.scale(cameraScale);
|
|
640
|
-
}
|
|
627
|
+
[pos, size, angle] = worldToScreenTransform(pos, size, angle);
|
|
641
628
|
context.save();
|
|
642
629
|
context.translate(pos.x+.5, pos.y+.5);
|
|
643
|
-
context.rotate(angle
|
|
630
|
+
context.rotate(angle);
|
|
644
631
|
context.scale(mirror ? -size.x : size.x, -size.y);
|
|
645
632
|
drawFunction(context);
|
|
646
633
|
context.restore();
|
|
@@ -666,7 +653,14 @@ function drawCanvas2D(pos, size, angle=0, mirror=false, drawFunction, screenSpac
|
|
|
666
653
|
* @memberof Draw */
|
|
667
654
|
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, fontStyle, maxWidth, angle=0, context=drawContext)
|
|
668
655
|
{
|
|
669
|
-
|
|
656
|
+
// convert to screen space
|
|
657
|
+
pos = worldToScreen(pos);
|
|
658
|
+
size *= cameraScale;
|
|
659
|
+
lineWidth *= cameraScale;
|
|
660
|
+
angle -= cameraAngle;
|
|
661
|
+
angle *= -1;
|
|
662
|
+
|
|
663
|
+
drawTextScreen(text, pos, size, color, lineWidth, lineColor, textAlign, font, fontStyle, maxWidth, angle, context);
|
|
670
664
|
}
|
|
671
665
|
|
|
672
666
|
/** Draw text on overlay canvas in world space
|
|
@@ -825,6 +819,36 @@ function worldToScreenDelta(worldDelta)
|
|
|
825
819
|
return new Vector2(x * cameraScale, y * -cameraScale);
|
|
826
820
|
}
|
|
827
821
|
|
|
822
|
+
/** Convert screen space transform to world space
|
|
823
|
+
* @param {Vector2} screenPos
|
|
824
|
+
* @param {Vector2} screenSize
|
|
825
|
+
* @param {number} [screenAngle]
|
|
826
|
+
* @return {[Vector2, Vector2, number]} - [pos, size, angle]
|
|
827
|
+
* @memberof Draw */
|
|
828
|
+
function screenToWorldTransform(screenPos, screenSize, screenAngle=0)
|
|
829
|
+
{
|
|
830
|
+
return [
|
|
831
|
+
screenToWorld(screenPos),
|
|
832
|
+
screenSize.scale(1/cameraScale),
|
|
833
|
+
screenAngle + cameraAngle
|
|
834
|
+
];
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/** Convert world space transform to screen space
|
|
838
|
+
* @param {Vector2} worldPos
|
|
839
|
+
* @param {Vector2} worldSize
|
|
840
|
+
* @param {number} [worldAngle]
|
|
841
|
+
* @return {[Vector2, Vector2, number]} - [pos, size, angle]
|
|
842
|
+
* @memberof Draw */
|
|
843
|
+
function worldToScreenTransform(worldPos, worldSize, worldAngle=0)
|
|
844
|
+
{
|
|
845
|
+
return [
|
|
846
|
+
worldToScreen(worldPos),
|
|
847
|
+
worldSize.scale(cameraScale),
|
|
848
|
+
worldAngle - cameraAngle
|
|
849
|
+
];
|
|
850
|
+
}
|
|
851
|
+
|
|
828
852
|
/** Get the camera's visible area in world space
|
|
829
853
|
* @return {Vector2}
|
|
830
854
|
* @memberof Draw */
|
package/src/engineInput.js
CHANGED
|
@@ -184,7 +184,7 @@ function inputUpdate()
|
|
|
184
184
|
if (headlessMode) return;
|
|
185
185
|
|
|
186
186
|
// clear input when lost focus (prevent stuck keys)
|
|
187
|
-
if(!(touchInputEnable && isTouchDevice) && !document.hasFocus())
|
|
187
|
+
if (!(touchInputEnable && isTouchDevice) && !document.hasFocus())
|
|
188
188
|
inputClear();
|
|
189
189
|
|
|
190
190
|
// update mouse world space position and delta
|
package/src/engineObject.js
CHANGED
|
@@ -448,7 +448,9 @@ class EngineObject
|
|
|
448
448
|
{
|
|
449
449
|
ASSERT(child.parent === this && this.children.includes(child));
|
|
450
450
|
ASSERT(child instanceof EngineObject, 'child must be an EngineObject');
|
|
451
|
-
this.children.
|
|
451
|
+
const index = this.children.indexOf(child);
|
|
452
|
+
ASSERT(index >= 0, 'child not found in children array');
|
|
453
|
+
index >= 0 && this.children.splice(index, 1);
|
|
452
454
|
child.parent = 0;
|
|
453
455
|
}
|
|
454
456
|
|
package/src/engineParticles.js
CHANGED
|
@@ -160,6 +160,9 @@ class ParticleEmitter extends EngineObject
|
|
|
160
160
|
this.previousPos = this.pos.copy();
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/** Emitters do not have physics */
|
|
164
|
+
updatePhysics() {}
|
|
165
|
+
|
|
163
166
|
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
164
167
|
update()
|
|
165
168
|
{
|
package/src/engineTileLayer.js
CHANGED
|
@@ -553,7 +553,7 @@ class TileCollisionLayer extends TileLayer
|
|
|
553
553
|
// remove from collision layers array and destroy
|
|
554
554
|
const index = tileCollisionLayers.indexOf(this);
|
|
555
555
|
ASSERT(index >= 0, 'tile collision layer not found in array');
|
|
556
|
-
tileCollisionLayers.splice(index, 1);
|
|
556
|
+
index >= 0 && tileCollisionLayers.splice(index, 1);
|
|
557
557
|
super.destroy();
|
|
558
558
|
}
|
|
559
559
|
|
package/src/engineUtilities.js
CHANGED
|
@@ -148,7 +148,7 @@ function percentLerp(value, percentA, percentB, lerpA, lerpB)
|
|
|
148
148
|
* @param {number} valueA
|
|
149
149
|
* @param {number} valueB
|
|
150
150
|
* @param {number} [wrapSize]
|
|
151
|
-
* @
|
|
151
|
+
* @return {number}
|
|
152
152
|
* @memberof Utilities */
|
|
153
153
|
function distanceWrap(valueA, valueB, wrapSize=1)
|
|
154
154
|
{ const d = (valueA - valueB) % wrapSize; return d*2 % wrapSize - d; }
|
|
@@ -158,7 +158,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
|
|
|
158
158
|
* @param {number} valueB
|
|
159
159
|
* @param {number} percent
|
|
160
160
|
* @param {number} [wrapSize]
|
|
161
|
-
* @
|
|
161
|
+
* @return {number}
|
|
162
162
|
* @memberof Utilities */
|
|
163
163
|
function lerpWrap(valueA, valueB, percent, wrapSize=1)
|
|
164
164
|
{
|
|
@@ -170,7 +170,7 @@ function lerpWrap(valueA, valueB, percent, wrapSize=1)
|
|
|
170
170
|
/** Returns signed wrapped distance between the two angles passed in
|
|
171
171
|
* @param {number} angleA
|
|
172
172
|
* @param {number} angleB
|
|
173
|
-
* @
|
|
173
|
+
* @return {number}
|
|
174
174
|
* @memberof Utilities */
|
|
175
175
|
function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
|
|
176
176
|
|
|
@@ -178,7 +178,7 @@ function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*P
|
|
|
178
178
|
* @param {number} angleA
|
|
179
179
|
* @param {number} angleB
|
|
180
180
|
* @param {number} percent
|
|
181
|
-
* @
|
|
181
|
+
* @return {number}
|
|
182
182
|
* @memberof Utilities */
|
|
183
183
|
function lerpAngle(angleA, angleB, percent) { return lerpWrap(angleA, angleB, percent, 2*PI); }
|
|
184
184
|
|
|
@@ -1147,11 +1147,14 @@ const MAGENTA = debugProtectConstant(rgb(1,0,1));
|
|
|
1147
1147
|
class Timer
|
|
1148
1148
|
{
|
|
1149
1149
|
/** Create a timer object set time passed in
|
|
1150
|
-
* @param {number} [timeLeft] - How much time left before the timer
|
|
1151
|
-
|
|
1150
|
+
* @param {number} [timeLeft] - How much time left before the timer
|
|
1151
|
+
* @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */
|
|
1152
|
+
constructor(timeLeft, useRealTime=false)
|
|
1152
1153
|
{
|
|
1153
1154
|
ASSERT(timeLeft === undefined || isNumber(timeLeft), 'Constructed Timer is invalid.', timeLeft);
|
|
1154
|
-
this.
|
|
1155
|
+
this.useRealTime = useRealTime;
|
|
1156
|
+
const globalTime = this.getGlobalTime();
|
|
1157
|
+
this.time = timeLeft === undefined ? undefined : globalTime + timeLeft;
|
|
1155
1158
|
this.setTime = timeLeft;
|
|
1156
1159
|
}
|
|
1157
1160
|
|
|
@@ -1160,10 +1163,19 @@ class Timer
|
|
|
1160
1163
|
set(timeLeft=0)
|
|
1161
1164
|
{
|
|
1162
1165
|
ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft);
|
|
1163
|
-
|
|
1166
|
+
const globalTime = this.getGlobalTime();
|
|
1167
|
+
this.time = globalTime + timeLeft;
|
|
1164
1168
|
this.setTime = timeLeft;
|
|
1165
1169
|
}
|
|
1166
1170
|
|
|
1171
|
+
/** Set if the timer should keep running even when the game is paused
|
|
1172
|
+
* @param {boolean} [useRealTime] */
|
|
1173
|
+
setUseRealTime(useRealTime=true)
|
|
1174
|
+
{
|
|
1175
|
+
ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.');
|
|
1176
|
+
this.useRealTime = useRealTime;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1167
1179
|
/** Unset the timer */
|
|
1168
1180
|
unset() { this.time = undefined; }
|
|
1169
1181
|
|
|
@@ -1173,24 +1185,28 @@ class Timer
|
|
|
1173
1185
|
|
|
1174
1186
|
/** Returns true if set and has not elapsed
|
|
1175
1187
|
* @return {boolean} */
|
|
1176
|
-
active() { return
|
|
1188
|
+
active() { return this.getGlobalTime() < this.time; }
|
|
1177
1189
|
|
|
1178
1190
|
/** Returns true if set and elapsed
|
|
1179
1191
|
* @return {boolean} */
|
|
1180
|
-
elapsed() { return
|
|
1192
|
+
elapsed() { return this.getGlobalTime() >= this.time; }
|
|
1181
1193
|
|
|
1182
1194
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
1183
1195
|
* @return {number} */
|
|
1184
|
-
get() { return this.isSet()?
|
|
1196
|
+
get() { return this.isSet()? this.getGlobalTime() - this.time : 0; }
|
|
1185
1197
|
|
|
1186
1198
|
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
1187
1199
|
* @return {number} */
|
|
1188
|
-
getPercent() { return this.isSet()? 1-percent(this.time -
|
|
1200
|
+
getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; }
|
|
1189
1201
|
|
|
1190
1202
|
/** Get the time this timer was set to, returns 0 if not set
|
|
1191
1203
|
* @return {number} */
|
|
1192
1204
|
getSetTime() { return this.isSet() ? this.setTime : 0; }
|
|
1193
1205
|
|
|
1206
|
+
/** Get the current global time this timer is based on
|
|
1207
|
+
* @return {number} */
|
|
1208
|
+
getGlobalTime() { return this.useRealTime ? timeReal : time; }
|
|
1209
|
+
|
|
1194
1210
|
/** Returns this timer expressed as a string
|
|
1195
1211
|
* @return {string} */
|
|
1196
1212
|
toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }
|