littlejsengine 1.6.6 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -53
- package/build/littlejs.d.ts +154 -136
- package/build/littlejs.esm.js +461 -305
- package/build/littlejs.esm.min.js +1 -1
- package/build/littlejs.js +452 -291
- package/build/littlejs.min.js +1 -1
- package/build/littlejs.release.js +2317 -2168
- package/examples/breakout/game.js +5 -1
- package/examples/breakout/index.html +5 -5
- package/examples/breakoutTutorial/README.md +6 -6
- package/examples/breakoutTutorial/game.js +3 -0
- package/examples/breakoutTutorial/index.html +3 -3
- package/examples/electron/build.js +105 -0
- package/examples/electron/game.js +4 -2
- package/examples/electron/index.html +13 -2
- package/examples/electron/package.json +5 -3
- package/examples/empty/game.js +3 -1
- package/examples/empty/index.html +1 -1
- package/examples/js13k/build.bat +2 -0
- package/examples/js13k/build.js +110 -0
- package/examples/js13k/game.js +109 -0
- package/examples/js13k/index.html +18 -0
- package/examples/js13k/tiles.png +0 -0
- package/examples/module/game.js +9 -3
- package/examples/module/index.html +2 -2
- package/examples/particles/index.html +7 -13
- package/examples/platformer/game.js +5 -2
- package/examples/platformer/gameEffects.js +12 -12
- package/examples/platformer/gamePlayer.js +2 -2
- package/examples/platformer/index.html +8 -8
- package/examples/puzzle/game.js +5 -3
- package/examples/puzzle/index.html +4 -4
- package/examples/starter/build.bat +2 -78
- package/examples/starter/build.js +109 -0
- package/examples/starter/game.js +4 -1
- package/examples/starter/index.html +15 -18
- package/examples/stress/index.html +5 -7
- package/examples/typescript/build.bat +2 -14
- package/examples/typescript/build.js +31 -0
- package/examples/typescript/game.js +94 -89
- package/examples/typescript/game.ts +9 -3
- package/examples/typescript/index.html +2 -2
- package/package.json +2 -2
- package/src/engine.js +3 -3
- package/src/engineAudio.js +44 -16
- package/src/engineBuild.bat +2 -132
- package/src/engineBuild.js +156 -0
- package/src/engineDebug.js +25 -15
- package/src/engineDraw.js +62 -53
- package/src/engineExport.js +7 -12
- package/src/engineInput.js +27 -35
- package/src/engineObject.js +15 -13
- package/src/engineParticles.js +6 -6
- package/src/engineRelease.js +1 -3
- package/src/engineSettings.js +1 -0
- package/src/engineTileLayer.js +31 -18
- package/src/engineUtilities.js +161 -93
- package/src/engineWebGL.js +63 -27
- package/examples/electron/build.bat +0 -52
package/build/littlejs.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
// LittleJS - MIT License - Copyright 2021 Frank Force
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* LittleJS Debug System
|
|
3
|
-
* - Press
|
|
5
|
+
* - Press Esc to show debug overlay with mouse pick
|
|
4
6
|
* - Number keys toggle debug functions
|
|
5
7
|
* - +/- apply time scale
|
|
6
8
|
* - Debug primitive rendering
|
|
7
|
-
* - Save a 2d canvas as
|
|
9
|
+
* - Save a 2d canvas as a png image
|
|
8
10
|
* @namespace Debug
|
|
9
11
|
*/
|
|
10
12
|
|
|
@@ -34,12 +36,6 @@ const debugPointSize = .5;
|
|
|
34
36
|
* @memberof Debug */
|
|
35
37
|
let showWatermark = 1;
|
|
36
38
|
|
|
37
|
-
/** True if god mode is enabled, handle this however you want
|
|
38
|
-
* @type {Boolean}
|
|
39
|
-
* @default
|
|
40
|
-
* @memberof Debug */
|
|
41
|
-
let godMode = 0;
|
|
42
|
-
|
|
43
39
|
/** Key code used to toggle debug mode, Esc by default
|
|
44
40
|
* @type {Boolean}
|
|
45
41
|
* @default
|
|
@@ -144,11 +140,27 @@ function debugClear() { debugPrimitives = []; }
|
|
|
144
140
|
/** Save a canvas to disk
|
|
145
141
|
* @param {HTMLCanvasElement} canvas
|
|
146
142
|
* @param {String} [filename]
|
|
143
|
+
* @param {String} [type='image/png']
|
|
144
|
+
* @memberof Debug */
|
|
145
|
+
function debugSaveCanvas(canvas, filename=engineName, type='image/png')
|
|
146
|
+
{ debugSaveDataURL(canvas.toDataURL(type), filename); }
|
|
147
|
+
|
|
148
|
+
/** Save a text file to disk
|
|
149
|
+
* @param {String} text
|
|
150
|
+
* @param {String} [filename]
|
|
151
|
+
* @param {String} [type='text/plain']
|
|
147
152
|
* @memberof Debug */
|
|
148
|
-
function
|
|
153
|
+
function debugSaveText(text, filename=engineName, type='text/plain')
|
|
154
|
+
{ debugSaveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); }
|
|
155
|
+
|
|
156
|
+
/** Save a data url to disk
|
|
157
|
+
* @param {String} dataURL
|
|
158
|
+
* @param {String} filename
|
|
159
|
+
* @memberof Debug */
|
|
160
|
+
function debugSaveDataURL(dataURL, filename)
|
|
149
161
|
{
|
|
150
|
-
downloadLink.download =
|
|
151
|
-
downloadLink.href =
|
|
162
|
+
downloadLink.download = filename;
|
|
163
|
+
downloadLink.href = dataURL;
|
|
152
164
|
downloadLink.click();
|
|
153
165
|
}
|
|
154
166
|
|
|
@@ -180,7 +192,7 @@ function debugUpdate()
|
|
|
180
192
|
if (keyWasPressed(51)) // 3
|
|
181
193
|
debugGamepads = !debugGamepads;
|
|
182
194
|
if (keyWasPressed(52)) // 4
|
|
183
|
-
|
|
195
|
+
debugRaycast = !debugRaycast;
|
|
184
196
|
if (keyWasPressed(53)) // 5
|
|
185
197
|
debugTakeScreenshot = 1;
|
|
186
198
|
//if (keyWasPressed(54)) // 6
|
|
@@ -359,8 +371,8 @@ function debugRender()
|
|
|
359
371
|
overlayContext.fillText('2: Debug Particles', x, y += h);
|
|
360
372
|
overlayContext.fillStyle = debugGamepads ? '#f00' : '#fff';
|
|
361
373
|
overlayContext.fillText('3: Debug Gamepads', x, y += h);
|
|
362
|
-
overlayContext.fillStyle =
|
|
363
|
-
overlayContext.fillText('4:
|
|
374
|
+
overlayContext.fillStyle = debugRaycast ? '#f00' : '#fff';
|
|
375
|
+
overlayContext.fillText('4: Debug Raycasts', x, y += h);
|
|
364
376
|
overlayContext.fillStyle = '#fff';
|
|
365
377
|
overlayContext.fillText('5: Save Screenshot', x, y += h);
|
|
366
378
|
|
|
@@ -385,19 +397,20 @@ function debugRender()
|
|
|
385
397
|
{
|
|
386
398
|
overlayContext.fillText(debugPhysics ? 'Debug Physics' : '', x, y += h);
|
|
387
399
|
overlayContext.fillText(debugParticles ? 'Debug Particles' : '', x, y += h);
|
|
388
|
-
overlayContext.fillText(
|
|
400
|
+
overlayContext.fillText(debugRaycast ? 'Debug Raycasts' : '', x, y += h);
|
|
389
401
|
overlayContext.fillText(debugGamepads ? 'Debug Gamepads' : '', x, y += h);
|
|
390
402
|
}
|
|
391
403
|
|
|
392
404
|
overlayContext.restore();
|
|
393
405
|
}
|
|
394
|
-
}
|
|
406
|
+
}
|
|
395
407
|
/**
|
|
396
408
|
* LittleJS Utility Classes and Functions
|
|
397
409
|
* - General purpose math library
|
|
398
410
|
* - Vector2 - fast, simple, easy 2D vector class
|
|
399
411
|
* - Color - holds a rgba color with some math functions
|
|
400
412
|
* - Timer - tracks time automatically
|
|
413
|
+
* - RandomGenerator - seeded random number generator
|
|
401
414
|
* @namespace Utilities
|
|
402
415
|
*/
|
|
403
416
|
|
|
@@ -413,34 +426,34 @@ const PI = Math.PI;
|
|
|
413
426
|
* @param {Number} value
|
|
414
427
|
* @return {Number}
|
|
415
428
|
* @memberof Utilities */
|
|
416
|
-
function abs(
|
|
429
|
+
function abs(value) { return Math.abs(value); }
|
|
417
430
|
|
|
418
431
|
/** Returns lowest of two values passed in
|
|
419
432
|
* @param {Number} valueA
|
|
420
433
|
* @param {Number} valueB
|
|
421
434
|
* @return {Number}
|
|
422
435
|
* @memberof Utilities */
|
|
423
|
-
function min(
|
|
436
|
+
function min(valueA, valueB) { return Math.min(valueA, valueB); }
|
|
424
437
|
|
|
425
438
|
/** Returns highest of two values passed in
|
|
426
439
|
* @param {Number} valueA
|
|
427
440
|
* @param {Number} valueB
|
|
428
441
|
* @return {Number}
|
|
429
442
|
* @memberof Utilities */
|
|
430
|
-
function max(
|
|
443
|
+
function max(valueA, valueB) { return Math.max(valueA, valueB); }
|
|
431
444
|
|
|
432
445
|
/** Returns the sign of value passed in (also returns 1 if 0)
|
|
433
446
|
* @param {Number} value
|
|
434
447
|
* @return {Number}
|
|
435
448
|
* @memberof Utilities */
|
|
436
|
-
function sign(
|
|
449
|
+
function sign(value) { return Math.sign(value); }
|
|
437
450
|
|
|
438
451
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
439
452
|
* @param {Number} dividend
|
|
440
453
|
* @param {Number} [divisor=1]
|
|
441
454
|
* @return {Number}
|
|
442
455
|
* @memberof Utilities */
|
|
443
|
-
function mod(
|
|
456
|
+
function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
|
|
444
457
|
|
|
445
458
|
/** Clamps the value beween max and min
|
|
446
459
|
* @param {Number} value
|
|
@@ -448,47 +461,83 @@ function mod(a, b=1) { return ((a % b) + b) % b; }
|
|
|
448
461
|
* @param {Number} [max=1]
|
|
449
462
|
* @return {Number}
|
|
450
463
|
* @memberof Utilities */
|
|
451
|
-
function clamp(
|
|
452
|
-
{ return v < min ? min : v > max ? max : v; }
|
|
464
|
+
function clamp(value, min=0, max=1) { return value < min ? min : value > max ? max : value; }
|
|
453
465
|
|
|
454
|
-
/** Returns what percentage the value is between
|
|
466
|
+
/** Returns what percentage the value is between valueA and valueB
|
|
455
467
|
* @param {Number} value
|
|
456
|
-
* @param {Number}
|
|
457
|
-
* @param {Number}
|
|
468
|
+
* @param {Number} valueA
|
|
469
|
+
* @param {Number} valueB
|
|
458
470
|
* @return {Number}
|
|
459
471
|
* @memberof Utilities */
|
|
460
|
-
function percent(
|
|
461
|
-
{ return
|
|
472
|
+
function percent(value, valueA, valueB)
|
|
473
|
+
{ return valueB-valueA ? clamp((value-valueA) / (valueB-valueA)) : 0; }
|
|
462
474
|
|
|
463
|
-
/** Linearly interpolates
|
|
475
|
+
/** Linearly interpolates between values passed in using percent
|
|
464
476
|
* @param {Number} percent
|
|
465
|
-
* @param {Number}
|
|
466
|
-
* @param {Number}
|
|
477
|
+
* @param {Number} valueA
|
|
478
|
+
* @param {Number} valueB
|
|
467
479
|
* @return {Number}
|
|
468
480
|
* @memberof Utilities */
|
|
469
|
-
function lerp(
|
|
481
|
+
function lerp(percent, valueA, valueB) { return valueA + clamp(percent) * (valueB-valueA); }
|
|
482
|
+
|
|
483
|
+
/** Returns signed wrapped distance between the two values passed in
|
|
484
|
+
* @param {Number} valueA
|
|
485
|
+
* @param {Number} valueB
|
|
486
|
+
* @param {Number} [wrapSize=1]
|
|
487
|
+
* @returns {Number}
|
|
488
|
+
* @memberof Utilities */
|
|
489
|
+
function distanceWrap(valueA, valueB, wrapSize=1)
|
|
490
|
+
{ const d = (valueA - valueB) % wrapSize; return d*2 % wrapSize - d; }
|
|
491
|
+
|
|
492
|
+
/** Linearly interpolates between values passed in with wrappping
|
|
493
|
+
* @param {Number} percent
|
|
494
|
+
* @param {Number} valueA
|
|
495
|
+
* @param {Number} valueB
|
|
496
|
+
* @param {Number} [wrapSize=1]
|
|
497
|
+
* @returns {Number}
|
|
498
|
+
* @memberof Utilities */
|
|
499
|
+
function lerpWrap(percent, valueA, valueB, wrapSize=1)
|
|
500
|
+
{ return valueB + clamp(percent) * distanceWrap(valueA, valueB, wrapSize); }
|
|
501
|
+
|
|
502
|
+
/** Returns signed wrapped distance between the two angles passed in
|
|
503
|
+
* @param {Number} angleA
|
|
504
|
+
* @param {Number} angleB
|
|
505
|
+
* @returns {Number}
|
|
506
|
+
* @memberof Utilities */
|
|
507
|
+
function distanceAngle(angleA, angleB) { distanceWrap(angleA, angleB, 2*PI); }
|
|
508
|
+
|
|
509
|
+
/** Linearly interpolates between the angles passed in with wrappping
|
|
510
|
+
* @param {Number} percent
|
|
511
|
+
* @param {Number} angleA
|
|
512
|
+
* @param {Number} angleB
|
|
513
|
+
* @returns {Number}
|
|
514
|
+
* @memberof Utilities */
|
|
515
|
+
function lerpAngle(percent, angleA, angleB) { return lerpWrap(percent, angleA, angleB, 2*PI); }
|
|
470
516
|
|
|
471
517
|
/** Applies smoothstep function to the percentage value
|
|
472
|
-
* @param {Number}
|
|
518
|
+
* @param {Number} percent
|
|
473
519
|
* @return {Number}
|
|
474
520
|
* @memberof Utilities */
|
|
475
|
-
function smoothStep(
|
|
521
|
+
function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
476
522
|
|
|
477
523
|
/** Returns the nearest power of two not less then the value
|
|
478
524
|
* @param {Number} value
|
|
479
525
|
* @return {Number}
|
|
480
526
|
* @memberof Utilities */
|
|
481
|
-
function nearestPowerOfTwo(
|
|
527
|
+
function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
|
|
482
528
|
|
|
483
529
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
484
530
|
* @param {Vector2} pointA - Center of box A
|
|
485
531
|
* @param {Vector2} sizeA - Size of box A
|
|
486
532
|
* @param {Vector2} pointB - Center of box B
|
|
487
|
-
* @param {Vector2}
|
|
533
|
+
* @param {Vector2} sizeB - Size of box B
|
|
488
534
|
* @return {Boolean} - True if overlapping
|
|
489
535
|
* @memberof Utilities */
|
|
490
|
-
function isOverlapping(
|
|
491
|
-
{
|
|
536
|
+
function isOverlapping(pointA, sizeA, pointB, sizeB)
|
|
537
|
+
{
|
|
538
|
+
return abs(pointA.x - pointB.x)*2 < sizeA.x + sizeB.x
|
|
539
|
+
&& abs(pointA.y - pointB.y)*2 < sizeA.y + sizeB.y;
|
|
540
|
+
}
|
|
492
541
|
|
|
493
542
|
/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
|
|
494
543
|
* @param {Number} [frequency=1] - Frequency of the wave in Hz
|
|
@@ -515,20 +564,26 @@ function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
|
|
|
515
564
|
* @param {Number} [valueB=0]
|
|
516
565
|
* @return {Number}
|
|
517
566
|
* @memberof Random */
|
|
518
|
-
function rand(
|
|
567
|
+
function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valueB); }
|
|
519
568
|
|
|
520
569
|
/** Returns a floored random value the two values passed in
|
|
521
|
-
* @param {Number}
|
|
570
|
+
* @param {Number} valueA
|
|
522
571
|
* @param {Number} [valueB=0]
|
|
523
572
|
* @return {Number}
|
|
524
573
|
* @memberof Random */
|
|
525
|
-
function randInt(
|
|
574
|
+
function randInt(valueA, valueB=0) { return Math.floor(rand(valueA,valueB)); }
|
|
526
575
|
|
|
527
576
|
/** Randomly returns either -1 or 1
|
|
528
577
|
* @return {Number}
|
|
529
578
|
* @memberof Random */
|
|
530
579
|
function randSign() { return randInt(2) * 2 - 1; }
|
|
531
580
|
|
|
581
|
+
/** Returns a random Vector2 with the passed in length
|
|
582
|
+
* @param {Number} [length=1]
|
|
583
|
+
* @return {Vector2}
|
|
584
|
+
* @memberof Random */
|
|
585
|
+
function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
|
|
586
|
+
|
|
532
587
|
/** Returns a random Vector2 within a circular shape
|
|
533
588
|
* @param {Number} [radius=1]
|
|
534
589
|
* @param {Number} [minRadius=0]
|
|
@@ -537,44 +592,62 @@ function randSign() { return randInt(2) * 2 - 1; }
|
|
|
537
592
|
function randInCircle(radius=1, minRadius=0)
|
|
538
593
|
{ return radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
|
|
539
594
|
|
|
540
|
-
/** Returns a random Vector2 with the passed in length
|
|
541
|
-
* @param {Number} [length=1]
|
|
542
|
-
* @return {Vector2}
|
|
543
|
-
* @memberof Random */
|
|
544
|
-
function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
|
|
545
|
-
|
|
546
595
|
/** Returns a random color between the two passed in colors, combine components if linear
|
|
547
596
|
* @param {Color} [colorA=Color()]
|
|
548
597
|
* @param {Color} [colorB=Color(0,0,0,1)]
|
|
549
598
|
* @param {Boolean} [linear]
|
|
550
599
|
* @return {Color}
|
|
551
600
|
* @memberof Random */
|
|
552
|
-
function randColor(
|
|
553
|
-
{
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
* @default
|
|
558
|
-
* @memberof Random */
|
|
559
|
-
let randSeed = 1;
|
|
601
|
+
function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear)
|
|
602
|
+
{
|
|
603
|
+
return linear ? colorA.lerp(colorB, rand()) :
|
|
604
|
+
new Color(rand(colorA.r,colorB.r), rand(colorA.g,colorB.g), rand(colorA.b,colorB.b), rand(colorA.a,colorB.a));
|
|
605
|
+
}
|
|
560
606
|
|
|
561
|
-
|
|
562
|
-
* @param {Number} seed
|
|
563
|
-
* @memberof Random */
|
|
564
|
-
function setRandSeed(seed) { randSeed = seed; }
|
|
607
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
565
608
|
|
|
566
|
-
/**
|
|
567
|
-
*
|
|
568
|
-
*
|
|
569
|
-
*
|
|
570
|
-
*
|
|
571
|
-
|
|
609
|
+
/**
|
|
610
|
+
* Seeded random number generator
|
|
611
|
+
* - Can be used to create a deterministic random number sequence
|
|
612
|
+
* @example
|
|
613
|
+
* let r = new RandomGenerator(123); // random number generator with seed 123
|
|
614
|
+
* let a = r.rand(); // random value between 0 and 1
|
|
615
|
+
* let b = r.randInt(10); // random integer between 0 and 9
|
|
616
|
+
* r.seed = 123; // reset the seed
|
|
617
|
+
* let c = r.rand(); // the same value as a
|
|
618
|
+
*/
|
|
619
|
+
class RandomGenerator
|
|
572
620
|
{
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
621
|
+
/** Create a random number generator with the seed passed in
|
|
622
|
+
* @param {Number} seed - Starting seed */
|
|
623
|
+
constructor(seed)
|
|
624
|
+
{
|
|
625
|
+
/** @property {Number} - random seed */
|
|
626
|
+
this.seed = seed;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/** Returns a seeded random value between the two values passed in
|
|
630
|
+
* @param {Number} [valueA=1]
|
|
631
|
+
* @param {Number} [valueB=0]
|
|
632
|
+
* @return {Number} */
|
|
633
|
+
float(valueA=1, valueB=0)
|
|
634
|
+
{
|
|
635
|
+
// xorshift algorithm
|
|
636
|
+
this.seed ^= this.seed << 13;
|
|
637
|
+
this.seed ^= this.seed >>> 17;
|
|
638
|
+
this.seed ^= this.seed << 5;
|
|
639
|
+
return valueB + (valueA - valueB) * abs(this.seed % 1e9) / 1e9;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** Returns a floored seeded random value the two values passed in
|
|
643
|
+
* @param {Number} valueA
|
|
644
|
+
* @param {Number} [valueB=0]
|
|
645
|
+
* @return {Number} */
|
|
646
|
+
int(valueA, valueB=0) { return Math.floor(this.rand(valueA, valueB)); }
|
|
647
|
+
|
|
648
|
+
/** Randomly returns either -1 or 1 deterministically
|
|
649
|
+
* @return {Number} */
|
|
650
|
+
sign() { return this.randInt(2) * 2 - 1; }
|
|
578
651
|
}
|
|
579
652
|
|
|
580
653
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -596,7 +669,7 @@ function vec2(x=0, y)
|
|
|
596
669
|
|
|
597
670
|
/**
|
|
598
671
|
* Check if object is a valid Vector2
|
|
599
|
-
* @param {Vector2}
|
|
672
|
+
* @param {Vector2} v
|
|
600
673
|
* @return {Boolean}
|
|
601
674
|
* @memberof Utilities
|
|
602
675
|
*/
|
|
@@ -629,27 +702,27 @@ class Vector2
|
|
|
629
702
|
copy() { return new Vector2(this.x, this.y); }
|
|
630
703
|
|
|
631
704
|
/** Returns a copy of this vector plus the vector passed in
|
|
632
|
-
* @param {Vector2} vector
|
|
705
|
+
* @param {Vector2} v - other vector
|
|
633
706
|
* @return {Vector2} */
|
|
634
707
|
add(v) { ASSERT(isVector2(v)); return new Vector2(this.x + v.x, this.y + v.y); }
|
|
635
708
|
|
|
636
709
|
/** Returns a copy of this vector minus the vector passed in
|
|
637
|
-
* @param {Vector2} vector
|
|
710
|
+
* @param {Vector2} v - other vector
|
|
638
711
|
* @return {Vector2} */
|
|
639
712
|
subtract(v) { ASSERT(isVector2(v)); return new Vector2(this.x - v.x, this.y - v.y); }
|
|
640
713
|
|
|
641
714
|
/** Returns a copy of this vector times the vector passed in
|
|
642
|
-
* @param {Vector2} vector
|
|
715
|
+
* @param {Vector2} v - other vector
|
|
643
716
|
* @return {Vector2} */
|
|
644
717
|
multiply(v) { ASSERT(isVector2(v)); return new Vector2(this.x * v.x, this.y * v.y); }
|
|
645
718
|
|
|
646
719
|
/** Returns a copy of this vector divided by the vector passed in
|
|
647
|
-
* @param {Vector2} vector
|
|
720
|
+
* @param {Vector2} v - other vector
|
|
648
721
|
* @return {Vector2} */
|
|
649
722
|
divide(v) { ASSERT(isVector2(v)); return new Vector2(this.x / v.x, this.y / v.y); }
|
|
650
723
|
|
|
651
724
|
/** Returns a copy of this vector scaled by the vector passed in
|
|
652
|
-
* @param {Number} scale
|
|
725
|
+
* @param {Number} s - scale
|
|
653
726
|
* @return {Vector2} */
|
|
654
727
|
scale(s) { ASSERT(!isVector2(s)); return new Vector2(this.x * s, this.y * s); }
|
|
655
728
|
|
|
@@ -662,12 +735,12 @@ class Vector2
|
|
|
662
735
|
lengthSquared() { return this.x**2 + this.y**2; }
|
|
663
736
|
|
|
664
737
|
/** Returns the distance from this vector to vector passed in
|
|
665
|
-
* @param {Vector2} vector
|
|
738
|
+
* @param {Vector2} v - other vector
|
|
666
739
|
* @return {Number} */
|
|
667
740
|
distance(v) { return this.distanceSquared(v)**.5; }
|
|
668
741
|
|
|
669
742
|
/** Returns the distance squared from this vector to vector passed in
|
|
670
|
-
* @param {Vector2} vector
|
|
743
|
+
* @param {Vector2} v - other vector
|
|
671
744
|
* @return {Number} */
|
|
672
745
|
distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
|
|
673
746
|
|
|
@@ -682,12 +755,12 @@ class Vector2
|
|
|
682
755
|
clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
|
|
683
756
|
|
|
684
757
|
/** Returns the dot product of this and the vector passed in
|
|
685
|
-
* @param {Vector2} vector
|
|
758
|
+
* @param {Vector2} v - other vector
|
|
686
759
|
* @return {Number} */
|
|
687
760
|
dot(v) { ASSERT(isVector2(v)); return this.x*v.x + this.y*v.y; }
|
|
688
761
|
|
|
689
762
|
/** Returns the cross product of this and the vector passed in
|
|
690
|
-
* @param {Vector2} vector
|
|
763
|
+
* @param {Vector2} v - other vector
|
|
691
764
|
* @return {Number} */
|
|
692
765
|
cross(v) { ASSERT(isVector2(v)); return this.x*v.y - this.y*v.x; }
|
|
693
766
|
|
|
@@ -699,12 +772,17 @@ class Vector2
|
|
|
699
772
|
* @param {Number} [angle=0]
|
|
700
773
|
* @param {Number} [length=1]
|
|
701
774
|
* @return {Vector2} */
|
|
702
|
-
setAngle(
|
|
775
|
+
setAngle(angle=0, length=1)
|
|
776
|
+
{ this.x = length*Math.sin(angle); this.y = length*Math.cos(angle); return this; }
|
|
703
777
|
|
|
704
778
|
/** Returns copy of this vector rotated by the angle passed in
|
|
705
779
|
* @param {Number} angle
|
|
706
780
|
* @return {Vector2} */
|
|
707
|
-
rotate(
|
|
781
|
+
rotate(angle)
|
|
782
|
+
{
|
|
783
|
+
const c = Math.cos(angle), s = Math.sin(angle);
|
|
784
|
+
return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
|
|
785
|
+
}
|
|
708
786
|
|
|
709
787
|
/** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
|
|
710
788
|
* @return {Number} */
|
|
@@ -723,10 +801,11 @@ class Vector2
|
|
|
723
801
|
area() { return abs(this.x * this.y); }
|
|
724
802
|
|
|
725
803
|
/** Returns a new vector that is p percent between this and the vector passed in
|
|
726
|
-
* @param {Vector2} vector
|
|
804
|
+
* @param {Vector2} v - other vector
|
|
727
805
|
* @param {Number} percent
|
|
728
806
|
* @return {Vector2} */
|
|
729
|
-
lerp(v,
|
|
807
|
+
lerp(v, percent)
|
|
808
|
+
{ ASSERT(isVector2(v)); return this.add(v.subtract(this).scale(clamp(percent))); }
|
|
730
809
|
|
|
731
810
|
/** Returns true if this vector is within the bounds of an array size passed in
|
|
732
811
|
* @param {Vector2} arraySize
|
|
@@ -744,10 +823,10 @@ class Vector2
|
|
|
744
823
|
|
|
745
824
|
/**
|
|
746
825
|
* Create a color object with RGBA values
|
|
747
|
-
* @param {Number} [r=1]
|
|
748
|
-
* @param {Number} [g=1]
|
|
749
|
-
* @param {Number} [b=1]
|
|
750
|
-
* @param {Number} [a=1]
|
|
826
|
+
* @param {Number} [r=1] - red
|
|
827
|
+
* @param {Number} [g=1] - green
|
|
828
|
+
* @param {Number} [b=1] - blue
|
|
829
|
+
* @param {Number} [a=1] - alpha
|
|
751
830
|
* @return {Color}
|
|
752
831
|
* @memberof Utilities
|
|
753
832
|
*/
|
|
@@ -755,10 +834,10 @@ function rgb(r, g, b, a) { return new Color(r, g, b, a); }
|
|
|
755
834
|
|
|
756
835
|
/**
|
|
757
836
|
* Create a color object with HSLA values
|
|
758
|
-
* @param {Number} [h=0]
|
|
759
|
-
* @param {Number} [s=0]
|
|
760
|
-
* @param {Number} [l=1]
|
|
761
|
-
* @param {Number} [a=1]
|
|
837
|
+
* @param {Number} [h=0] - hue
|
|
838
|
+
* @param {Number} [s=0] - saturation
|
|
839
|
+
* @param {Number} [l=1] - lightness
|
|
840
|
+
* @param {Number} [a=1] - alpha
|
|
762
841
|
* @return {Color}
|
|
763
842
|
* @memberof Utilities
|
|
764
843
|
*/
|
|
@@ -775,11 +854,11 @@ function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
|
|
|
775
854
|
*/
|
|
776
855
|
class Color
|
|
777
856
|
{
|
|
778
|
-
/** Create a color with the components passed in, white by default
|
|
779
|
-
* @param {Number} [
|
|
780
|
-
* @param {Number} [
|
|
781
|
-
* @param {Number} [
|
|
782
|
-
* @param {Number} [
|
|
857
|
+
/** Create a color with the rgba components passed in, white by default
|
|
858
|
+
* @param {Number} [r=1] - red
|
|
859
|
+
* @param {Number} [g=1] - green
|
|
860
|
+
* @param {Number} [b=1] - blue
|
|
861
|
+
* @param {Number} [a=1] - alpha*/
|
|
783
862
|
constructor(r=1, g=1, b=1, a=1)
|
|
784
863
|
{
|
|
785
864
|
/** @property {Number} - Red */
|
|
@@ -797,22 +876,22 @@ class Color
|
|
|
797
876
|
copy() { return new Color(this.r, this.g, this.b, this.a); }
|
|
798
877
|
|
|
799
878
|
/** Returns a copy of this color plus the color passed in
|
|
800
|
-
* @param {Color} color
|
|
879
|
+
* @param {Color} c - other color
|
|
801
880
|
* @return {Color} */
|
|
802
881
|
add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
|
|
803
882
|
|
|
804
883
|
/** Returns a copy of this color minus the color passed in
|
|
805
|
-
* @param {Color} color
|
|
884
|
+
* @param {Color} c - other color
|
|
806
885
|
* @return {Color} */
|
|
807
886
|
subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
|
|
808
887
|
|
|
809
888
|
/** Returns a copy of this color times the color passed in
|
|
810
|
-
* @param {Color} color
|
|
889
|
+
* @param {Color} c - other color
|
|
811
890
|
* @return {Color} */
|
|
812
891
|
multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
|
|
813
892
|
|
|
814
893
|
/** Returns a copy of this color divided by the color passed in
|
|
815
|
-
* @param {Color} color
|
|
894
|
+
* @param {Color} c - other color
|
|
816
895
|
* @return {Color} */
|
|
817
896
|
divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
|
|
818
897
|
|
|
@@ -820,23 +899,24 @@ class Color
|
|
|
820
899
|
* @param {Number} scale
|
|
821
900
|
* @param {Number} [alphaScale=scale]
|
|
822
901
|
* @return {Color} */
|
|
823
|
-
scale(
|
|
902
|
+
scale(scale, alphaScale=scale)
|
|
903
|
+
{ return new Color(this.r*scale, this.g*scale, this.b*scale, this.a*alphaScale); }
|
|
824
904
|
|
|
825
905
|
/** Returns a copy of this color clamped to the valid range between 0 and 1
|
|
826
906
|
* @return {Color} */
|
|
827
907
|
clamp() { return new Color(clamp(this.r), clamp(this.g), clamp(this.b), clamp(this.a)); }
|
|
828
908
|
|
|
829
909
|
/** Returns a new color that is p percent between this and the color passed in
|
|
830
|
-
* @param {Color} color
|
|
910
|
+
* @param {Color} c - other color
|
|
831
911
|
* @param {Number} percent
|
|
832
912
|
* @return {Color} */
|
|
833
|
-
lerp(c,
|
|
913
|
+
lerp(c, percent) { return this.add(c.subtract(this).scale(clamp(percent))); }
|
|
834
914
|
|
|
835
915
|
/** Sets this color given a hue, saturation, lightness, and alpha
|
|
836
|
-
* @param {Number} [
|
|
837
|
-
* @param {Number} [
|
|
838
|
-
* @param {Number} [
|
|
839
|
-
* @param {Number} [
|
|
916
|
+
* @param {Number} [h=0] - hue
|
|
917
|
+
* @param {Number} [s=0] - saturation
|
|
918
|
+
* @param {Number} [l=1] - lightness
|
|
919
|
+
* @param {Number} [a=1] - alpha
|
|
840
920
|
* @return {Color} */
|
|
841
921
|
setHSLA(h=0, s=0, l=1, a=1)
|
|
842
922
|
{
|
|
@@ -977,14 +1057,15 @@ class Timer
|
|
|
977
1057
|
|
|
978
1058
|
/** Returns this timer expressed as a string
|
|
979
1059
|
* @return {String} */
|
|
980
|
-
toString() { if (debug) { return this.
|
|
1060
|
+
toString() { if (debug) { return this.isSet() ? Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }}
|
|
981
1061
|
|
|
982
1062
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
983
1063
|
* @return {Number} */
|
|
984
1064
|
valueOf() { return this.get(); }
|
|
985
|
-
}
|
|
1065
|
+
}
|
|
986
1066
|
/**
|
|
987
1067
|
* LittleJS Engine Settings
|
|
1068
|
+
* - All settings for the engine are here
|
|
988
1069
|
* @namespace Settings
|
|
989
1070
|
*/
|
|
990
1071
|
|
|
@@ -1231,7 +1312,7 @@ let medalDisplayIconSize = 50;
|
|
|
1231
1312
|
* @type {Boolean}
|
|
1232
1313
|
* @default 0
|
|
1233
1314
|
* @memberof Settings */
|
|
1234
|
-
let medalsPreventUnlock;
|
|
1315
|
+
let medalsPreventUnlock;
|
|
1235
1316
|
/**
|
|
1236
1317
|
* LittleJS Object System
|
|
1237
1318
|
*/
|
|
@@ -1240,12 +1321,12 @@ let medalsPreventUnlock;
|
|
|
1240
1321
|
|
|
1241
1322
|
/**
|
|
1242
1323
|
* LittleJS Object Base Object Class
|
|
1243
|
-
* -
|
|
1324
|
+
* - Top level object class used by the engine
|
|
1244
1325
|
* - Automatically adds self to object list
|
|
1245
1326
|
* - Will be updated and rendered each frame
|
|
1246
1327
|
* - Renders as a sprite from a tilesheet by default
|
|
1247
1328
|
* - Can have color and addtive color applied
|
|
1248
|
-
* -
|
|
1329
|
+
* - 2D Physics and collision system
|
|
1249
1330
|
* - Sorted by renderOrder
|
|
1250
1331
|
* - Objects can have children attached
|
|
1251
1332
|
* - Parents are updated before children, and set child transform
|
|
@@ -1312,7 +1393,7 @@ class EngineObject
|
|
|
1312
1393
|
/** @property {Number} [renderOrder=0] - Objects are sorted by render order */
|
|
1313
1394
|
this.renderOrder = renderOrder;
|
|
1314
1395
|
/** @property {Vector2} [velocity=Vector2()] - Velocity of the object */
|
|
1315
|
-
this.velocity =
|
|
1396
|
+
this.velocity = vec2();
|
|
1316
1397
|
/** @property {Number} [angleVelocity=0] - Angular velocity of the object */
|
|
1317
1398
|
this.angleVelocity = 0;
|
|
1318
1399
|
|
|
@@ -1372,15 +1453,17 @@ class EngineObject
|
|
|
1372
1453
|
for (const o of engineObjectsCollide)
|
|
1373
1454
|
{
|
|
1374
1455
|
// non solid objects don't collide with eachother
|
|
1375
|
-
if (!this.isSolid
|
|
1456
|
+
if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o == this)
|
|
1376
1457
|
continue;
|
|
1377
1458
|
|
|
1378
1459
|
// check collision
|
|
1379
1460
|
if (!isOverlapping(this.pos, this.size, o.pos, o.size))
|
|
1380
1461
|
continue;
|
|
1381
1462
|
|
|
1382
|
-
//
|
|
1383
|
-
|
|
1463
|
+
// notify objects of collision and check if should be resolved
|
|
1464
|
+
const collide1 = this.collideWithObject(o);
|
|
1465
|
+
const collide2 = o.collideWithObject(this);
|
|
1466
|
+
if (!collide1 || !collide2)
|
|
1384
1467
|
continue;
|
|
1385
1468
|
|
|
1386
1469
|
if (isOverlapping(oldPos, this.size, o.pos, o.size))
|
|
@@ -1405,7 +1488,7 @@ class EngineObject
|
|
|
1405
1488
|
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
|
|
1406
1489
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1407
1490
|
|
|
1408
|
-
if (smallStepUp
|
|
1491
|
+
if (smallStepUp || isBlockedY || !isBlockedX) // resolve y collision
|
|
1409
1492
|
{
|
|
1410
1493
|
// push outside object collision
|
|
1411
1494
|
this.pos.y = o.pos.y + (sizeBoth.y/2 + epsilon) * sign(oldPos.y - o.pos.y);
|
|
@@ -1434,7 +1517,7 @@ class EngineObject
|
|
|
1434
1517
|
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
1435
1518
|
}
|
|
1436
1519
|
}
|
|
1437
|
-
if (!smallStepUp
|
|
1520
|
+
if (!smallStepUp && isBlockedX) // resolve x collision
|
|
1438
1521
|
{
|
|
1439
1522
|
// push outside collision
|
|
1440
1523
|
this.pos.x = o.pos.x + (sizeBoth.x/2 + epsilon) * sign(oldPos.x - o.pos.x);
|
|
@@ -1469,9 +1552,9 @@ class EngineObject
|
|
|
1469
1552
|
if (!tileCollisionTest(oldPos, this.size, this))
|
|
1470
1553
|
{
|
|
1471
1554
|
// test which side we bounced off (or both if a corner)
|
|
1472
|
-
const isBlockedY = tileCollisionTest(
|
|
1473
|
-
const isBlockedX = tileCollisionTest(
|
|
1474
|
-
if (isBlockedY
|
|
1555
|
+
const isBlockedY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
1556
|
+
const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
1557
|
+
if (isBlockedY || !isBlockedX)
|
|
1475
1558
|
{
|
|
1476
1559
|
// set if landed on ground
|
|
1477
1560
|
this.groundObject = wasMovingDown;
|
|
@@ -1534,7 +1617,7 @@ class EngineObject
|
|
|
1534
1617
|
* @param {EngineObject} object - the object to test against
|
|
1535
1618
|
* @return {Boolean} - true if the collision should be resolved
|
|
1536
1619
|
*/
|
|
1537
|
-
collideWithObject(
|
|
1620
|
+
collideWithObject(object) { return 1; }
|
|
1538
1621
|
|
|
1539
1622
|
/** How long since the object was created
|
|
1540
1623
|
* @return {Number} */
|
|
@@ -1542,7 +1625,7 @@ class EngineObject
|
|
|
1542
1625
|
|
|
1543
1626
|
/** Apply acceleration to this object (adjust velocity, not affected by mass)
|
|
1544
1627
|
* @param {Vector2} acceleration */
|
|
1545
|
-
applyAcceleration(
|
|
1628
|
+
applyAcceleration(acceleration) { if (this.mass) this.velocity = this.velocity.add(acceleration); }
|
|
1546
1629
|
|
|
1547
1630
|
/** Apply force to this object (adjust velocity, affected by mass)
|
|
1548
1631
|
* @param {Vector2} force */
|
|
@@ -1607,12 +1690,13 @@ class EngineObject
|
|
|
1607
1690
|
return text;
|
|
1608
1691
|
}
|
|
1609
1692
|
}
|
|
1610
|
-
}
|
|
1693
|
+
}
|
|
1611
1694
|
/**
|
|
1612
1695
|
* LittleJS Drawing System
|
|
1613
|
-
* - Hybrid with both Canvas2D and WebGL available
|
|
1696
|
+
* - Hybrid system with both Canvas2D and WebGL available
|
|
1614
1697
|
* - Super fast tile sheet rendering with WebGL
|
|
1615
1698
|
* - Can apply rotation, mirror, color and additive color
|
|
1699
|
+
* - Font rendering system with built in engine font
|
|
1616
1700
|
* - Many useful utility functions
|
|
1617
1701
|
*
|
|
1618
1702
|
* LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
|
|
@@ -1622,7 +1706,6 @@ class EngineObject
|
|
|
1622
1706
|
* overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
|
|
1623
1707
|
*
|
|
1624
1708
|
* The WebGL rendering system is very fast with some caveats...
|
|
1625
|
-
* - The default setup supports only 1 tile sheet, to support more call glCreateTexture and glSetTexture
|
|
1626
1709
|
* - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
|
|
1627
1710
|
* - Group additive rendering together using renderOrder to mitigate this issue
|
|
1628
1711
|
*
|
|
@@ -1666,25 +1749,29 @@ const tileImage = new Image;
|
|
|
1666
1749
|
let tileImageSize, tileImageFixBleed, drawCount;
|
|
1667
1750
|
|
|
1668
1751
|
/** Convert from screen to world space coordinates
|
|
1669
|
-
* - if calling outside of render, you may need to manually set mainCanvasSize
|
|
1670
1752
|
* @param {Vector2} screenPos
|
|
1671
1753
|
* @return {Vector2}
|
|
1672
1754
|
* @memberof Draw */
|
|
1673
1755
|
function screenToWorld(screenPos)
|
|
1674
1756
|
{
|
|
1675
|
-
|
|
1676
|
-
|
|
1757
|
+
return new Vector2
|
|
1758
|
+
(
|
|
1759
|
+
(screenPos.x - mainCanvasSize.x/2 + .5) / cameraScale + cameraPos.x,
|
|
1760
|
+
(screenPos.y - mainCanvasSize.y/2 + .5) / -cameraScale + cameraPos.y
|
|
1761
|
+
);
|
|
1677
1762
|
}
|
|
1678
1763
|
|
|
1679
1764
|
/** Convert from world to screen space coordinates
|
|
1680
|
-
* - if calling outside of render, you may need to manually set mainCanvasSize
|
|
1681
1765
|
* @param {Vector2} worldPos
|
|
1682
1766
|
* @return {Vector2}
|
|
1683
1767
|
* @memberof Draw */
|
|
1684
1768
|
function worldToScreen(worldPos)
|
|
1685
1769
|
{
|
|
1686
|
-
|
|
1687
|
-
|
|
1770
|
+
return new Vector2
|
|
1771
|
+
(
|
|
1772
|
+
(worldPos.x - cameraPos.x) * cameraScale + mainCanvasSize.x/2 - .5,
|
|
1773
|
+
(worldPos.y - cameraPos.y) * -cameraScale + mainCanvasSize.y/2 - .5
|
|
1774
|
+
);
|
|
1688
1775
|
}
|
|
1689
1776
|
|
|
1690
1777
|
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
@@ -1697,13 +1784,21 @@ function worldToScreen(worldPos)
|
|
|
1697
1784
|
* @param {Boolean} [mirror=0] - If true image is flipped along the Y axis
|
|
1698
1785
|
* @param {Color} [additiveColor=Color(0,0,0,0)] - Additive color to be applied
|
|
1699
1786
|
* @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
1787
|
+
* @param {Boolean} [screenSpace=0] - If true the pos and size are in screen space
|
|
1700
1788
|
* @memberof Draw */
|
|
1701
|
-
function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color,
|
|
1702
|
-
additiveColor=new Color(0,0,0,0), useWebGL=glEnable)
|
|
1789
|
+
function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color,
|
|
1790
|
+
angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace)
|
|
1703
1791
|
{
|
|
1704
1792
|
showWatermark && ++drawCount;
|
|
1793
|
+
|
|
1705
1794
|
if (glEnable && useWebGL)
|
|
1706
1795
|
{
|
|
1796
|
+
if (screenSpace)
|
|
1797
|
+
{
|
|
1798
|
+
// convert to world space
|
|
1799
|
+
pos = screenToWorld(pos);
|
|
1800
|
+
size = size.scale(1/cameraScale);
|
|
1801
|
+
}
|
|
1707
1802
|
if (tileIndex < 0 || !tileImage.width)
|
|
1708
1803
|
{
|
|
1709
1804
|
// if negative tile index or image not found, force untextured
|
|
@@ -1745,7 +1840,7 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
|
|
|
1745
1840
|
context.globalAlpha = color.a; // only alpha is supported
|
|
1746
1841
|
context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
|
|
1747
1842
|
}
|
|
1748
|
-
});
|
|
1843
|
+
}, undefined, screenSpace);
|
|
1749
1844
|
}
|
|
1750
1845
|
}
|
|
1751
1846
|
|
|
@@ -1755,38 +1850,30 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
|
|
|
1755
1850
|
* @param {Color} [color=Color()]
|
|
1756
1851
|
* @param {Number} [angle=0]
|
|
1757
1852
|
* @param {Boolean} [useWebGL=glEnable]
|
|
1853
|
+
* @param {Boolean} [screenSpace=0]
|
|
1758
1854
|
* @memberof Draw */
|
|
1759
|
-
function drawRect(pos, size, color, angle, useWebGL)
|
|
1760
|
-
{
|
|
1761
|
-
drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
/** Draw textured tile centered on pos in screen space
|
|
1765
|
-
* @param {Vector2} pos - Center of the tile
|
|
1766
|
-
* @param {Vector2} [size=Vector2(1,1)] - Size of the tile
|
|
1767
|
-
* @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
|
|
1768
|
-
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
|
|
1769
|
-
* @param {Color} [color=Color()]
|
|
1770
|
-
* @param {Number} [angle=0]
|
|
1771
|
-
* @param {Boolean} [mirror=0]
|
|
1772
|
-
* @param {Color} [additiveColor=Color(0,0,0,0)]
|
|
1773
|
-
* @param {Boolean} [useWebGL=glEnable]
|
|
1774
|
-
* @memberof Draw */
|
|
1775
|
-
function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL)
|
|
1776
|
-
{
|
|
1777
|
-
drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL);
|
|
1778
|
-
}
|
|
1855
|
+
function drawRect(pos, size, color, angle, useWebGL, screenSpace)
|
|
1856
|
+
{ drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, undefined, useWebGL, screenSpace); }
|
|
1779
1857
|
|
|
1780
|
-
/** Draw colored
|
|
1781
|
-
* @param {
|
|
1782
|
-
* @param {Vector2} [size=Vector2(1,1)]
|
|
1858
|
+
/** Draw colored polygon using passed in points
|
|
1859
|
+
* @param {Array} points - Array of Vector2 points
|
|
1783
1860
|
* @param {Color} [color=Color()]
|
|
1784
|
-
* @param {Number} [angle=0]
|
|
1785
1861
|
* @param {Boolean} [useWebGL=glEnable]
|
|
1862
|
+
* @param {Boolean} [screenSpace=0]
|
|
1786
1863
|
* @memberof Draw */
|
|
1787
|
-
function
|
|
1864
|
+
function drawPoly(points, color=new Color, useWebGL=glEnable, screenSpace)
|
|
1788
1865
|
{
|
|
1789
|
-
|
|
1866
|
+
if (useWebGL)
|
|
1867
|
+
glDrawPoints(screenSpace ? points.map(screenToWorld) : points, color.rgbaInt());
|
|
1868
|
+
else
|
|
1869
|
+
{
|
|
1870
|
+
// draw using canvas
|
|
1871
|
+
mainContext.fillStyle = color;
|
|
1872
|
+
mainContext.beginPath();
|
|
1873
|
+
for (const point of screenSpace ? points : points.map(worldToScreen))
|
|
1874
|
+
mainContext.lineTo(point.x, point.y);
|
|
1875
|
+
mainContext.fill();
|
|
1876
|
+
}
|
|
1790
1877
|
}
|
|
1791
1878
|
|
|
1792
1879
|
/** Draw colored line between two points
|
|
@@ -1795,12 +1882,13 @@ function drawRectScreenSpace(pos, size, color, angle, useWebGL)
|
|
|
1795
1882
|
* @param {Number} [thickness=.1]
|
|
1796
1883
|
* @param {Color} [color=Color()]
|
|
1797
1884
|
* @param {Boolean} [useWebGL=glEnable]
|
|
1885
|
+
* @param {Boolean} [screenSpace=0]
|
|
1798
1886
|
* @memberof Draw */
|
|
1799
1887
|
function drawLine(posA, posB, thickness=.1, color, useWebGL)
|
|
1800
1888
|
{
|
|
1801
1889
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
1802
1890
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
1803
|
-
drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL);
|
|
1891
|
+
drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace);
|
|
1804
1892
|
}
|
|
1805
1893
|
|
|
1806
1894
|
/** Draw directly to a 2d canvas context in world space
|
|
@@ -1810,12 +1898,16 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL)
|
|
|
1810
1898
|
* @param {Boolean} mirror
|
|
1811
1899
|
* @param {Function} drawFunction
|
|
1812
1900
|
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
1901
|
+
* @param {Boolean} [screenSpace=0]
|
|
1813
1902
|
* @memberof Draw */
|
|
1814
|
-
function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainContext)
|
|
1903
|
+
function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainContext, screenSpace)
|
|
1815
1904
|
{
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1905
|
+
if (!screenSpace)
|
|
1906
|
+
{
|
|
1907
|
+
// create canvas transform from world space to screen space
|
|
1908
|
+
pos = worldToScreen(pos);
|
|
1909
|
+
size = size.scale(cameraScale);
|
|
1910
|
+
}
|
|
1819
1911
|
context.save();
|
|
1820
1912
|
context.translate(pos.x+.5|0, pos.y+.5|0);
|
|
1821
1913
|
context.rotate(angle);
|
|
@@ -1922,6 +2014,17 @@ class FontImage
|
|
|
1922
2014
|
this.context = context;
|
|
1923
2015
|
}
|
|
1924
2016
|
|
|
2017
|
+
/** Draw text in world space using the image font
|
|
2018
|
+
* @param {String} text
|
|
2019
|
+
* @param {Vector2} pos
|
|
2020
|
+
* @param {Number} [scale=.25]
|
|
2021
|
+
* @param {Boolean} [center]
|
|
2022
|
+
*/
|
|
2023
|
+
drawText(text, pos, scale=1, center)
|
|
2024
|
+
{
|
|
2025
|
+
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
|
|
2026
|
+
}
|
|
2027
|
+
|
|
1925
2028
|
/** Draw text in screen space using the image font
|
|
1926
2029
|
* @param {String} text
|
|
1927
2030
|
* @param {Vector2} pos
|
|
@@ -1959,17 +2062,6 @@ class FontImage
|
|
|
1959
2062
|
|
|
1960
2063
|
context.restore();
|
|
1961
2064
|
}
|
|
1962
|
-
|
|
1963
|
-
/** Draw text in world space using the image font
|
|
1964
|
-
* @param {String} text
|
|
1965
|
-
* @param {Vector2} pos
|
|
1966
|
-
* @param {Number} [scale=.25]
|
|
1967
|
-
* @param {Boolean} [center]
|
|
1968
|
-
*/
|
|
1969
|
-
drawText(text, pos, scale=1, center)
|
|
1970
|
-
{
|
|
1971
|
-
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
|
|
1972
|
-
}
|
|
1973
2065
|
}
|
|
1974
2066
|
|
|
1975
2067
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1992,13 +2084,13 @@ function toggleFullscreen()
|
|
|
1992
2084
|
else if (document.body.requestFullscreen)
|
|
1993
2085
|
document.body.requestFullscreen();
|
|
1994
2086
|
}
|
|
1995
|
-
|
|
2087
|
+
|
|
1996
2088
|
/**
|
|
1997
2089
|
* LittleJS Input System
|
|
1998
|
-
* - Tracks
|
|
1999
|
-
* -
|
|
2000
|
-
* -
|
|
2001
|
-
* - Virtual gamepad for touch devices
|
|
2090
|
+
* - Tracks keyboard down, pressed, and released
|
|
2091
|
+
* - Tracks mouse buttons, position, and wheel
|
|
2092
|
+
* - Tracks multiple analog gamepads
|
|
2093
|
+
* - Virtual gamepad for touch devices
|
|
2002
2094
|
* @namespace Input
|
|
2003
2095
|
*/
|
|
2004
2096
|
|
|
@@ -2270,40 +2362,32 @@ if (isTouchDevice)
|
|
|
2270
2362
|
let wasTouching, mouseDown = onmousedown, mouseUp = onmouseup;
|
|
2271
2363
|
onmousedown = onmouseup = ()=> 0;
|
|
2272
2364
|
|
|
2273
|
-
//
|
|
2274
|
-
ontouchstart = (e)=>
|
|
2365
|
+
// handle all touch events the same way
|
|
2366
|
+
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2275
2367
|
{
|
|
2276
|
-
|
|
2277
|
-
zzfx(0);
|
|
2278
|
-
|
|
2279
|
-
// handle all touch events the same way
|
|
2280
|
-
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2281
|
-
{
|
|
2282
|
-
e.button = 0; // all touches are left click
|
|
2368
|
+
e.button = 0; // all touches are left click
|
|
2283
2369
|
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
{
|
|
2288
|
-
// set event pos and pass it along
|
|
2289
|
-
e.x = e.touches[0].clientX;
|
|
2290
|
-
e.y = e.touches[0].clientY;
|
|
2291
|
-
wasTouching ? onmousemove(e) : mouseDown(e);
|
|
2292
|
-
}
|
|
2293
|
-
else if (wasTouching)
|
|
2294
|
-
mouseUp(e);
|
|
2295
|
-
|
|
2296
|
-
// set was touching
|
|
2297
|
-
wasTouching = touching;
|
|
2370
|
+
// fix stalled audio on mobile
|
|
2371
|
+
if (soundEnable)
|
|
2372
|
+
audioContext ? audioContext.resume() : zzfx(0);
|
|
2298
2373
|
|
|
2299
|
-
|
|
2300
|
-
|
|
2374
|
+
// check if touching and pass to mouse events
|
|
2375
|
+
const touching = e.touches.length;
|
|
2376
|
+
if (touching)
|
|
2377
|
+
{
|
|
2378
|
+
// set event pos and pass it along
|
|
2379
|
+
e.x = e.touches[0].clientX;
|
|
2380
|
+
e.y = e.touches[0].clientY;
|
|
2381
|
+
wasTouching ? onmousemove(e) : mouseDown(e);
|
|
2301
2382
|
}
|
|
2383
|
+
else if (wasTouching)
|
|
2384
|
+
mouseUp(e);
|
|
2302
2385
|
|
|
2303
|
-
//
|
|
2304
|
-
|
|
2386
|
+
// set was touching
|
|
2387
|
+
wasTouching = touching;
|
|
2305
2388
|
|
|
2306
|
-
return
|
|
2389
|
+
// must return true so the document will get focus
|
|
2390
|
+
return true;
|
|
2307
2391
|
}
|
|
2308
2392
|
}
|
|
2309
2393
|
|
|
@@ -2314,13 +2398,13 @@ if (isTouchDevice)
|
|
|
2314
2398
|
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2315
2399
|
|
|
2316
2400
|
// create the touch gamepad, called automatically by the engine
|
|
2317
|
-
|
|
2401
|
+
if (touchGamepadEnable)
|
|
2318
2402
|
{
|
|
2319
2403
|
// touch input internal variables
|
|
2320
2404
|
touchGamepadButtons = [];
|
|
2321
2405
|
touchGamepadStick = vec2();
|
|
2322
2406
|
|
|
2323
|
-
|
|
2407
|
+
const touchHandler = ontouchstart;
|
|
2324
2408
|
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2325
2409
|
{
|
|
2326
2410
|
// clear touch gamepad input
|
|
@@ -2426,7 +2510,7 @@ function touchGamepadRender()
|
|
|
2426
2510
|
const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2427
2511
|
for (let i=4; i--;)
|
|
2428
2512
|
{
|
|
2429
|
-
const pos = rightCenter.add((
|
|
2513
|
+
const pos = rightCenter.add(vec2().setAngle(i*PI/2, touchGamepadSize/2));
|
|
2430
2514
|
overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
2431
2515
|
overlayContext.beginPath();
|
|
2432
2516
|
overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
@@ -2436,15 +2520,15 @@ function touchGamepadRender()
|
|
|
2436
2520
|
|
|
2437
2521
|
// set canvas back to normal
|
|
2438
2522
|
overlayContext.restore();
|
|
2439
|
-
}
|
|
2523
|
+
}
|
|
2440
2524
|
/**
|
|
2441
2525
|
* LittleJS Audio System
|
|
2442
|
-
* - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a> - Sound Effect Generator
|
|
2443
|
-
* - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a> - Music System
|
|
2526
|
+
* - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a> - ZzFX Sound Effect Generator
|
|
2527
|
+
* - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a> - ZzFXM Music System
|
|
2444
2528
|
* - Caches sounds and music for fast playback
|
|
2445
2529
|
* - Can attenuate and apply stereo panning to sounds
|
|
2446
2530
|
* - Ability to play mp3, ogg, and wave files
|
|
2447
|
-
* - Speech synthesis
|
|
2531
|
+
* - Speech synthesis functions
|
|
2448
2532
|
* @namespace Audio
|
|
2449
2533
|
*/
|
|
2450
2534
|
|
|
@@ -2478,12 +2562,15 @@ class Sound
|
|
|
2478
2562
|
/** @property {Number} - At what percentage of range should it start tapering off */
|
|
2479
2563
|
this.taper = taper;
|
|
2480
2564
|
|
|
2481
|
-
|
|
2482
|
-
this.randomness =
|
|
2483
|
-
zzfxSound[1] = 0;
|
|
2565
|
+
/** @property {Number} - How much to randomize frequency each time sound plays */
|
|
2566
|
+
this.randomness = 0;
|
|
2484
2567
|
|
|
2485
|
-
|
|
2486
|
-
|
|
2568
|
+
if (zzfxSound)
|
|
2569
|
+
{
|
|
2570
|
+
// generate zzfx sound now for fast playback
|
|
2571
|
+
this.randomness = zzfxSound[1] || (zzfxSound[1] = 0);
|
|
2572
|
+
this.cachedSamples = zzfxSound && zzfxG(...zzfxSound);
|
|
2573
|
+
}
|
|
2487
2574
|
}
|
|
2488
2575
|
|
|
2489
2576
|
/** Play the sound
|
|
@@ -2495,7 +2582,7 @@ class Sound
|
|
|
2495
2582
|
*/
|
|
2496
2583
|
play(pos, volume=1, pitch=1, randomnessScale=1)
|
|
2497
2584
|
{
|
|
2498
|
-
if (!soundEnable) return;
|
|
2585
|
+
if (!soundEnable || !this.cachedSamples) return;
|
|
2499
2586
|
|
|
2500
2587
|
let pan;
|
|
2501
2588
|
if (pos)
|
|
@@ -2528,12 +2615,36 @@ class Sound
|
|
|
2528
2615
|
* @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
|
|
2529
2616
|
*/
|
|
2530
2617
|
playNote(semitoneOffset, pos, volume)
|
|
2618
|
+
{ return this.play(pos, volume, 2**(semitoneOffset/12), 0); }
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
/**
|
|
2622
|
+
* Sound Wave Object - Stores a wave sound for later use and can be played positionally
|
|
2623
|
+
*/
|
|
2624
|
+
class SoundWave extends Sound
|
|
2625
|
+
{
|
|
2626
|
+
/** Create a sound object and cache the wave file for later use
|
|
2627
|
+
* @param {String} waveFilename - Filename of wave file to load
|
|
2628
|
+
* @param {Number} [randomness=.05] - How much to randomize frequency each time sound plays
|
|
2629
|
+
* @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
|
|
2630
|
+
* @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
|
|
2631
|
+
*/
|
|
2632
|
+
constructor(waveFilename, randomness=.05, range, taper)
|
|
2531
2633
|
{
|
|
2634
|
+
super(0, range, taper);
|
|
2635
|
+
this.randomness = randomness;
|
|
2636
|
+
|
|
2532
2637
|
if (!soundEnable) return;
|
|
2638
|
+
if (!soundWaveDecoderContext)
|
|
2639
|
+
soundDecoderContext = new AudioContext;
|
|
2533
2640
|
|
|
2534
|
-
|
|
2641
|
+
fetch(waveFilename)
|
|
2642
|
+
.then(response => response.arrayBuffer())
|
|
2643
|
+
.then(arrayBuffer => soundWaveDecoderContext.decodeAudioData(arrayBuffer))
|
|
2644
|
+
.then(audioBuffer => this.cachedSamples = audioBuffer.getChannelData(0));
|
|
2535
2645
|
}
|
|
2536
2646
|
}
|
|
2647
|
+
let soundDecoderContext; // audio context used only to decode audio files
|
|
2537
2648
|
|
|
2538
2649
|
/**
|
|
2539
2650
|
* Music Object - Stores a zzfx music track for later use
|
|
@@ -2676,16 +2787,17 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2676
2787
|
{
|
|
2677
2788
|
if (!soundEnable) return;
|
|
2678
2789
|
|
|
2679
|
-
// create audio context
|
|
2790
|
+
// create audio context if needed
|
|
2680
2791
|
if (!audioContext)
|
|
2681
2792
|
audioContext = new AudioContext;
|
|
2682
2793
|
|
|
2683
|
-
// fix stalled audio
|
|
2684
|
-
audioContext.resume();
|
|
2685
|
-
|
|
2686
2794
|
// prevent sounds from building up if they can't be played
|
|
2687
2795
|
if (audioContext.state != 'running')
|
|
2796
|
+
{
|
|
2797
|
+
// fix stalled audio
|
|
2798
|
+
audioContext.resume();
|
|
2688
2799
|
return;
|
|
2800
|
+
}
|
|
2689
2801
|
|
|
2690
2802
|
// create buffer and source
|
|
2691
2803
|
const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, zzfxR),
|
|
@@ -2877,7 +2989,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
2877
2989
|
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
2878
2990
|
|
|
2879
2991
|
// check if there are more channels
|
|
2880
|
-
hasMore
|
|
2992
|
+
hasMore ||= !!patterns[patternIndex][channelIndex];
|
|
2881
2993
|
|
|
2882
2994
|
// get next offset, use the length of first channel
|
|
2883
2995
|
nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - !notFirstBeat) * beatLength;
|
|
@@ -2890,7 +3002,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
2890
3002
|
|
|
2891
3003
|
// stop if end, different instrument or new note
|
|
2892
3004
|
stop = i == patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
|
|
2893
|
-
instrument != (patternChannel[0] || 0)
|
|
3005
|
+
instrument != (patternChannel[0] || 0) || note | 0;
|
|
2894
3006
|
|
|
2895
3007
|
// fill buffer with samples for previous beat, most cpu intensive part
|
|
2896
3008
|
for (j = 0; j < beatLength && notFirstBeat;
|
|
@@ -2934,7 +3046,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
2934
3046
|
}
|
|
2935
3047
|
|
|
2936
3048
|
return [leftChannelBuffer, rightChannelBuffer];
|
|
2937
|
-
}
|
|
3049
|
+
}
|
|
2938
3050
|
/**
|
|
2939
3051
|
* LittleJS Tile Layer System
|
|
2940
3052
|
* - Caches arrays of tiles to off screen canvas for fast rendering
|
|
@@ -3004,12 +3116,12 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
3004
3116
|
for (let x = minX; x < maxX; ++x)
|
|
3005
3117
|
{
|
|
3006
3118
|
const tileData = tileCollision[y*tileCollisionSize.x+x];
|
|
3007
|
-
if (tileData && (!object || object.collideWithTile(tileData,
|
|
3119
|
+
if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
|
|
3008
3120
|
return 1;
|
|
3009
3121
|
}
|
|
3010
3122
|
}
|
|
3011
3123
|
|
|
3012
|
-
/** Return the center of tile if any that is hit (
|
|
3124
|
+
/** Return the center of tile if any that is hit (does not return the exact intersection)
|
|
3013
3125
|
* @param {Vector2} posStart
|
|
3014
3126
|
* @param {Vector2} posEnd
|
|
3015
3127
|
* @param {EngineObject} [object]
|
|
@@ -3018,28 +3130,41 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
3018
3130
|
function tileCollisionRaycast(posStart, posEnd, object)
|
|
3019
3131
|
{
|
|
3020
3132
|
// test if a ray collides with tiles from start to end
|
|
3021
|
-
// todo: a way to get the exact hit point, it must still
|
|
3022
|
-
const
|
|
3023
|
-
const
|
|
3024
|
-
const
|
|
3025
|
-
|
|
3026
|
-
|
|
3133
|
+
// todo: a way to get the exact hit point, it must still be inside the hit tile
|
|
3134
|
+
const delta = posEnd.subtract(posStart);
|
|
3135
|
+
const totalLength = delta.length();
|
|
3136
|
+
const normalizedDelta = delta.normalize();
|
|
3137
|
+
const unit = vec2(abs(1/normalizedDelta.x), abs(1/normalizedDelta.y));
|
|
3138
|
+
const flooredPosStart = posStart.floor();
|
|
3139
|
+
|
|
3140
|
+
// setup iteration variables
|
|
3141
|
+
let pos = flooredPosStart;
|
|
3142
|
+
let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
|
|
3143
|
+
let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
|
|
3144
|
+
|
|
3145
|
+
while (1)
|
|
3027
3146
|
{
|
|
3028
|
-
|
|
3029
|
-
|
|
3147
|
+
// check for tile collision
|
|
3148
|
+
const tileData = getTileCollisionData(pos);
|
|
3149
|
+
if (tileData && (!object || object.collideWithTile(tileData, pos)))
|
|
3030
3150
|
{
|
|
3031
|
-
debugRaycast && debugLine(posStart, posEnd, '#f00'
|
|
3032
|
-
debugRaycast && debugPoint(
|
|
3033
|
-
return
|
|
3151
|
+
debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
|
|
3152
|
+
debugRaycast && debugPoint(pos.add(vec2(.5)), '#ff0');
|
|
3153
|
+
return pos.add(vec2(.5));
|
|
3034
3154
|
}
|
|
3035
3155
|
|
|
3036
|
-
//
|
|
3037
|
-
if (
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3156
|
+
// check if past the end
|
|
3157
|
+
if (xi > totalLength && yi > totalLength)
|
|
3158
|
+
break;
|
|
3159
|
+
|
|
3160
|
+
// get coordinates of the next tile to check
|
|
3161
|
+
if (xi > yi)
|
|
3162
|
+
pos.y += sign(delta.y), yi += unit.y;
|
|
3163
|
+
else
|
|
3164
|
+
pos.x += sign(delta.x), xi += unit.x;
|
|
3041
3165
|
}
|
|
3042
|
-
|
|
3166
|
+
|
|
3167
|
+
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|
|
3043
3168
|
}
|
|
3044
3169
|
|
|
3045
3170
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3282,7 +3407,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
|
|
|
3282
3407
|
* @param {Number} [angle=0] */
|
|
3283
3408
|
drawRect(pos, size, color, angle)
|
|
3284
3409
|
{ this.drawTile(pos, size, -1, 0, color, angle); }
|
|
3285
|
-
}
|
|
3410
|
+
}
|
|
3286
3411
|
/**
|
|
3287
3412
|
* LittleJS Particle System
|
|
3288
3413
|
*/
|
|
@@ -3370,7 +3495,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3370
3495
|
localSpace
|
|
3371
3496
|
)
|
|
3372
3497
|
{
|
|
3373
|
-
super(pos,
|
|
3498
|
+
super(pos, vec2(), tileIndex, tileSize, angle, undefined, renderOrder);
|
|
3374
3499
|
|
|
3375
3500
|
// emitter settings
|
|
3376
3501
|
/** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
@@ -3437,7 +3562,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3437
3562
|
this.parent && super.update();
|
|
3438
3563
|
|
|
3439
3564
|
// update emitter
|
|
3440
|
-
if (!this.emitTime
|
|
3565
|
+
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
3441
3566
|
{
|
|
3442
3567
|
// emit particles
|
|
3443
3568
|
if (this.emitRate * particleEmitRateScale)
|
|
@@ -3459,7 +3584,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3459
3584
|
{
|
|
3460
3585
|
// spawn a particle
|
|
3461
3586
|
let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
|
|
3462
|
-
(
|
|
3587
|
+
vec2(rand(-.5,.5), rand(-.5,.5))
|
|
3463
3588
|
.multiply(this.emitSize).rotate(this.angle) // box emitter
|
|
3464
3589
|
: randInCircle(this.emitSize/2); // circle emitter
|
|
3465
3590
|
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
@@ -3489,7 +3614,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3489
3614
|
// build particle settings
|
|
3490
3615
|
particle.colorStart = colorStart;
|
|
3491
3616
|
particle.colorEndDelta = colorEnd.subtract(colorStart);
|
|
3492
|
-
particle.velocity = (
|
|
3617
|
+
particle.velocity = vec2().setAngle(velocityAngle, speed);
|
|
3493
3618
|
particle.angleVelocity = angleSpeed;
|
|
3494
3619
|
particle.lifeTime = particleTime;
|
|
3495
3620
|
particle.sizeStart = sizeStart;
|
|
@@ -3534,7 +3659,7 @@ class Particle extends EngineObject
|
|
|
3534
3659
|
* @param {Number} [angle=0] - Angle to rotate the particle
|
|
3535
3660
|
*/
|
|
3536
3661
|
constructor(pos, tileIndex, tileSize, angle)
|
|
3537
|
-
{ super(pos,
|
|
3662
|
+
{ super(pos, vec2(), tileIndex, tileSize, angle); }
|
|
3538
3663
|
|
|
3539
3664
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
3540
3665
|
render()
|
|
@@ -3542,7 +3667,7 @@ class Particle extends EngineObject
|
|
|
3542
3667
|
// modulate size and color
|
|
3543
3668
|
const p = min((time - this.spawnTime) / this.lifeTime, 1);
|
|
3544
3669
|
const radius = this.sizeStart + p * this.sizeEndDelta;
|
|
3545
|
-
const size =
|
|
3670
|
+
const size = vec2(radius);
|
|
3546
3671
|
const fadeRate = this.fadeRate/2;
|
|
3547
3672
|
const color = new Color(
|
|
3548
3673
|
this.colorStart.r + p * this.colorEndDelta.r,
|
|
@@ -3588,7 +3713,7 @@ class Particle extends EngineObject
|
|
|
3588
3713
|
this.destroyed = 1;
|
|
3589
3714
|
}
|
|
3590
3715
|
}
|
|
3591
|
-
}
|
|
3716
|
+
}
|
|
3592
3717
|
/**
|
|
3593
3718
|
* LittleJS Medal System
|
|
3594
3719
|
* - Tracks and displays medals
|
|
@@ -3909,7 +4034,7 @@ class Newgrounds
|
|
|
3909
4034
|
// end of Crypto-JS
|
|
3910
4035
|
///////////////////////////////////////////////////////////////////////////////
|
|
3911
4036
|
}
|
|
3912
|
-
}
|
|
4037
|
+
}
|
|
3913
4038
|
/**
|
|
3914
4039
|
* LittleJS WebGL Interface
|
|
3915
4040
|
* - All webgl used by the engine is wrapped up here
|
|
@@ -4115,8 +4240,8 @@ function glFlush()
|
|
|
4115
4240
|
|
|
4116
4241
|
// draw all the sprites in the batch and reset the buffer
|
|
4117
4242
|
glContext.bufferSubData(gl_ARRAY_BUFFER, 0,
|
|
4118
|
-
glPositionData.subarray(0, glBatchCount *
|
|
4119
|
-
glContext.drawArrays(
|
|
4243
|
+
glPositionData.subarray(0, glBatchCount * gl_INDICIES_PER_VERT));
|
|
4244
|
+
glContext.drawArrays(gl_TRIANGLE_STRIP, 0, glBatchCount);
|
|
4120
4245
|
glBatchCount = 0;
|
|
4121
4246
|
glBatchAdditive = glAdditive;
|
|
4122
4247
|
}
|
|
@@ -4137,39 +4262,75 @@ function glCopyToContext(context, forceDraw)
|
|
|
4137
4262
|
}
|
|
4138
4263
|
|
|
4139
4264
|
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
4140
|
-
* @param x
|
|
4141
|
-
* @param y
|
|
4142
|
-
* @param sizeX
|
|
4143
|
-
* @param sizeY
|
|
4144
|
-
* @param angle
|
|
4145
|
-
* @param uv0X
|
|
4146
|
-
* @param uv0Y
|
|
4147
|
-
* @param uv1X
|
|
4148
|
-
* @param uv1Y
|
|
4149
|
-
* @param rgba
|
|
4150
|
-
* @param [rgbaAdditive=0]
|
|
4265
|
+
* @param {Number} x
|
|
4266
|
+
* @param {Number} y
|
|
4267
|
+
* @param {Number} sizeX
|
|
4268
|
+
* @param {Number} sizeY
|
|
4269
|
+
* @param {Number} angle
|
|
4270
|
+
* @param {Number} uv0X
|
|
4271
|
+
* @param {Number} uv0Y
|
|
4272
|
+
* @param {Number} uv1X
|
|
4273
|
+
* @param {Number} uv1Y
|
|
4274
|
+
* @param {Number} rgba
|
|
4275
|
+
* @param {Number} [rgbaAdditive=0]
|
|
4151
4276
|
* @memberof WebGL */
|
|
4152
4277
|
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
|
|
4153
4278
|
{
|
|
4154
|
-
// flush if there is
|
|
4155
|
-
|
|
4279
|
+
// flush if there is not enough room or if different blend mode
|
|
4280
|
+
const vertCount = 6;
|
|
4281
|
+
if (glBatchCount >= gl_MAX_BATCH-vertCount || glBatchAdditive != glAdditive)
|
|
4156
4282
|
glFlush();
|
|
4157
4283
|
|
|
4158
4284
|
// prepare to create the verts from size and angle
|
|
4159
4285
|
const c = Math.cos(angle)/2, s = Math.sin(angle)/2;
|
|
4160
4286
|
const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4287
|
+
const positionData =
|
|
4288
|
+
[
|
|
4289
|
+
x-cx+sy, y+cy+sx, uv0X, uv0Y,
|
|
4290
|
+
x-cx-sy, y-cy+sx, uv0X, uv1Y,
|
|
4291
|
+
x+cx+sy, y+cy-sx, uv1X, uv0Y,
|
|
4292
|
+
x+cx-sy, y-cy-sx, uv1X, uv1Y,
|
|
4293
|
+
];
|
|
4294
|
+
|
|
4295
|
+
// setup 2 triangle strip quad
|
|
4296
|
+
for(let i = vertCount, offset = glBatchCount * gl_INDICIES_PER_VERT; i--;)
|
|
4164
4297
|
{
|
|
4165
|
-
|
|
4166
|
-
glPositionData[offset++] =
|
|
4167
|
-
glPositionData[offset++] =
|
|
4168
|
-
glPositionData[offset++] =
|
|
4169
|
-
glPositionData[offset++] =
|
|
4298
|
+
let j = clamp(i-1, 0, 3)*4; // degenerate tri at ends
|
|
4299
|
+
glPositionData[offset++] = positionData[j++];
|
|
4300
|
+
glPositionData[offset++] = positionData[j++];
|
|
4301
|
+
glPositionData[offset++] = positionData[j++];
|
|
4302
|
+
glPositionData[offset++] = positionData[j++];
|
|
4170
4303
|
glColorData[offset++] = rgba;
|
|
4171
4304
|
glColorData[offset++] = rgbaAdditive;
|
|
4172
4305
|
}
|
|
4306
|
+
glBatchCount += vertCount;
|
|
4307
|
+
}
|
|
4308
|
+
|
|
4309
|
+
/** Add a convex polygon to the gl draw list
|
|
4310
|
+
* @param {Array} points - Array of Vector2 points
|
|
4311
|
+
* @param {Number} rgba - Color of the polygon
|
|
4312
|
+
* @memberof WebGL */
|
|
4313
|
+
function glDrawPoints(points, rgba)
|
|
4314
|
+
{
|
|
4315
|
+
// flush if there is not enough room or if different blend mode
|
|
4316
|
+
const vertCount = points.length + 2;
|
|
4317
|
+
if (glBatchCount >= gl_MAX_BATCH-vertCount || glBatchAdditive != glAdditive)
|
|
4318
|
+
glFlush();
|
|
4319
|
+
|
|
4320
|
+
// setup triangle strip from list of points
|
|
4321
|
+
for(let i = vertCount, offset = glBatchCount * gl_INDICIES_PER_VERT; i--;)
|
|
4322
|
+
{
|
|
4323
|
+
const j = clamp(i-1, 0, vertCount-3); // degenerate tri at ends
|
|
4324
|
+
const h = j>>1;
|
|
4325
|
+
const point = points[j%2? h : vertCount-3-h];
|
|
4326
|
+
glPositionData[offset++] = point.x;
|
|
4327
|
+
glPositionData[offset++] = point.y;
|
|
4328
|
+
glPositionData[offset++] = 0; // uvx
|
|
4329
|
+
glPositionData[offset++] = 0; // uvy
|
|
4330
|
+
glColorData[offset++] = 0; // nothing to tint
|
|
4331
|
+
glColorData[offset++] = rgba; // apply rgba via additive
|
|
4332
|
+
}
|
|
4333
|
+
glBatchCount += vertCount;
|
|
4173
4334
|
}
|
|
4174
4335
|
|
|
4175
4336
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -4263,14 +4424,14 @@ function glRenderPostProcess()
|
|
|
4263
4424
|
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
4264
4425
|
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
4265
4426
|
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
4266
|
-
glContext.drawArrays(
|
|
4427
|
+
glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 3);
|
|
4267
4428
|
}
|
|
4268
4429
|
|
|
4269
4430
|
///////////////////////////////////////////////////////////////////////////////
|
|
4270
4431
|
// store gl constants as integers so their name doesn't use space in minifed
|
|
4271
4432
|
const
|
|
4272
4433
|
gl_ONE = 1,
|
|
4273
|
-
|
|
4434
|
+
gl_TRIANGLE_STRIP = 5,
|
|
4274
4435
|
gl_SRC_ALPHA = 770,
|
|
4275
4436
|
gl_ONE_MINUS_SRC_ALPHA = 771,
|
|
4276
4437
|
gl_BLEND = 3042,
|
|
@@ -4301,9 +4462,9 @@ gl_UNPACK_FLIP_Y_WEBGL = 37440,
|
|
|
4301
4462
|
// constants for batch rendering
|
|
4302
4463
|
gl_VERTICES_PER_QUAD = 6,
|
|
4303
4464
|
gl_INDICIES_PER_VERT = 6,
|
|
4304
|
-
gl_MAX_BATCH =
|
|
4465
|
+
gl_MAX_BATCH = 1e5,
|
|
4305
4466
|
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
|
|
4306
|
-
gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH *
|
|
4467
|
+
gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTEX_BYTE_STRIDE;
|
|
4307
4468
|
/**
|
|
4308
4469
|
* LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
4309
4470
|
* MIT License - Copyright 2021 Frank Force
|
|
@@ -4336,7 +4497,7 @@ const engineName = 'LittleJS';
|
|
|
4336
4497
|
* @type {String}
|
|
4337
4498
|
* @default
|
|
4338
4499
|
* @memberof Engine */
|
|
4339
|
-
const engineVersion = '1.
|
|
4500
|
+
const engineVersion = '1.7.01';
|
|
4340
4501
|
|
|
4341
4502
|
/** Frames per second to update objects
|
|
4342
4503
|
* @type {Number}
|
|
@@ -4426,7 +4587,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4426
4587
|
// set canvas style
|
|
4427
4588
|
const styleCanvas = 'position:absolute;' +
|
|
4428
4589
|
'top:50%;left:50%;transform:translate(-50%,-50%);' + // center the canvas
|
|
4429
|
-
(canvasPixelated?'image-rendering:pixelated':'');
|
|
4590
|
+
(canvasPixelated?'image-rendering:pixelated':''); // set pixelated rendering
|
|
4430
4591
|
(glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
|
|
4431
4592
|
|
|
4432
4593
|
gameInit();
|
|
@@ -4434,7 +4595,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4434
4595
|
};
|
|
4435
4596
|
|
|
4436
4597
|
// frame time tracking
|
|
4437
|
-
let frameTimeLastMS = 0, frameTimeBufferMS, averageFPS;
|
|
4598
|
+
let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
4438
4599
|
|
|
4439
4600
|
// main update loop
|
|
4440
4601
|
function engineUpdate(frameTimeMS=0)
|
|
@@ -4625,4 +4786,4 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
|
|
|
4625
4786
|
for (const o of objects)
|
|
4626
4787
|
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
|
4627
4788
|
}
|
|
4628
|
-
}
|
|
4789
|
+
}
|