littlejsengine 1.18.8 → 1.18.12
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 +2 -4
- package/dist/littlejs.d.ts +67 -3
- package/dist/littlejs.esm.js +262 -23
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +257 -23
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +253 -21
- package/package.json +1 -1
- package/plugins/medalSystem.js +2 -1
- package/plugins/newgrounds.js +7 -0
- package/plugins/pathFinder.js +35 -6
- package/plugins/tweenSystem.js +8 -0
- package/src/engine.js +3 -2
- package/src/engineAudio.js +3 -2
- package/src/engineDebug.js +4 -2
- package/src/engineDraw.js +89 -9
- package/src/engineExport.js +5 -0
- package/src/engineUtilities.js +43 -0
- package/src/engineWebGL.js +63 -1
package/src/engineAudio.js
CHANGED
|
@@ -423,14 +423,15 @@ class SoundInstance
|
|
|
423
423
|
|
|
424
424
|
/** Speak text with passed in settings
|
|
425
425
|
* @param {string} text - The text to speak
|
|
426
|
-
* @param {string} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
427
426
|
* @param {number} [volume] - How much to scale volume by
|
|
428
427
|
* @param {number} [rate] - How quickly to speak
|
|
429
428
|
* @param {number} [pitch] - How much to change the pitch by
|
|
429
|
+
* @param {string} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
430
430
|
* @return {SpeechSynthesisUtterance} - The utterance that was spoken
|
|
431
431
|
* @memberof Audio */
|
|
432
|
-
function speak(text,
|
|
432
|
+
function speak(text, volume=1, rate=1, pitch=1, language='')
|
|
433
433
|
{
|
|
434
|
+
ASSERT(typeof volume !== 'string', 'speak() signature changed: language is now the last parameter, after pitch');
|
|
434
435
|
if (!soundEnable || headlessMode) return;
|
|
435
436
|
if (!speechSynthesis) return;
|
|
436
437
|
|
package/src/engineDebug.js
CHANGED
|
@@ -502,7 +502,8 @@ function debugRender()
|
|
|
502
502
|
debugContext.fillText('FPS: ' + averageFPS.toFixed(1) + (glEnable?' WebGL':' Canvas2D'),
|
|
503
503
|
x, y += h);
|
|
504
504
|
debugContext.fillText('Objects: ' + engineObjects.length, x, y += h);
|
|
505
|
-
debugContext.fillText('Draw
|
|
505
|
+
debugContext.fillText('Draw Calls: ' + drawCount, x, y += h);
|
|
506
|
+
debugContext.fillText('Primitives: ' + primitiveCount, x, y += h);
|
|
506
507
|
debugContext.fillText('---------', x, y += h);
|
|
507
508
|
debugContext.fillStyle = '#f00';
|
|
508
509
|
debugContext.fillText('ESC: Debug Overlay', x, y += h);
|
|
@@ -573,7 +574,8 @@ function debugRenderPost()
|
|
|
573
574
|
mainContext.font = '1em monospace';
|
|
574
575
|
mainContext.fillStyle = '#000';
|
|
575
576
|
const text = engineName + ' v' + engineVersion + ' / '
|
|
576
|
-
+ drawCount + ' / ' +
|
|
577
|
+
+ drawCount + ' / ' + primitiveCount + ' / '
|
|
578
|
+
+ engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
577
579
|
+ (glEnable ? ' GL' : ' 2D') ;
|
|
578
580
|
mainContext.fillText(text, mainCanvas.width-3, 3);
|
|
579
581
|
mainContext.fillStyle = '#fff';
|
package/src/engineDraw.js
CHANGED
|
@@ -72,6 +72,12 @@ let textureInfos = [];
|
|
|
72
72
|
* @memberof Draw */
|
|
73
73
|
let drawCount;
|
|
74
74
|
|
|
75
|
+
/** Keeps track of how many primitives were drawn each frame for debugging
|
|
76
|
+
* A single draw call can render many primitives (e.g. a WebGL sprite batch).
|
|
77
|
+
* @type {number}
|
|
78
|
+
* @memberof Draw */
|
|
79
|
+
let primitiveCount;
|
|
80
|
+
|
|
75
81
|
// internal predicates for tint short-circuiting in canvas2D draw paths
|
|
76
82
|
// isWhite ignores alpha because alpha is applied via globalAlpha, not multiply
|
|
77
83
|
// isBlack includes alpha so additive colors that only contribute alpha are not skipped
|
|
@@ -312,19 +318,19 @@ function drawTile(pos, size=vec2(1), tileInfo, color=WHITE,
|
|
|
312
318
|
}
|
|
313
319
|
else
|
|
314
320
|
{
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
// color.add(additiveColor) on line ~337.
|
|
321
|
+
// untextured: glDrawUntextured picks the optimal path (poly
|
|
322
|
+
// tristrip if already in poly mode, otherwise instanced with
|
|
323
|
+
// uvs/rgba zeroed). Color+additive are folded together to match
|
|
324
|
+
// the Canvas2D path's color.add(additiveColor) on line ~337.
|
|
319
325
|
const combined = additiveColor ? color.add(additiveColor) : color;
|
|
320
|
-
|
|
321
|
-
0, combined.rgbaInt());
|
|
326
|
+
glDrawUntextured(pos.x, pos.y, size.x, size.y, angle, combined.rgbaInt());
|
|
322
327
|
}
|
|
323
328
|
}
|
|
324
329
|
else
|
|
325
330
|
{
|
|
326
331
|
// normal canvas 2D rendering method (slower)
|
|
327
332
|
++drawCount;
|
|
333
|
+
++primitiveCount;
|
|
328
334
|
size = new Vector2(size.x, -size.y); // flip upside down sprites
|
|
329
335
|
drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
330
336
|
{
|
|
@@ -364,13 +370,13 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
364
370
|
* @param {Vector2} pos
|
|
365
371
|
* @param {Vector2} [size=vec2(1)]
|
|
366
372
|
* @param {Color} [colorTop=WHITE]
|
|
367
|
-
* @param {Color} [colorBottom=
|
|
373
|
+
* @param {Color} [colorBottom=CLEAR_WHITE]
|
|
368
374
|
* @param {number} [angle]
|
|
369
375
|
* @param {boolean} [useWebGL=glEnable]
|
|
370
376
|
* @param {boolean} [screenSpace]
|
|
371
377
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
372
378
|
* @memberof Draw */
|
|
373
|
-
function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=
|
|
379
|
+
function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=CLEAR_WHITE, angle=0, useWebGL=glEnable, screenSpace=false, context)
|
|
374
380
|
{
|
|
375
381
|
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
376
382
|
ASSERT(isVector2(size), 'size must be a vec2');
|
|
@@ -410,6 +416,7 @@ function drawRectGradient(pos, size, colorTop=WHITE, colorBottom=BLACK, angle=0,
|
|
|
410
416
|
{
|
|
411
417
|
// normal canvas 2D rendering method (slower)
|
|
412
418
|
++drawCount;
|
|
419
|
+
++primitiveCount;
|
|
413
420
|
size = new Vector2(size.x, -size.y); // fix upside down sprites
|
|
414
421
|
drawCanvas2D(pos, size, angle, false, (context)=>
|
|
415
422
|
{
|
|
@@ -472,8 +479,9 @@ function drawTextureWrapped(pos, size, wrapCount, texture=0, color=WHITE,
|
|
|
472
479
|
return;
|
|
473
480
|
}
|
|
474
481
|
|
|
475
|
-
// Canvas2D path — increment
|
|
482
|
+
// Canvas2D path — increment counts here (WebGL counts via glFlush)
|
|
476
483
|
++drawCount;
|
|
484
|
+
++primitiveCount;
|
|
477
485
|
|
|
478
486
|
if (!screenSpace)
|
|
479
487
|
{
|
|
@@ -547,6 +555,7 @@ function drawLineList(points, width=.1, color, wrap=false, pos=vec2(), angle=0,
|
|
|
547
555
|
{
|
|
548
556
|
// normal canvas 2D rendering method (slower)
|
|
549
557
|
++drawCount;
|
|
558
|
+
++primitiveCount;
|
|
550
559
|
drawCanvas2D(pos, vec2(1), angle, false, (context)=>
|
|
551
560
|
{
|
|
552
561
|
context.strokeStyle = color.toString();
|
|
@@ -727,6 +736,77 @@ function drawCircle(pos, size=1, color=WHITE, lineWidth=0, lineColor=BLACK, useW
|
|
|
727
736
|
drawEllipse(pos, vec2(size), color, 0, lineWidth, lineColor, useWebGL, screenSpace, context);
|
|
728
737
|
}
|
|
729
738
|
|
|
739
|
+
/** Draw a circle filled with a radial gradient from the center to the rim
|
|
740
|
+
* - Best when batched with other untextured polys
|
|
741
|
+
* - If drawing mostly textured sprites, bake the gradient into a texture and use drawTile instead
|
|
742
|
+
* - Stacking gradients at the exact same position may show a faint vertical artifact
|
|
743
|
+
* @param {Vector2} pos
|
|
744
|
+
* @param {number} [size=1] - Diameter
|
|
745
|
+
* @param {Color} [colorInner=WHITE]
|
|
746
|
+
* @param {Color} [colorOuter=CLEAR_WHITE]
|
|
747
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
748
|
+
* @param {boolean} [screenSpace]
|
|
749
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
|
|
750
|
+
* @memberof Draw */
|
|
751
|
+
let drawCircleGradientOffset = 0;
|
|
752
|
+
function drawCircleGradient(pos, size=1, colorInner=WHITE, colorOuter=CLEAR_WHITE, useWebGL=glEnable, screenSpace=false, context)
|
|
753
|
+
{
|
|
754
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
755
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
756
|
+
ASSERT(isColor(colorInner) && isColor(colorOuter), 'color is invalid');
|
|
757
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
758
|
+
|
|
759
|
+
if (headlessMode) return;
|
|
760
|
+
|
|
761
|
+
if (useWebGL && glEnable)
|
|
762
|
+
{
|
|
763
|
+
ASSERT(!!glContext, 'WebGL is not enabled!');
|
|
764
|
+
if (screenSpace)
|
|
765
|
+
{
|
|
766
|
+
// convert to world space
|
|
767
|
+
pos = screenToWorld(pos);
|
|
768
|
+
size /= cameraScale;
|
|
769
|
+
}
|
|
770
|
+
// fan as tristrip; rotate the boundary vertex by one slice per call
|
|
771
|
+
// so back-to-back gradients at the same position have their hole
|
|
772
|
+
// (from gpu edge-rule on the boundary line-degen) at different rim
|
|
773
|
+
// verts and don't visibly stack
|
|
774
|
+
const sides = glCircleSides;
|
|
775
|
+
const radius = size/2;
|
|
776
|
+
const innerInt = colorInner.rgbaInt();
|
|
777
|
+
const outerInt = colorOuter.rgbaInt();
|
|
778
|
+
const offset = drawCircleGradientOffset++;
|
|
779
|
+
const startA = (offset%sides)/sides*PI*2;
|
|
780
|
+
const points = [vec2(pos.x + sin(startA)*radius, pos.y + cos(startA)*radius)];
|
|
781
|
+
const colors = [outerInt];
|
|
782
|
+
for (let i=sides; i--;)
|
|
783
|
+
{
|
|
784
|
+
const a = ((i+offset)%sides)/sides*PI*2;
|
|
785
|
+
points.push(pos);
|
|
786
|
+
colors.push(innerInt);
|
|
787
|
+
points.push(vec2(pos.x + sin(a)*radius, pos.y + cos(a)*radius));
|
|
788
|
+
colors.push(outerInt);
|
|
789
|
+
}
|
|
790
|
+
glDrawColoredPoints(points, colors);
|
|
791
|
+
}
|
|
792
|
+
else
|
|
793
|
+
{
|
|
794
|
+
// normal canvas 2D rendering method (slower)
|
|
795
|
+
++drawCount;
|
|
796
|
+
++primitiveCount;
|
|
797
|
+
drawCanvas2D(pos, vec2(size), 0, false, (context)=>
|
|
798
|
+
{
|
|
799
|
+
const gradient = context.createRadialGradient(0, 0, 0, 0, 0, .5);
|
|
800
|
+
gradient.addColorStop(0, colorInner.toString());
|
|
801
|
+
gradient.addColorStop(1, colorOuter.toString());
|
|
802
|
+
context.fillStyle = gradient;
|
|
803
|
+
context.beginPath();
|
|
804
|
+
context.ellipse(0, 0, .5, .5, 0, 0, 9);
|
|
805
|
+
context.fill();
|
|
806
|
+
}, screenSpace, context);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
730
810
|
/**
|
|
731
811
|
* @callback Canvas2DDrawFunction - A function that draws to a 2D canvas context
|
|
732
812
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
package/src/engineExport.js
CHANGED
|
@@ -187,6 +187,8 @@ export
|
|
|
187
187
|
shareURL,
|
|
188
188
|
readSaveData,
|
|
189
189
|
writeSaveData,
|
|
190
|
+
noise1D,
|
|
191
|
+
noise2D,
|
|
190
192
|
|
|
191
193
|
// Random
|
|
192
194
|
rand,
|
|
@@ -240,6 +242,7 @@ export
|
|
|
240
242
|
mainCanvasSize,
|
|
241
243
|
textureInfos,
|
|
242
244
|
drawCount,
|
|
245
|
+
primitiveCount,
|
|
243
246
|
screenToWorld,
|
|
244
247
|
worldToScreen,
|
|
245
248
|
screenToWorldDelta,
|
|
@@ -255,6 +258,7 @@ export
|
|
|
255
258
|
drawRegularPoly,
|
|
256
259
|
drawEllipse,
|
|
257
260
|
drawCircle,
|
|
261
|
+
drawCircleGradient,
|
|
258
262
|
drawCanvas2D,
|
|
259
263
|
drawText,
|
|
260
264
|
drawTextScreen,
|
|
@@ -284,6 +288,7 @@ export
|
|
|
284
288
|
glCopyToContext,
|
|
285
289
|
glSetAntialias,
|
|
286
290
|
glDraw,
|
|
291
|
+
glDrawUntextured,
|
|
287
292
|
glDrawPointsTransform,
|
|
288
293
|
glDrawOutlineTransform,
|
|
289
294
|
glDrawPoints,
|
package/src/engineUtilities.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* - File saving (text, canvas, data URLs)
|
|
7
7
|
* - Native share dialog support
|
|
8
8
|
* - Local storage save data management
|
|
9
|
+
* - Gradient noise (1D and 2D)
|
|
9
10
|
* @namespace Utilities
|
|
10
11
|
*/
|
|
11
12
|
|
|
@@ -209,4 +210,46 @@ function writeSaveData(saveName, saveData)
|
|
|
209
210
|
{
|
|
210
211
|
ASSERT(isStringLike(saveName), 'saveData requires saveName string');
|
|
211
212
|
localStorage[saveName] = JSON.stringify(saveData);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
216
|
+
|
|
217
|
+
// Deterministic well-distributed hash of an integer lattice index to [0, 1).
|
|
218
|
+
// Murmur3 finalizer — adjacent integers produce uncorrelated outputs.
|
|
219
|
+
function noiseHash(i)
|
|
220
|
+
{
|
|
221
|
+
let h = (i | 0) ^ 0x9e3779b9;
|
|
222
|
+
h = Math.imul(h ^ (h >>> 16), 0x85ebca6b);
|
|
223
|
+
h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35);
|
|
224
|
+
h ^= h >>> 16;
|
|
225
|
+
return (h >>> 0) / 2**32;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** 1D gradient noise — returns a smooth value in [0, 1] for any real x.
|
|
229
|
+
* Integer inputs land on deterministic lattice values; non-integer inputs
|
|
230
|
+
* are interpolated with smoothStep for C1 continuity.
|
|
231
|
+
* @param {number} x
|
|
232
|
+
* @return {number}
|
|
233
|
+
* @memberof Utilities */
|
|
234
|
+
function noise1D(x)
|
|
235
|
+
{
|
|
236
|
+
const i = floor(x);
|
|
237
|
+
return lerp(noiseHash(i), noiseHash(i + 1), smoothStep(x - i));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** 2D gradient noise — returns a smooth value in [0, 1] for any real (x, y).
|
|
241
|
+
* @param {number} x
|
|
242
|
+
* @param {number} y
|
|
243
|
+
* @return {number}
|
|
244
|
+
* @memberof Utilities */
|
|
245
|
+
function noise2D(x, y)
|
|
246
|
+
{
|
|
247
|
+
const ix = floor(x), iy = floor(y);
|
|
248
|
+
const fx = smoothStep(x - ix), fy = smoothStep(y - iy);
|
|
249
|
+
// large prime decorrelates neighboring rows
|
|
250
|
+
const h = (a, b) => noiseHash(a + b * 374761393);
|
|
251
|
+
return lerp(
|
|
252
|
+
lerp(h(ix, iy ), h(ix + 1, iy ), fx),
|
|
253
|
+
lerp(h(ix, iy + 1), h(ix + 1, iy + 1), fx),
|
|
254
|
+
fy);
|
|
212
255
|
}
|
package/src/engineWebGL.js
CHANGED
|
@@ -505,7 +505,8 @@ function glFlush()
|
|
|
505
505
|
glContext.drawArrays(glContext.TRIANGLE_STRIP, 0, glBatchCount);
|
|
506
506
|
else
|
|
507
507
|
glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glBatchCount);
|
|
508
|
-
drawCount
|
|
508
|
+
++drawCount;
|
|
509
|
+
primitiveCount += glBatchCount;
|
|
509
510
|
glBatchCount = 0;
|
|
510
511
|
}
|
|
511
512
|
glBatchAdditive = glAdditive;
|
|
@@ -566,6 +567,67 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
|
|
|
566
567
|
glPositionData[offset++] = angle;
|
|
567
568
|
}
|
|
568
569
|
|
|
570
|
+
/** Add an untextured rect to the gl draw list
|
|
571
|
+
* Picks the optimal path: if already in poly mode, emits a tristrip rect
|
|
572
|
+
* so it batches with surrounding polys; otherwise uses the instanced path
|
|
573
|
+
* with uvs and rgba zeroed so the color falls through the additive slot.
|
|
574
|
+
* @param {number} x
|
|
575
|
+
* @param {number} y
|
|
576
|
+
* @param {number} sizeX
|
|
577
|
+
* @param {number} sizeY
|
|
578
|
+
* @param {number} angle
|
|
579
|
+
* @param {number} rgba - color as 32-bit integer
|
|
580
|
+
* @memberof WebGL */
|
|
581
|
+
function glDrawUntextured(x, y, sizeX, sizeY, angle, rgba)
|
|
582
|
+
{
|
|
583
|
+
if (glPolyMode)
|
|
584
|
+
{
|
|
585
|
+
// batch with surrounding polys as a 4-vertex tristrip rect
|
|
586
|
+
const vertCount = 6; // 4 corners + 2 degenerate verts
|
|
587
|
+
if (glBatchCount+vertCount >= gl_MAX_POLY_VERTEXES || glBatchAdditive !== glAdditive)
|
|
588
|
+
glFlush();
|
|
589
|
+
|
|
590
|
+
// compute rotated corners in world space (matches glDrawPointsTransform rotation)
|
|
591
|
+
const hx = sizeX*.5, hy = sizeY*.5;
|
|
592
|
+
const c = cos(angle), s = sin(angle);
|
|
593
|
+
const chx = c*hx, shx = s*hx, chy = c*hy, shy = s*hy;
|
|
594
|
+
const x0 = x - chx - shy, y0 = y + shx - chy; // (-hx,-hy)
|
|
595
|
+
const x1 = x + chx - shy, y1 = y - shx - chy; // ( hx,-hy)
|
|
596
|
+
const x2 = x - chx + shy, y2 = y + shx + chy; // (-hx, hy)
|
|
597
|
+
const x3 = x + chx + shy, y3 = y - shx + chy; // ( hx, hy)
|
|
598
|
+
|
|
599
|
+
// write tristrip with leading/trailing degenerate verts
|
|
600
|
+
let offset = glBatchCount * gl_INDICES_PER_POLY_VERTEX;
|
|
601
|
+
glPositionData[offset++] = x0; glPositionData[offset++] = y0; glColorData[offset++] = rgba;
|
|
602
|
+
glPositionData[offset++] = x0; glPositionData[offset++] = y0; glColorData[offset++] = rgba;
|
|
603
|
+
glPositionData[offset++] = x1; glPositionData[offset++] = y1; glColorData[offset++] = rgba;
|
|
604
|
+
glPositionData[offset++] = x2; glPositionData[offset++] = y2; glColorData[offset++] = rgba;
|
|
605
|
+
glPositionData[offset++] = x3; glPositionData[offset++] = y3; glColorData[offset++] = rgba;
|
|
606
|
+
glPositionData[offset++] = x3; glPositionData[offset++] = y3; glColorData[offset++] = rgba;
|
|
607
|
+
glBatchCount += vertCount;
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// instanced path: zero uvs and rgba so the texture contribution is killed,
|
|
612
|
+
// then carry the real color in the additive slot
|
|
613
|
+
if (glBatchCount >= gl_MAX_INSTANCES || glBatchAdditive !== glAdditive)
|
|
614
|
+
glFlush();
|
|
615
|
+
glSetInstancedMode();
|
|
616
|
+
|
|
617
|
+
let offset = glBatchCount++ * gl_INDICES_PER_INSTANCE;
|
|
618
|
+
glPositionData[offset++] = x;
|
|
619
|
+
glPositionData[offset++] = y;
|
|
620
|
+
glPositionData[offset++] = sizeX;
|
|
621
|
+
glPositionData[offset++] = sizeY;
|
|
622
|
+
glPositionData[offset++] = 0;
|
|
623
|
+
glPositionData[offset++] = 0;
|
|
624
|
+
glPositionData[offset++] = 0;
|
|
625
|
+
glPositionData[offset++] = 0;
|
|
626
|
+
glColorData[offset++] = 0;
|
|
627
|
+
glColorData[offset++] = rgba;
|
|
628
|
+
glPositionData[offset++] = angle;
|
|
629
|
+
}
|
|
630
|
+
|
|
569
631
|
/** Transform and add a polygon to the gl draw list
|
|
570
632
|
* @param {Array<Vector2>} points - Array of Vector2 points
|
|
571
633
|
* @param {number} rgba - Color of the polygon as a 32-bit integer
|