littlejsengine 1.1.2 → 1.1.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/README.md +55 -0
- package/build.bat +2 -1
- package/engine/engine.all.js +1320 -930
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.release.js +965 -601
- package/engine/engine.js +71 -60
- package/engine/engineAudio.js +80 -22
- package/engine/engineBuild.bat +2 -2
- package/engine/engineDebug.js +65 -36
- package/engine/engineDraw.js +71 -56
- package/engine/engineInput.js +178 -28
- package/engine/engineMedals.js +66 -58
- package/engine/engineObject.js +86 -71
- package/engine/engineParticles.js +101 -63
- package/engine/engineRelease.js +4 -1
- package/engine/engineSettings.js +62 -53
- package/engine/engineTileLayer.js +99 -83
- package/engine/engineUtilities.js +95 -59
- package/engine/engineWebGL.js +53 -48
- package/examples/breakout/game.js +1 -1
- package/examples/breakout/gameObjects.js +2 -2
- package/examples/breakout/index.html +7 -5
- package/examples/platformer/game.js +7 -4
- package/examples/platformer/gameEffects.js +8 -11
- package/examples/platformer/gameLevel.js +15 -8
- package/examples/platformer/gameObjects.js +2 -2
- package/examples/platformer/gamePlayer.js +6 -6
- package/examples/platformer/index.html +10 -8
- package/examples/puzzle/game.js +3 -3
- package/examples/puzzle/index.html +6 -4
- package/examples/stress/index.html +7 -5
- package/game.js +10 -10
- package/index.html +17 -15
- package/package.json +4 -7
- package/tiles.png +0 -0
- package/LittleJS.zip +0 -0
- package/docs/Audio.html +0 -2264
- package/docs/Color.html +0 -2506
- package/docs/Debug.html +0 -2929
- package/docs/Draw.html +0 -3854
- package/docs/EngineObject.html +0 -2506
- package/docs/Input.html +0 -2478
- package/docs/Medal.html +0 -992
- package/docs/Medals.html +0 -624
- package/docs/Music.html +0 -569
- package/docs/Newgrounds.html +0 -1384
- package/docs/Particle.html +0 -567
- package/docs/ParticleEmitter.html +0 -1537
- package/docs/Random.html +0 -1770
- package/docs/Settings.html +0 -2885
- package/docs/Sound.html +0 -998
- package/docs/TileLayer.html +0 -3211
- package/docs/TileLayerData.html +0 -569
- package/docs/Timer.html +0 -1168
- package/docs/Utilities.html +0 -3266
- package/docs/Vector2.html +0 -3956
- package/docs/WebGL.html +0 -1842
- package/docs/engine.js.html +0 -428
- package/docs/engineAudio.js.html +0 -567
- package/docs/engineDebug.js.html +0 -721
- package/docs/engineDraw.js.html +0 -409
- package/docs/engineInput.js.html +0 -397
- package/docs/engineMedals.js.html +0 -445
- package/docs/engineObject.js.html +0 -492
- package/docs/engineParticles.js.html +0 -399
- package/docs/engineSettings.js.html +0 -358
- package/docs/engineTileLayer.js.html +0 -482
- package/docs/engineUtilities.js.html +0 -600
- package/docs/engineWebGL.js.html +0 -507
- package/docs/fonts/MavenPro-Regular.ttf +0 -0
- package/docs/fonts/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Muli-Black.ttf +0 -0
- package/docs/fonts/OFL-hind.txt +0 -93
- package/docs/fonts/OFL-montserrat.txt +0 -93
- package/docs/global.html +0 -1635
- package/docs/index.html +0 -243
- package/docs/scripts/fix-code-block.js +0 -53
- package/docs/scripts/fix-navbar.js +0 -25
- package/docs/scripts/linenumber.js +0 -25
- package/docs/scripts/misc.js +0 -217
- package/docs/scripts/resize.js +0 -85
- package/docs/scripts/search.js +0 -83
- package/docs/scripts/third-party/Apache-License-2.0.txt +0 -202
- package/docs/scripts/third-party/fuse.js +0 -9
- package/docs/scripts/third-party/lang-css.js +0 -2
- package/docs/scripts/third-party/prettify.js +0 -28
- package/docs/static/favicon.png +0 -0
- package/docs/styles/clean-jsdoc-theme-base.css +0 -393
- package/docs/styles/clean-jsdoc-theme-dark.css +0 -324
- package/docs/styles/clean-jsdoc-theme-light.css +0 -319
- package/docs/styles/reset.css +0 -346
- package/docs/styles/third-party/ionicons.min.css +0 -11
- package/docs/styles/third-party/prettify-jsdoc.css +0 -111
- package/docs/styles/third-party/prettify-tomorrow.css +0 -5
package/engine/engine.all.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
LittleJS - Debug Build
|
|
3
|
+
MIT License - Copyright 2021 Frank Force
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
+
* LittleJS Debug System
|
|
8
|
+
* <br> - Press ~ to show debug overlay with mouse pick
|
|
9
|
+
* <br> - Number keys toggle debug functions
|
|
10
|
+
* <br> - +/- apply time scale
|
|
11
|
+
* <br> - Debug primitive rendering
|
|
12
|
+
* <br> - Save a 2d canvas as an image
|
|
13
|
+
* @namespace Debug
|
|
7
14
|
*/
|
|
8
15
|
|
|
9
16
|
'use strict';
|
|
@@ -34,7 +41,7 @@ let showWatermark = 1;
|
|
|
34
41
|
let godMode = 0;
|
|
35
42
|
|
|
36
43
|
// Engine internal variables not exposed to documentation
|
|
37
|
-
let debugPrimitives = [], debugOverlay = 0, debugPhysics = 0, debugRaycast = 0,
|
|
44
|
+
let debugPrimitives = [], debugOverlay = 0, debugPhysics = 0, debugRaycast = 0,
|
|
38
45
|
debugParticles = 0, debugGamepads = 0, debugMedals = 0, debugTakeScreenshot, downloadLink;
|
|
39
46
|
|
|
40
47
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -156,22 +163,25 @@ const debugUpdate = ()=>
|
|
|
156
163
|
|
|
157
164
|
if (keyWasPressed(192)) // ~
|
|
158
165
|
debugOverlay = !debugOverlay;
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
166
|
+
if (debugOverlay)
|
|
167
|
+
{
|
|
168
|
+
if (keyWasPressed(49)) // 1
|
|
169
|
+
debugPhysics = !debugPhysics, debugParticles = 0;
|
|
170
|
+
if (keyWasPressed(50)) // 2
|
|
171
|
+
debugParticles = !debugParticles, debugPhysics = 0;
|
|
172
|
+
if (keyWasPressed(51)) // 3
|
|
173
|
+
godMode = !godMode;
|
|
174
|
+
if (keyWasPressed(53)) // 5
|
|
175
|
+
debugTakeScreenshot = 1;
|
|
176
|
+
//if (keyWasPressed(54)) // 6
|
|
177
|
+
// debugToggleParticleEditor();
|
|
178
|
+
if (keyWasPressed(55)) // 7
|
|
179
|
+
debugGamepads = !debugGamepads;
|
|
180
|
+
//if (keyWasPressed(56)) // 8
|
|
181
|
+
//if (keyWasPressed(57)) // 9
|
|
182
|
+
if (keyWasPressed(48)) // 0
|
|
183
|
+
showWatermark = !showWatermark;
|
|
184
|
+
}
|
|
175
185
|
}
|
|
176
186
|
|
|
177
187
|
const debugRender = ()=>
|
|
@@ -224,12 +234,24 @@ const debugRender = ()=>
|
|
|
224
234
|
|
|
225
235
|
if (debugOverlay)
|
|
226
236
|
{
|
|
237
|
+
// mouse pick
|
|
238
|
+
let bestDistance = Infinity, bestObject;
|
|
227
239
|
for (const o of engineObjects)
|
|
228
240
|
{
|
|
229
|
-
if (o.canvas)
|
|
241
|
+
if (o.canvas || o.destroyed)
|
|
230
242
|
continue; // skip tile layers
|
|
231
243
|
|
|
232
244
|
const size = o.size.copy();
|
|
245
|
+
if (!size.x || !size.y)
|
|
246
|
+
continue;
|
|
247
|
+
|
|
248
|
+
const distance = mousePos.distanceSquared(o.pos);
|
|
249
|
+
if (distance < bestDistance)
|
|
250
|
+
{
|
|
251
|
+
bestDistance = distance;
|
|
252
|
+
bestObject = o
|
|
253
|
+
}
|
|
254
|
+
|
|
233
255
|
size.x = max(size.x, .2);
|
|
234
256
|
size.y = max(size.y, .2);
|
|
235
257
|
|
|
@@ -244,18 +266,6 @@ const debugRender = ()=>
|
|
|
244
266
|
drawRect(o.pos, size.scale(.8), o.parent ? new Color(1,1,1,.5) : new Color(0,0,0,.8));
|
|
245
267
|
o.parent && drawLine(o.pos, o.parent.pos, .1, new Color(0,0,1,.5));
|
|
246
268
|
}
|
|
247
|
-
|
|
248
|
-
// mouse pick
|
|
249
|
-
let bestDistance = Infinity, bestObject;
|
|
250
|
-
for (const o of engineObjects)
|
|
251
|
-
{
|
|
252
|
-
const distance = mousePos.distanceSquared(o.pos);
|
|
253
|
-
if (distance < bestDistance)
|
|
254
|
-
{
|
|
255
|
-
bestDistance = distance;
|
|
256
|
-
bestObject = o
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
269
|
|
|
260
270
|
if (bestObject)
|
|
261
271
|
{
|
|
@@ -275,7 +285,8 @@ const debugRender = ()=>
|
|
|
275
285
|
pos = pos.add(height), ...args);
|
|
276
286
|
drawText('vel = ' + printVec2(bestObject.velocity), pos = pos.add(height), ...args);
|
|
277
287
|
drawText('size = ' + printVec2(bestObject.size), pos = pos.add(height), ...args);
|
|
278
|
-
drawText('
|
|
288
|
+
drawText('type = ' + ( bestObject.constructor.name), pos = mousePos.subtract(height), ...args);
|
|
289
|
+
drawText('collision = ' + getTileCollisionData(mousePos), pos = mousePos.subtract(height.scale(2)), ...args);
|
|
279
290
|
mainContext = saveContext;
|
|
280
291
|
}
|
|
281
292
|
|
|
@@ -361,6 +372,24 @@ const debugRender = ()=>
|
|
|
361
372
|
//overlayContext.fillText('6: Particle Editor', x, y += h);
|
|
362
373
|
overlayContext.fillStyle = debugGamepads ? '#f00' : '#fff';
|
|
363
374
|
overlayContext.fillText('7: Debug Gamepads', x, y += h);
|
|
375
|
+
|
|
376
|
+
let keysPressed = '';
|
|
377
|
+
for(const i in inputData[0])
|
|
378
|
+
{
|
|
379
|
+
if (i && keyIsDown(i, 0))
|
|
380
|
+
keysPressed += i + ' ' ;
|
|
381
|
+
}
|
|
382
|
+
keysPressed && overlayContext.fillText('Keys Down: ' + keysPressed, x, y += h);
|
|
383
|
+
|
|
384
|
+
let buttonsPressed = '';
|
|
385
|
+
if (inputData[1])
|
|
386
|
+
for(const i in inputData[1])
|
|
387
|
+
{
|
|
388
|
+
if (i && keyIsDown(i, 1))
|
|
389
|
+
buttonsPressed += i + ' ' ;
|
|
390
|
+
}
|
|
391
|
+
buttonsPressed && overlayContext.fillText('Gamepad: ' + buttonsPressed, x, y += h);
|
|
392
|
+
|
|
364
393
|
}
|
|
365
394
|
else
|
|
366
395
|
{
|
|
@@ -565,12 +594,12 @@ const debugParticleSettings =
|
|
|
565
594
|
['renderOrder'],
|
|
566
595
|
];
|
|
567
596
|
/**
|
|
568
|
-
*
|
|
569
|
-
*
|
|
570
|
-
*
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
597
|
+
* LittleJS Utility Classes and Functions
|
|
598
|
+
* <br> - General purpose math library
|
|
599
|
+
* <br> - Vector2 - fast, simple, easy 2D vector class
|
|
600
|
+
* <br> - Color - holds a rgba color with some math functions
|
|
601
|
+
* <br> - Timer - tracks time automatically
|
|
602
|
+
* @namespace Utilities
|
|
574
603
|
*/
|
|
575
604
|
|
|
576
605
|
'use strict';
|
|
@@ -580,23 +609,12 @@ const debugParticleSettings =
|
|
|
580
609
|
* @memberof Utilities */
|
|
581
610
|
const PI = Math.PI;
|
|
582
611
|
|
|
583
|
-
/** True if running a Chromium based browser
|
|
584
|
-
* @const
|
|
585
|
-
* @memberof Utilities */
|
|
586
|
-
const isChrome = window['chrome'];
|
|
587
|
-
|
|
588
612
|
/** Returns absoulte value of value passed in
|
|
589
613
|
* @param {Number} value
|
|
590
614
|
* @return {Number}
|
|
591
615
|
* @memberof Utilities */
|
|
592
616
|
const abs = (a)=> a < 0 ? -a : a;
|
|
593
617
|
|
|
594
|
-
/** Returns the sign of value passed in
|
|
595
|
-
* @param {Number} value
|
|
596
|
-
* @return {Number}
|
|
597
|
-
* @memberof Utilities */
|
|
598
|
-
const sign = (a)=> a < 0 ? -1 : 1;
|
|
599
|
-
|
|
600
618
|
/** Returns lowest of two values passed in
|
|
601
619
|
* @param {Number} valueA
|
|
602
620
|
* @param {Number} valueB
|
|
@@ -611,54 +629,54 @@ const min = (a, b)=> a < b ? a : b;
|
|
|
611
629
|
* @memberof Utilities */
|
|
612
630
|
const max = (a, b)=> a > b ? a : b;
|
|
613
631
|
|
|
632
|
+
/** Returns the sign of value passed in (also returns 1 if 0)
|
|
633
|
+
* @param {Number} value
|
|
634
|
+
* @return {Number}
|
|
635
|
+
* @memberof Utilities */
|
|
636
|
+
const sign = (a)=> a < 0 ? -1 : 1;
|
|
637
|
+
|
|
614
638
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
615
639
|
* @param {Number} dividend
|
|
616
|
-
* @param {Number} divisor
|
|
640
|
+
* @param {Number} [divisor=1]
|
|
617
641
|
* @return {Number}
|
|
618
642
|
* @memberof Utilities */
|
|
619
|
-
const mod = (a, b)=> ((a % b) + b) % b;
|
|
643
|
+
const mod = (a, b=1)=> ((a % b) + b) % b;
|
|
620
644
|
|
|
621
645
|
/** Clamps the value beween max and min
|
|
622
646
|
* @param {Number} value
|
|
623
|
-
* @param {Number} [max=1]
|
|
624
647
|
* @param {Number} [min=0]
|
|
648
|
+
* @param {Number} [max=1]
|
|
625
649
|
* @return {Number}
|
|
626
650
|
* @memberof Utilities */
|
|
627
|
-
const clamp = (v,
|
|
651
|
+
const clamp = (v, min=0, max=1)=> v < min ? min : v > max ? max : v;
|
|
628
652
|
|
|
629
653
|
/** Returns what percentage the value is between max and min
|
|
630
654
|
* @param {Number} value
|
|
631
|
-
* @param {Number} [max=1]
|
|
632
655
|
* @param {Number} [min=0]
|
|
656
|
+
* @param {Number} [max=1]
|
|
633
657
|
* @return {Number}
|
|
634
658
|
* @memberof Utilities */
|
|
635
|
-
const percent = (v,
|
|
659
|
+
const percent = (v, min=0, max=1)=> max-min ? clamp((v-min) / (max-min)) : 0;
|
|
636
660
|
|
|
637
661
|
/** Linearly interpolates the percent value between max and min
|
|
638
662
|
* @param {Number} percent
|
|
639
|
-
* @param {Number} [max=1]
|
|
640
663
|
* @param {Number} [min=0]
|
|
664
|
+
* @param {Number} [max=1]
|
|
641
665
|
* @return {Number}
|
|
642
666
|
* @memberof Utilities */
|
|
643
|
-
const lerp = (p,
|
|
644
|
-
|
|
645
|
-
/** Formats seconds to 00:00 style for display purposes
|
|
646
|
-
* @param {Number} t - time in seconds
|
|
647
|
-
* @return {String}
|
|
648
|
-
* @memberof Utilities */
|
|
649
|
-
const formatTime = (t)=> (t/60|0)+':'+(t%60<10?'0':'')+(t%60|0);
|
|
667
|
+
const lerp = (p, min=0, max=1)=> min + clamp(p) * (max-min);
|
|
650
668
|
|
|
651
|
-
/**
|
|
669
|
+
/** Applies smoothstep function to the percentage value
|
|
652
670
|
* @param {Number} value
|
|
653
671
|
* @return {Number}
|
|
654
672
|
* @memberof Utilities */
|
|
655
|
-
const
|
|
673
|
+
const smoothStep = (p)=> p * p * (3 - 2 * p);
|
|
656
674
|
|
|
657
|
-
/**
|
|
675
|
+
/** Returns the nearest power of two not less then the value
|
|
658
676
|
* @param {Number} value
|
|
659
677
|
* @return {Number}
|
|
660
678
|
* @memberof Utilities */
|
|
661
|
-
const
|
|
679
|
+
const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
|
|
662
680
|
|
|
663
681
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
664
682
|
* @param {Vector2} pointA - Center of box A
|
|
@@ -677,6 +695,12 @@ const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x & abs(
|
|
|
677
695
|
* @memberof Utilities */
|
|
678
696
|
const wave = (frequency=1, amplitude=1, t=time)=> amplitude/2 * (1 - Math.cos(t*frequency*2*PI));
|
|
679
697
|
|
|
698
|
+
/** Formats seconds to mm:ss style for display purposes
|
|
699
|
+
* @param {Number} t - time in seconds
|
|
700
|
+
* @return {String}
|
|
701
|
+
* @memberof Utilities */
|
|
702
|
+
const formatTime = (t)=> (t/60|0)+':'+(t%60<10?'0':'')+(t%60|0);
|
|
703
|
+
|
|
680
704
|
///////////////////////////////////////////////////////////////////////////////
|
|
681
705
|
|
|
682
706
|
/** Random global functions
|
|
@@ -699,7 +723,7 @@ const randInt = (a=1, b=0)=> rand(a,b)|0;
|
|
|
699
723
|
/** Randomly returns either -1 or 1
|
|
700
724
|
* @return {Number}
|
|
701
725
|
* @memberof Random */
|
|
702
|
-
const randSign = ()=> (rand(2)|0)*2-1;
|
|
726
|
+
const randSign = ()=> (rand(2)|0) * 2 - 1;
|
|
703
727
|
|
|
704
728
|
/** Returns a random Vector2 within a circular shape
|
|
705
729
|
* @param {Number} [radius=1]
|
|
@@ -735,25 +759,46 @@ let randSeed = 1;
|
|
|
735
759
|
const randSeeded = (a=1, b=0)=>
|
|
736
760
|
{
|
|
737
761
|
randSeed ^= randSeed << 13; randSeed ^= randSeed >>> 17; randSeed ^= randSeed << 5; // xorshift
|
|
738
|
-
return b + (a-b)*abs(randSeed % 1e9)/1e9;
|
|
762
|
+
return b + (a-b) * abs(randSeed % 1e9) / 1e9;
|
|
739
763
|
}
|
|
740
764
|
|
|
741
765
|
///////////////////////////////////////////////////////////////////////////////
|
|
742
766
|
|
|
743
|
-
/**
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
767
|
+
/**
|
|
768
|
+
* Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
|
|
769
|
+
* @param {Number} [x=0]
|
|
770
|
+
* @param {Number} [y=0]
|
|
771
|
+
* @return {Vector2}
|
|
772
|
+
* @example
|
|
773
|
+
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
774
|
+
* let b = vec2(a); // copy a into b
|
|
775
|
+
* a = vec2(5); // set a to (5, 5)
|
|
776
|
+
* b = vec2(); // set b to (0, 0)
|
|
777
|
+
* @memberof Utilities
|
|
778
|
+
*/
|
|
748
779
|
const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
|
|
749
780
|
|
|
750
|
-
/**
|
|
781
|
+
/**
|
|
782
|
+
* 2D Vector object with vector math library
|
|
783
|
+
* <br> - Functions do not change this so they can be chained together
|
|
784
|
+
* @example
|
|
785
|
+
* let a = new Vector2(2, 3); // vector with coordinates (2, 3)
|
|
786
|
+
* let b = new Vector2; // vector with coordinates (0, 0)
|
|
787
|
+
* let c = vec2(4, 2); // use the vec2 function to make a Vector2
|
|
788
|
+
* let d = a.add(b).scale(5); // operators can be chained
|
|
789
|
+
*/
|
|
751
790
|
class Vector2
|
|
752
791
|
{
|
|
753
792
|
/** Create a 2D vector with the x and y passed in, can also be created with vec2()
|
|
754
|
-
* @param {Number} [x=0] -
|
|
755
|
-
* @param {Number} [y=0] -
|
|
756
|
-
constructor(x=0, y=0)
|
|
793
|
+
* @param {Number} [x=0] - X axis location
|
|
794
|
+
* @param {Number} [y=0] - Y axis location */
|
|
795
|
+
constructor(x=0, y=0)
|
|
796
|
+
{
|
|
797
|
+
/** @property {Number} - X axis location */
|
|
798
|
+
this.x = x;
|
|
799
|
+
/** @property {Number} - Y axis location */
|
|
800
|
+
this.y = y;
|
|
801
|
+
}
|
|
757
802
|
|
|
758
803
|
/** Returns a new vector that is a copy of this
|
|
759
804
|
* @return {Vector2} */
|
|
@@ -844,10 +889,6 @@ class Vector2
|
|
|
844
889
|
* @return {Vector2} */
|
|
845
890
|
invert() { return new Vector2(this.y, -this.x); }
|
|
846
891
|
|
|
847
|
-
/** Returns a copy of this vector with the axies flipped
|
|
848
|
-
* @return {Vector2} */
|
|
849
|
-
flip() { return new Vector2(this.y, this.x); }
|
|
850
|
-
|
|
851
892
|
/** Returns a copy of this vector with each axis floored
|
|
852
893
|
* @return {Vector2} */
|
|
853
894
|
floor() { return new Vector2(Math.floor(this.x), Math.floor(this.y)); }
|
|
@@ -870,15 +911,31 @@ class Vector2
|
|
|
870
911
|
|
|
871
912
|
///////////////////////////////////////////////////////////////////////////////
|
|
872
913
|
|
|
873
|
-
/**
|
|
914
|
+
/**
|
|
915
|
+
* Color object (red, green, blue, alpha) with some helpful functions
|
|
916
|
+
* @example
|
|
917
|
+
* let a = new Color; // white
|
|
918
|
+
* let b = new Color(1, 0, 0); // red
|
|
919
|
+
* let c = new Color(0, 0, 0, 0); // transparent black
|
|
920
|
+
*/
|
|
874
921
|
class Color
|
|
875
922
|
{
|
|
876
923
|
/** Create a color with the components passed in, white by default
|
|
877
|
-
* @param {Number} [
|
|
878
|
-
* @param {Number} [
|
|
879
|
-
* @param {Number} [
|
|
880
|
-
* @param {Number} [
|
|
881
|
-
constructor(r=1, g=1, b=1, a=1)
|
|
924
|
+
* @param {Number} [red=1]
|
|
925
|
+
* @param {Number} [green=1]
|
|
926
|
+
* @param {Number} [blue=1]
|
|
927
|
+
* @param {Number} [alpha=1] */
|
|
928
|
+
constructor(r=1, g=1, b=1, a=1)
|
|
929
|
+
{
|
|
930
|
+
/** @property {Number} - Red */
|
|
931
|
+
this.r = r;
|
|
932
|
+
/** @property {Number} - Green */
|
|
933
|
+
this.g = g;
|
|
934
|
+
/** @property {Number} - Blue */
|
|
935
|
+
this.b = b;
|
|
936
|
+
/** @property {Number} - Alpha */
|
|
937
|
+
this.a = a;
|
|
938
|
+
}
|
|
882
939
|
|
|
883
940
|
/** Returns a new color that is a copy of this
|
|
884
941
|
* @return {Color} */
|
|
@@ -975,7 +1032,15 @@ class Color
|
|
|
975
1032
|
|
|
976
1033
|
///////////////////////////////////////////////////////////////////////////////
|
|
977
1034
|
|
|
978
|
-
/**
|
|
1035
|
+
/**
|
|
1036
|
+
* Timer object tracks how long has passed since it was set
|
|
1037
|
+
* @example
|
|
1038
|
+
* let a = new Timer; // creates a timer that is not set
|
|
1039
|
+
* a.set(3); // sets the timer to 3 seconds
|
|
1040
|
+
*
|
|
1041
|
+
* let b = new Timer(1); // creates a timer with 1 second left
|
|
1042
|
+
* b.unset(); // unsets the timer
|
|
1043
|
+
*/
|
|
979
1044
|
class Timer
|
|
980
1045
|
{
|
|
981
1046
|
/** Create a timer object set time passed in
|
|
@@ -1001,17 +1066,17 @@ class Timer
|
|
|
1001
1066
|
* @return {Boolean} */
|
|
1002
1067
|
elapsed() { return time > this.time; }
|
|
1003
1068
|
|
|
1004
|
-
/** Get how long since elapsed, returns 0 if not set
|
|
1069
|
+
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
1005
1070
|
* @return {Number} */
|
|
1006
1071
|
get() { return this.isSet()? time - this.time : 0; }
|
|
1007
1072
|
|
|
1008
1073
|
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
1009
1074
|
* @return {Number} */
|
|
1010
|
-
getPercent() { return this.isSet()? percent(this.time - time,
|
|
1075
|
+
getPercent() { return this.isSet()? percent(this.time - time, this.setTime, 0) : 0; }
|
|
1011
1076
|
}
|
|
1012
1077
|
/**
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1078
|
+
* LittleJS Engine Settings
|
|
1079
|
+
* @namespace Settings
|
|
1015
1080
|
*/
|
|
1016
1081
|
|
|
1017
1082
|
'use strict';
|
|
@@ -1019,35 +1084,27 @@ class Timer
|
|
|
1019
1084
|
///////////////////////////////////////////////////////////////////////////////
|
|
1020
1085
|
// Display settings
|
|
1021
1086
|
|
|
1022
|
-
/** The max
|
|
1023
|
-
* @
|
|
1024
|
-
* @memberof Settings */
|
|
1025
|
-
const maxWidth = 1920;
|
|
1026
|
-
|
|
1027
|
-
/** The max height of the canvas, centered if window is larger
|
|
1028
|
-
* @default
|
|
1029
|
-
* @memberof Settings */
|
|
1030
|
-
const maxHeight = 1200; // up to 1080p and 16:10
|
|
1031
|
-
|
|
1032
|
-
/** Fixed witdh, if enabled cavnvas size never changes
|
|
1087
|
+
/** The max size of the canvas, centered if window is larger
|
|
1088
|
+
* @type {Vector2}
|
|
1033
1089
|
* @default
|
|
1034
1090
|
* @memberof Settings */
|
|
1035
|
-
let
|
|
1091
|
+
let canvasMaxSize = vec2(1920, 1200);
|
|
1036
1092
|
|
|
1037
|
-
/** Fixed
|
|
1093
|
+
/** Fixed size of the canvas, if enabled cavnvas size never changes
|
|
1094
|
+
* @type {Vector2}
|
|
1038
1095
|
* @default
|
|
1039
1096
|
* @memberof Settings */
|
|
1040
|
-
let
|
|
1097
|
+
let canvasFixedSize = vec2();
|
|
1041
1098
|
|
|
1042
|
-
/**
|
|
1099
|
+
/** Disables anti aliasing for pixel art if true
|
|
1043
1100
|
* @default
|
|
1044
1101
|
* @memberof Settings */
|
|
1045
|
-
let
|
|
1102
|
+
let cavasPixelated = 1;
|
|
1046
1103
|
|
|
1047
1104
|
/** Default font used for text rendering
|
|
1048
1105
|
* @default
|
|
1049
1106
|
* @memberof Settings */
|
|
1050
|
-
let
|
|
1107
|
+
let fontDefault = 'arial';
|
|
1051
1108
|
|
|
1052
1109
|
///////////////////////////////////////////////////////////////////////////////
|
|
1053
1110
|
// Tile sheet settings
|
|
@@ -1056,17 +1113,12 @@ let defaultFont = 'arial';
|
|
|
1056
1113
|
* @type {Vector2}
|
|
1057
1114
|
* @default
|
|
1058
1115
|
* @memberof Settings */
|
|
1059
|
-
|
|
1116
|
+
let tileSizeDefault = vec2(16);
|
|
1060
1117
|
|
|
1061
1118
|
/** Prevent tile bleeding from neighbors in pixels
|
|
1062
1119
|
* @default
|
|
1063
1120
|
* @memberof Settings */
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
/** Use crisp pixels for pixel art if true
|
|
1067
|
-
* @default
|
|
1068
|
-
* @memberof Settings */
|
|
1069
|
-
let pixelated = 1;
|
|
1121
|
+
let tileFixBleedScale = .3;
|
|
1070
1122
|
|
|
1071
1123
|
///////////////////////////////////////////////////////////////////////////////
|
|
1072
1124
|
// Object settings
|
|
@@ -1075,43 +1127,48 @@ let pixelated = 1;
|
|
|
1075
1127
|
* @type {Vector2}
|
|
1076
1128
|
* @default
|
|
1077
1129
|
* @memberof Settings */
|
|
1078
|
-
|
|
1130
|
+
let objectDefaultSize = vec2(1);
|
|
1079
1131
|
|
|
1080
1132
|
/** Default object mass for collison calcuations (how heavy objects are)
|
|
1081
1133
|
* @default
|
|
1082
1134
|
* @memberof Settings */
|
|
1083
|
-
|
|
1135
|
+
let objectDefaultMass = 1;
|
|
1084
1136
|
|
|
1085
1137
|
/** How much to slow velocity by each frame (0-1)
|
|
1086
1138
|
* @default
|
|
1087
1139
|
* @memberof Settings */
|
|
1088
|
-
|
|
1140
|
+
let objectDefaultDamping = .99;
|
|
1089
1141
|
|
|
1090
1142
|
/** How much to slow angular velocity each frame (0-1)
|
|
1091
1143
|
* @default
|
|
1092
1144
|
* @memberof Settings */
|
|
1093
|
-
|
|
1145
|
+
let objectDefaultAngleDamping = .99;
|
|
1094
1146
|
|
|
1095
1147
|
/** How much to bounce when a collision occurs (0-1)
|
|
1096
1148
|
* @default
|
|
1097
1149
|
* @memberof Settings */
|
|
1098
|
-
|
|
1150
|
+
let objectDefaultElasticity = 0;
|
|
1099
1151
|
|
|
1100
1152
|
/** How much to slow when touching (0-1)
|
|
1101
1153
|
* @default
|
|
1102
1154
|
* @memberof Settings */
|
|
1103
|
-
|
|
1155
|
+
let objectDefaultFriction = .8;
|
|
1104
1156
|
|
|
1105
1157
|
/** Clamp max speed to avoid fast objects missing collisions
|
|
1106
1158
|
* @default
|
|
1107
1159
|
* @memberof Settings */
|
|
1108
|
-
|
|
1160
|
+
let objectMaxSpeed = 1;
|
|
1109
1161
|
|
|
1110
1162
|
/** How much gravity to apply to objects along the Y axis, negative is down
|
|
1111
1163
|
* @default
|
|
1112
1164
|
* @memberof Settings */
|
|
1113
1165
|
let gravity = 0;
|
|
1114
1166
|
|
|
1167
|
+
/** Scales emit rate of particles, useful for low graphics mode (0 disables particle emitters)
|
|
1168
|
+
* @default
|
|
1169
|
+
* @memberof Settings */
|
|
1170
|
+
let particleEmitRateScale = 1;
|
|
1171
|
+
|
|
1115
1172
|
///////////////////////////////////////////////////////////////////////////////
|
|
1116
1173
|
// Camera settings
|
|
1117
1174
|
|
|
@@ -1124,7 +1181,7 @@ let cameraPos = vec2();
|
|
|
1124
1181
|
/** Scale of camera in world space
|
|
1125
1182
|
* @default
|
|
1126
1183
|
* @memberof Settings */
|
|
1127
|
-
let cameraScale = max(
|
|
1184
|
+
let cameraScale = max(tileSizeDefault.x, tileSizeDefault.y);
|
|
1128
1185
|
|
|
1129
1186
|
///////////////////////////////////////////////////////////////////////////////
|
|
1130
1187
|
// WebGL settings
|
|
@@ -1132,7 +1189,7 @@ let cameraScale = max(defaultTileSize.x, defaultTileSize.y);
|
|
|
1132
1189
|
/** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
|
|
1133
1190
|
* @default
|
|
1134
1191
|
* @memberof Settings */
|
|
1135
|
-
|
|
1192
|
+
let glEnable = 1;
|
|
1136
1193
|
|
|
1137
1194
|
/** Fixes slow rendering in some browsers by not compositing the WebGL canvas
|
|
1138
1195
|
* @default
|
|
@@ -1145,45 +1202,62 @@ let glOverlay = 1;
|
|
|
1145
1202
|
/** Should gamepads be allowed
|
|
1146
1203
|
* @default
|
|
1147
1204
|
* @memberof Settings */
|
|
1148
|
-
|
|
1205
|
+
let gamepadsEnable = 1;
|
|
1206
|
+
|
|
1207
|
+
/** If true, the dpad input is also routed to the left analog stick (for better accessability)
|
|
1208
|
+
* @default
|
|
1209
|
+
* @memberof Settings */
|
|
1210
|
+
let gamepadDirectionEmulateStick = 1;
|
|
1211
|
+
|
|
1212
|
+
/** If true the WASD keys are also routed to the direction keys (for better accessability)
|
|
1213
|
+
* @default
|
|
1214
|
+
* @memberof Settings */
|
|
1215
|
+
let inputWASDEmulateDirection = 1;
|
|
1216
|
+
|
|
1217
|
+
/** True if touch gamepad should appear on mobile devices
|
|
1218
|
+
* <br> - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
1219
|
+
* <br> - Must be set by end of gameInit to be activated
|
|
1220
|
+
* @default
|
|
1221
|
+
* @memberof Settings */
|
|
1222
|
+
let touchGamepadEnable = 0;
|
|
1149
1223
|
|
|
1150
|
-
/**
|
|
1224
|
+
/** Size of virutal gamepad for touch devices in pixels
|
|
1151
1225
|
* @default
|
|
1152
1226
|
* @memberof Settings */
|
|
1153
|
-
|
|
1227
|
+
let touchGamepadSize = 80;
|
|
1154
1228
|
|
|
1155
|
-
/**
|
|
1229
|
+
/** Transparency of touch gamepad overlay
|
|
1156
1230
|
* @default
|
|
1157
1231
|
* @memberof Settings */
|
|
1158
|
-
|
|
1232
|
+
let touchGamepadAlpha = .3;
|
|
1159
1233
|
|
|
1160
|
-
/**
|
|
1234
|
+
/** Allow vibration hardware if it exists
|
|
1161
1235
|
* @default
|
|
1162
1236
|
* @memberof Settings */
|
|
1163
|
-
|
|
1237
|
+
let vibrateEnable = 1;
|
|
1164
1238
|
|
|
1165
1239
|
///////////////////////////////////////////////////////////////////////////////
|
|
1166
1240
|
// Audio settings
|
|
1167
1241
|
|
|
1168
|
-
/**
|
|
1242
|
+
/** Volume scale to apply to all sound, music and speech
|
|
1169
1243
|
* @default
|
|
1170
1244
|
* @memberof Settings */
|
|
1171
|
-
|
|
1245
|
+
let soundVolume = .5;
|
|
1172
1246
|
|
|
1173
|
-
/**
|
|
1247
|
+
/** All audio code can be disabled and removed from build
|
|
1174
1248
|
* @default
|
|
1175
1249
|
* @memberof Settings */
|
|
1176
|
-
let
|
|
1250
|
+
let soundEnable = 1;
|
|
1177
1251
|
|
|
1178
1252
|
/** Default range where sound no longer plays
|
|
1179
1253
|
* @default
|
|
1180
1254
|
* @memberof Settings */
|
|
1181
|
-
|
|
1255
|
+
let soundDefaultRange = 30;
|
|
1182
1256
|
|
|
1183
1257
|
/** Default range percent to start tapering off sound (0-1)
|
|
1184
1258
|
* @default
|
|
1185
1259
|
* @memberof Settings */
|
|
1186
|
-
|
|
1260
|
+
let soundDefaultTaper = .7;
|
|
1187
1261
|
|
|
1188
1262
|
///////////////////////////////////////////////////////////////////////////////
|
|
1189
1263
|
// Medals settings
|
|
@@ -1191,409 +1265,168 @@ const defaultSoundTaper = .7;
|
|
|
1191
1265
|
/** How long to show medals for in seconds
|
|
1192
1266
|
* @default
|
|
1193
1267
|
* @memberof Settings */
|
|
1194
|
-
|
|
1268
|
+
let medalDisplayTime = 5;
|
|
1195
1269
|
|
|
1196
1270
|
/** How quickly to slide on/off medals in seconds
|
|
1197
1271
|
* @default
|
|
1198
1272
|
* @memberof Settings */
|
|
1199
|
-
|
|
1273
|
+
let medalDisplaySlideTime = .5;
|
|
1200
1274
|
|
|
1201
1275
|
/** Width of medal display
|
|
1202
1276
|
* @default
|
|
1203
1277
|
* @memberof Settings */
|
|
1204
|
-
|
|
1278
|
+
let medalDisplayWidth = 640;
|
|
1205
1279
|
|
|
1206
1280
|
/** Height of medal display
|
|
1207
1281
|
* @default
|
|
1208
1282
|
* @memberof Settings */
|
|
1209
|
-
|
|
1283
|
+
let medalDisplayHeight = 80;
|
|
1210
1284
|
|
|
1211
1285
|
/** Size of icon in medal display
|
|
1212
1286
|
* @default
|
|
1213
1287
|
* @memberof Settings */
|
|
1214
|
-
|
|
1288
|
+
let medalDisplayIconSize = 50;
|
|
1215
1289
|
/*
|
|
1216
|
-
LittleJS
|
|
1217
|
-
MIT License - Copyright 2021 Frank Force
|
|
1218
|
-
|
|
1219
|
-
Engine Features
|
|
1220
|
-
- Object oriented system with base class engine object
|
|
1221
|
-
- Base class object handles update, physics, collision, rendering, etc
|
|
1222
|
-
- Engine helper classes and functions like Vector2, Color, and Timer
|
|
1223
|
-
- Super fast rendering system for tile sheets
|
|
1224
|
-
- Sound effects audio with zzfx and music with zzfxm
|
|
1225
|
-
- Input processing system with gamepad and touchscreen support
|
|
1226
|
-
- Tile layer rendering and collision system
|
|
1227
|
-
- Particle effect system
|
|
1228
|
-
- Medal system tracks and displays achievements
|
|
1229
|
-
- Debug tools and debug rendering system
|
|
1230
|
-
- Call engineInit() to start it up!
|
|
1290
|
+
LittleJS Object System
|
|
1231
1291
|
*/
|
|
1232
1292
|
|
|
1233
1293
|
'use strict';
|
|
1234
1294
|
|
|
1235
|
-
/**
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
*
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
*
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
let time = 0;
|
|
1260
|
-
|
|
1261
|
-
/** Actual clock time since start in seconds (not affected by pause or frame rate clamping) */
|
|
1262
|
-
let timeReal = 0;
|
|
1263
|
-
|
|
1264
|
-
/** Is the game paused? Causes time and objects to not be updated. */
|
|
1265
|
-
let paused = 0;
|
|
1266
|
-
|
|
1267
|
-
// Engine internal variables not exposed to documentation
|
|
1268
|
-
let frameTimeLastMS = 0, frameTimeBufferMS = 0, debugFPS = 0,
|
|
1269
|
-
shrinkTilesX, shrinkTilesY, drawCount, tileImageSize, tileImageSizeInverse;
|
|
1270
|
-
|
|
1271
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
1272
|
-
|
|
1273
|
-
/** Start up LittleJS engine with your callback functions
|
|
1274
|
-
* @param {Function} gameInit - Called once after the engine starts up, setup the game
|
|
1275
|
-
* @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
|
|
1276
|
-
* @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
|
|
1277
|
-
* @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
|
|
1278
|
-
* @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
|
|
1279
|
-
* @param {String} tileImageSource - Tile image to use, everything starts when the image is finished loading
|
|
1295
|
+
/**
|
|
1296
|
+
* LittleJS Object Base Object Class
|
|
1297
|
+
* <br> - Base object class used by the engine
|
|
1298
|
+
* <br> - Automatically adds self to object list
|
|
1299
|
+
* <br> - Will be updated and rendered each frame
|
|
1300
|
+
* <br> - Renders as a sprite from a tilesheet by default
|
|
1301
|
+
* <br> - Can have color and addtive color applied
|
|
1302
|
+
* <br> - 2d Physics and collision system
|
|
1303
|
+
* <br> - Sorted by renderOrder
|
|
1304
|
+
* <br> - Objects can have children attached
|
|
1305
|
+
* <br> - Parents are updated before children, and set child transform
|
|
1306
|
+
* <br> - Call destroy() to get rid of objects
|
|
1307
|
+
* <br>
|
|
1308
|
+
* <br>The physics system used by objects is simple and fast with some caveats...
|
|
1309
|
+
* <br> - Collision uses the axis aligned size, the object's rotation angle is only for rendering
|
|
1310
|
+
* <br> - Objects are guaranteed to not intersect tile collision from physics
|
|
1311
|
+
* <br> - If an object starts or is moved inside tile collision, it will not collide with that tile
|
|
1312
|
+
* <br> - Collision for objects can be set to be solid to block other objects
|
|
1313
|
+
* <br> - Objects may get pushed into overlapping other solid objects, if so they will push away
|
|
1314
|
+
* <br> - Solid objects are more performance intensive and should be used sparingly
|
|
1315
|
+
* @example
|
|
1316
|
+
* // create an engine object, normally you would first extend the class with your own
|
|
1317
|
+
* const pos = vec2(2,3);
|
|
1318
|
+
* const object = new EngineObject(pos);
|
|
1280
1319
|
*/
|
|
1281
|
-
|
|
1320
|
+
class EngineObject
|
|
1282
1321
|
{
|
|
1283
|
-
|
|
1284
|
-
|
|
1322
|
+
/** Create an engine object and adds it to the list of objects
|
|
1323
|
+
* @param {Vector2} [position=new Vector2(0,0)] - World space position of the object
|
|
1324
|
+
* @param {Vector2} [size=objectDefaultSize] - World space size of the object
|
|
1325
|
+
* @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
|
|
1326
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
1327
|
+
* @param {Number} [angle=0] - Angle to rotate the object
|
|
1328
|
+
* @param {Color} [color] - Color to apply to tile when rendered
|
|
1329
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
1330
|
+
*/
|
|
1331
|
+
constructor(pos=vec2(), size=objectDefaultSize, tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
|
|
1285
1332
|
{
|
|
1286
|
-
//
|
|
1287
|
-
|
|
1288
|
-
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
1289
|
-
shrinkTilesX = tileBleedShrinkFix/tileImageSize.x;
|
|
1290
|
-
shrinkTilesY = tileBleedShrinkFix/tileImageSize.y;
|
|
1333
|
+
// set passed in params
|
|
1334
|
+
ASSERT(pos && pos.x != undefined && size.x != undefined); // ensure pos and size are vec2s
|
|
1291
1335
|
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1336
|
+
/** @property {Vector2} - World space position of the object */
|
|
1337
|
+
this.pos = pos.copy();
|
|
1338
|
+
/** @property {Vector2} - World space width and height of the object */
|
|
1339
|
+
this.size = size;
|
|
1340
|
+
/** @property {Vector2} - Size of object used for drawing, uses size if not set */
|
|
1341
|
+
this.drawSize;
|
|
1342
|
+
/** @property {Number} - Tile to use to render object, untextured if -1 */
|
|
1343
|
+
this.tileIndex = tileIndex;
|
|
1344
|
+
/** @property {Vector2} - Size of tile in source pixels */
|
|
1345
|
+
this.tileSize = tileSize;
|
|
1346
|
+
/** @property {Number} - Angle to rotate the object */
|
|
1347
|
+
this.angle = angle;
|
|
1348
|
+
/** @property {Color} - Color to apply when rendered */
|
|
1349
|
+
this.color = color;
|
|
1350
|
+
/** @property {Color} - Additive color to apply when rendered */
|
|
1351
|
+
this.additiveColor;
|
|
1352
|
+
|
|
1353
|
+
// set object defaults
|
|
1354
|
+
/** @property {Number} [mass=objectDefaultMass] - How heavy the object is */
|
|
1355
|
+
this.mass = objectDefaultMass;
|
|
1356
|
+
/** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
|
|
1357
|
+
this.damping = objectDefaultDamping;
|
|
1358
|
+
/** @property {Number} [angleDamping=objectDefaultAngleDamping] - How much to slow down rotation each frame (0-1) */
|
|
1359
|
+
this.angleDamping = objectDefaultAngleDamping;
|
|
1360
|
+
/** @property {Number} [elasticity=objectDefaultElasticity] - How bouncy the object is when colliding (0-1) */
|
|
1361
|
+
this.elasticity = objectDefaultElasticity;
|
|
1362
|
+
/** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
|
|
1363
|
+
this.friction = objectDefaultFriction;
|
|
1364
|
+
/** @property {Number} [gravityScale=1] - How much to scale gravity by for this object */
|
|
1365
|
+
this.gravityScale = 1;
|
|
1366
|
+
/** @property {Number} [renderOrder=0] - Objects are sorted by render order */
|
|
1367
|
+
this.renderOrder = renderOrder;
|
|
1298
1368
|
|
|
1299
|
-
// init
|
|
1300
|
-
|
|
1301
|
-
|
|
1369
|
+
// init other internal object stuff
|
|
1370
|
+
this.spawnTime = time;
|
|
1371
|
+
this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
|
|
1372
|
+
this.collideTiles = 1;
|
|
1373
|
+
this.children = [];
|
|
1302
1374
|
|
|
1303
|
-
//
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1375
|
+
// add to list of objects
|
|
1376
|
+
engineObjects.push(this);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/** Update the object transform and physics, called automatically by engine once each frame */
|
|
1380
|
+
update()
|
|
1381
|
+
{
|
|
1382
|
+
const parent = this.parent;
|
|
1383
|
+
if (parent)
|
|
1384
|
+
{
|
|
1385
|
+
// copy parent pos/angle
|
|
1386
|
+
this.pos = this.localPos.multiply(vec2(parent.getMirrorSign(),1)).rotate(-parent.angle).add(parent.pos);
|
|
1387
|
+
this.angle = parent.getMirrorSign()*this.localAngle + parent.angle;
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1307
1390
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1391
|
+
// limit max speed to prevent missing collisions
|
|
1392
|
+
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
1393
|
+
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
1311
1394
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1395
|
+
// apply physics
|
|
1396
|
+
const oldPos = this.pos.copy();
|
|
1397
|
+
this.pos.x += this.velocity.x = this.damping * this.velocity.x;
|
|
1398
|
+
this.pos.y += this.velocity.y = this.damping * this.velocity.y + gravity * this.gravityScale;
|
|
1399
|
+
this.angle += this.angleVelocity *= this.angleDamping;
|
|
1316
1400
|
|
|
1317
|
-
//
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
if (debug || showWatermark)
|
|
1321
|
-
debugFPS = lerp(.05, 1e3/(frameTimeDeltaMS||1), debugFPS);
|
|
1322
|
-
if (debug)
|
|
1323
|
-
frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
|
|
1324
|
-
timeReal += frameTimeDeltaMS / 1e3;
|
|
1325
|
-
frameTimeBufferMS = min(frameTimeBufferMS + !paused * frameTimeDeltaMS, 50); // clamp incase of slow framerate
|
|
1401
|
+
// physics sanity checks
|
|
1402
|
+
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
1403
|
+
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
1326
1404
|
|
|
1327
|
-
if (
|
|
1405
|
+
if (!this.mass) // do not update collision for fixed objects
|
|
1406
|
+
return;
|
|
1407
|
+
|
|
1408
|
+
const wasMovingDown = this.velocity.y < 0;
|
|
1409
|
+
if (this.groundObject)
|
|
1328
1410
|
{
|
|
1329
|
-
//
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1411
|
+
// apply friction in local space of ground object
|
|
1412
|
+
const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
|
|
1413
|
+
this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * this.friction;
|
|
1414
|
+
this.groundObject = 0;
|
|
1415
|
+
//debugPhysics && debugPoint(this.pos.subtract(vec2(0,this.size.y/2)), '#0f0');
|
|
1334
1416
|
}
|
|
1335
|
-
|
|
1417
|
+
|
|
1418
|
+
if (this.collideSolidObjects)
|
|
1336
1419
|
{
|
|
1337
|
-
//
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
{
|
|
1341
|
-
// force an update each frame if time is close enough (not just a fast refresh rate)
|
|
1342
|
-
deltaSmooth = frameTimeBufferMS;
|
|
1343
|
-
frameTimeBufferMS = 0;
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
// update multiple frames if necessary in case of slow framerate
|
|
1347
|
-
for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / FPS)
|
|
1348
|
-
{
|
|
1349
|
-
// update game and objects
|
|
1350
|
-
inputUpdate();
|
|
1351
|
-
gameUpdate();
|
|
1352
|
-
engineObjectsUpdate();
|
|
1353
|
-
|
|
1354
|
-
// do post update
|
|
1355
|
-
debugUpdate();
|
|
1356
|
-
gameUpdatePost();
|
|
1357
|
-
inputUpdatePost();
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
// add the time smoothing back in
|
|
1361
|
-
frameTimeBufferMS += deltaSmooth;
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
if (fixedWidth)
|
|
1365
|
-
{
|
|
1366
|
-
// clear set fixed size
|
|
1367
|
-
mainCanvas.width = fixedWidth;
|
|
1368
|
-
mainCanvas.height = fixedHeight;
|
|
1369
|
-
|
|
1370
|
-
if (fixedFitToWindow)
|
|
1371
|
-
{
|
|
1372
|
-
// fit to window by adding space on top or bottom if necessary
|
|
1373
|
-
const aspect = innerWidth / innerHeight;
|
|
1374
|
-
const fixedAspect = fixedWidth / fixedHeight;
|
|
1375
|
-
mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
|
|
1376
|
-
mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
1377
|
-
if (glCanvas)
|
|
1378
|
-
{
|
|
1379
|
-
glCanvas.style.width = mainCanvas.style.width;
|
|
1380
|
-
glCanvas.style.height = mainCanvas.style.height;
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
else
|
|
1385
|
-
{
|
|
1386
|
-
// clear and set size to same as window
|
|
1387
|
-
mainCanvas.width = min(innerWidth, maxWidth);
|
|
1388
|
-
mainCanvas.height = min(innerHeight, maxHeight);
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
// save canvas size and clear overlay canvas
|
|
1392
|
-
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
1393
|
-
mainContext.imageSmoothingEnabled = !pixelated; // disable smoothing for pixel art
|
|
1394
|
-
|
|
1395
|
-
// render sort then render while removing destroyed objects
|
|
1396
|
-
glPreRender(mainCanvas.width, mainCanvas.height);
|
|
1397
|
-
gameRender();
|
|
1398
|
-
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
1399
|
-
for (const o of engineObjects)
|
|
1400
|
-
o.destroyed || o.render();
|
|
1401
|
-
gameRenderPost();
|
|
1402
|
-
medalsRender();
|
|
1403
|
-
debugRender();
|
|
1404
|
-
glCopyToContext(mainContext);
|
|
1405
|
-
|
|
1406
|
-
if (showWatermark)
|
|
1407
|
-
{
|
|
1408
|
-
// update fps
|
|
1409
|
-
overlayContext.textAlign = 'right';
|
|
1410
|
-
overlayContext.textBaseline = 'top';
|
|
1411
|
-
overlayContext.font = '1em monospace';
|
|
1412
|
-
overlayContext.fillStyle = '#000';
|
|
1413
|
-
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
1414
|
-
+ drawCount + ' / ' + engineObjects.length + ' / ' + debugFPS.toFixed(1);
|
|
1415
|
-
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
1416
|
-
overlayContext.fillStyle = '#fff';
|
|
1417
|
-
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
1418
|
-
drawCount = 0;
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
// set tile image source to load the image and start the engine
|
|
1423
|
-
tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
1428
|
-
|
|
1429
|
-
/** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
|
|
1430
|
-
function engineObjectsUpdate()
|
|
1431
|
-
{
|
|
1432
|
-
// recursive object update
|
|
1433
|
-
const updateObject = (o)=>
|
|
1434
|
-
{
|
|
1435
|
-
if (!o.destroyed)
|
|
1436
|
-
{
|
|
1437
|
-
o.update();
|
|
1438
|
-
for (const child of o.children)
|
|
1439
|
-
updateObject(child);
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
for (const o of engineObjects)
|
|
1443
|
-
o.parent || updateObject(o);
|
|
1444
|
-
|
|
1445
|
-
// remove destroyed objects
|
|
1446
|
-
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
1447
|
-
engineCollideObjects = engineCollideObjects.filter(o=>!o.destroyed);
|
|
1448
|
-
|
|
1449
|
-
// increment frame and update time
|
|
1450
|
-
time = ++frame / FPS;
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
/** Detroy and remove all objects that are not persistent or descendants of a persistent object */
|
|
1454
|
-
function engineObjectsDestroy()
|
|
1455
|
-
{
|
|
1456
|
-
for (const o of engineObjects)
|
|
1457
|
-
o.persistent || o.parent || o.destroy();
|
|
1458
|
-
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
/** Triggers a callback for each object within a given area
|
|
1462
|
-
* @param {Vector2} [pos] - Center of test area
|
|
1463
|
-
* @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
|
|
1464
|
-
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
1465
|
-
* @param {Array} [objects=engineObjects] - List of objects to check */
|
|
1466
|
-
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
1467
|
-
{
|
|
1468
|
-
if (!pos)
|
|
1469
|
-
{
|
|
1470
|
-
// all objects
|
|
1471
|
-
for (const o of objects)
|
|
1472
|
-
callbackFunction(o);
|
|
1473
|
-
}
|
|
1474
|
-
else if (size.x != undefined)
|
|
1475
|
-
{
|
|
1476
|
-
// aabb test
|
|
1477
|
-
for (const o of objects)
|
|
1478
|
-
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
1479
|
-
}
|
|
1480
|
-
else
|
|
1481
|
-
{
|
|
1482
|
-
// circle test
|
|
1483
|
-
const sizeSquared = size*size;
|
|
1484
|
-
for (const o of objects)
|
|
1485
|
-
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
/*
|
|
1489
|
-
LittleJS Object System
|
|
1490
|
-
*/
|
|
1491
|
-
|
|
1492
|
-
'use strict';
|
|
1493
|
-
|
|
1494
|
-
/**
|
|
1495
|
-
* LittleJS Object Base Object Class
|
|
1496
|
-
* <br> - Base object class used by the engine
|
|
1497
|
-
* <br> - Automatically adds self to object list
|
|
1498
|
-
* <br> - Will be updated and rendered each frame
|
|
1499
|
-
* <br> - Renders as a sprite from a tilesheet by default
|
|
1500
|
-
* <br> - Can have color and addtive color applied
|
|
1501
|
-
* <br> - 2d Physics and collision system
|
|
1502
|
-
* <br> - Sorted by renderOrder
|
|
1503
|
-
* <br> - Objects can have children attached
|
|
1504
|
-
* <br> - Parents are updated before children, and set child transform
|
|
1505
|
-
* <br> - Call destroy() to get rid of objects
|
|
1506
|
-
*/
|
|
1507
|
-
class EngineObject
|
|
1508
|
-
{
|
|
1509
|
-
/**
|
|
1510
|
-
* Create an engine object and adds it to the list of objects
|
|
1511
|
-
* @param {Vector2} [position=new Vector2(0,0)] - World space position of the object
|
|
1512
|
-
* @param {Vector2} [size=defaultObjectSize] - World space size of the object
|
|
1513
|
-
* @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
|
|
1514
|
-
* @param {Vector2} [tileSize=defaultTileSize] - Size of tile in source pixels
|
|
1515
|
-
* @param {Number} [angle=0] - Angle to rotate the object
|
|
1516
|
-
* @param {Color} [color] - Color to apply to tile when rendered
|
|
1517
|
-
*/
|
|
1518
|
-
constructor(pos=vec2(), size=defaultObjectSize, tileIndex=-1, tileSize=defaultTileSize, angle=0, color)
|
|
1519
|
-
{
|
|
1520
|
-
// set passed in params
|
|
1521
|
-
ASSERT(pos && pos.x != undefined && size.x != undefined); // ensure pos and size are vec2s
|
|
1522
|
-
this.pos = pos.copy();
|
|
1523
|
-
this.size = size;
|
|
1524
|
-
this.tileIndex = tileIndex;
|
|
1525
|
-
this.tileSize = tileSize;
|
|
1526
|
-
this.angle = angle;
|
|
1527
|
-
this.color = color;
|
|
1528
|
-
|
|
1529
|
-
// set physics defaults
|
|
1530
|
-
this.mass = defaultObjectMass;
|
|
1531
|
-
this.damping = defaultObjectDamping;
|
|
1532
|
-
this.angleDamping = defaultObjectAngleDamping;
|
|
1533
|
-
this.elasticity = defaultObjectElasticity;
|
|
1534
|
-
this.friction = defaultObjectFriction;
|
|
1535
|
-
|
|
1536
|
-
// init other object stuff
|
|
1537
|
-
this.spawnTime = time;
|
|
1538
|
-
this.velocity = vec2(this.collideSolidObjects = this.renderOrder = this.angleVelocity = 0);
|
|
1539
|
-
this.collideTiles = this.gravityScale = 1;
|
|
1540
|
-
this.children = [];
|
|
1541
|
-
|
|
1542
|
-
// add to list of objects
|
|
1543
|
-
engineObjects.push(this);
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
/** Update the object transform and physics, called automatically by engine once each frame */
|
|
1547
|
-
update()
|
|
1548
|
-
{
|
|
1549
|
-
const parent = this.parent;
|
|
1550
|
-
if (parent)
|
|
1551
|
-
{
|
|
1552
|
-
// copy parent pos/angle
|
|
1553
|
-
this.pos = this.localPos.multiply(vec2(parent.getMirrorSign(),1)).rotate(-parent.angle).add(parent.pos);
|
|
1554
|
-
this.angle = parent.getMirrorSign()*this.localAngle + parent.angle;
|
|
1555
|
-
return;
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
// limit max speed to prevent missing collisions
|
|
1559
|
-
this.velocity.x = clamp(this.velocity.x, maxObjectSpeed, -maxObjectSpeed);
|
|
1560
|
-
this.velocity.y = clamp(this.velocity.y, maxObjectSpeed, -maxObjectSpeed);
|
|
1561
|
-
|
|
1562
|
-
// apply physics
|
|
1563
|
-
const oldPos = this.pos.copy();
|
|
1564
|
-
this.pos.x += this.velocity.x = this.damping * this.velocity.x;
|
|
1565
|
-
this.pos.y += this.velocity.y = this.damping * this.velocity.y + gravity * this.gravityScale;
|
|
1566
|
-
this.angle += this.angleVelocity *= this.angleDamping;
|
|
1567
|
-
|
|
1568
|
-
// physics sanity checks
|
|
1569
|
-
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
1570
|
-
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
1571
|
-
|
|
1572
|
-
if (!this.mass) // do not update collision for fixed objects
|
|
1573
|
-
return;
|
|
1574
|
-
|
|
1575
|
-
const wasMovingDown = this.velocity.y < 0;
|
|
1576
|
-
if (this.groundObject)
|
|
1577
|
-
{
|
|
1578
|
-
// apply friction in local space of ground object
|
|
1579
|
-
const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
|
|
1580
|
-
this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * this.friction;
|
|
1581
|
-
this.groundObject = 0;
|
|
1582
|
-
//debugPhysics && debugPoint(this.pos.subtract(vec2(0,this.size.y/2)), '#0f0');
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
if (this.collideSolidObjects)
|
|
1586
|
-
{
|
|
1587
|
-
// check collisions against solid objects
|
|
1588
|
-
const epsilon = 1e-3; // necessary to push slightly outside of the collision
|
|
1589
|
-
for (const o of engineCollideObjects)
|
|
1420
|
+
// check collisions against solid objects
|
|
1421
|
+
const epsilon = 1e-3; // necessary to push slightly outside of the collision
|
|
1422
|
+
for (const o of engineObjectsCollide)
|
|
1590
1423
|
{
|
|
1591
1424
|
// non solid objects don't collide with eachother
|
|
1592
|
-
if (!this.isSolid & !o.isSolid || o.destroyed || o.parent)
|
|
1425
|
+
if (!this.isSolid & !o.isSolid || o.destroyed || o.parent || o == this)
|
|
1593
1426
|
continue;
|
|
1594
1427
|
|
|
1595
1428
|
// check collision
|
|
1596
|
-
if (!isOverlapping(this.pos, this.size, o.pos, o.size)
|
|
1429
|
+
if (!isOverlapping(this.pos, this.size, o.pos, o.size))
|
|
1597
1430
|
continue;
|
|
1598
1431
|
|
|
1599
1432
|
// pass collision to objects
|
|
@@ -1605,7 +1438,7 @@ class EngineObject
|
|
|
1605
1438
|
// if already was touching, try to push away
|
|
1606
1439
|
const deltaPos = oldPos.subtract(o.pos);
|
|
1607
1440
|
const length = deltaPos.length();
|
|
1608
|
-
const pushAwayAccel = .001; // push away if
|
|
1441
|
+
const pushAwayAccel = .001; // push away if already overlapping
|
|
1609
1442
|
const velocity = length < .01 ? randVector(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
|
|
1610
1443
|
this.velocity = this.velocity.add(velocity);
|
|
1611
1444
|
if (o.mass) // push away if not fixed
|
|
@@ -1616,16 +1449,15 @@ class EngineObject
|
|
|
1616
1449
|
}
|
|
1617
1450
|
|
|
1618
1451
|
// check for collision
|
|
1619
|
-
const
|
|
1620
|
-
const
|
|
1621
|
-
const
|
|
1622
|
-
const
|
|
1623
|
-
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sx;
|
|
1452
|
+
const sizeBoth = this.size.add(o.size);
|
|
1453
|
+
const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity; // prefer to push up if small delta
|
|
1454
|
+
const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
|
|
1455
|
+
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
|
|
1624
1456
|
|
|
1625
1457
|
if (smallStepUp || isBlockedY || !isBlockedX) // resolve y collision
|
|
1626
1458
|
{
|
|
1627
1459
|
// push outside object collision
|
|
1628
|
-
this.pos.y = o.pos.y + (
|
|
1460
|
+
this.pos.y = o.pos.y + (sizeBoth.y/2 + epsilon) * sign(oldPos.y - o.pos.y);
|
|
1629
1461
|
if (o.groundObject && wasMovingDown || !o.mass)
|
|
1630
1462
|
{
|
|
1631
1463
|
// set ground object if landed on something
|
|
@@ -1648,15 +1480,14 @@ class EngineObject
|
|
|
1648
1480
|
|
|
1649
1481
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1650
1482
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1651
|
-
this.velocity.y = lerp(elasticity,
|
|
1652
|
-
o.velocity.y = lerp(elasticity,
|
|
1483
|
+
this.velocity.y = lerp(elasticity, inelastic, elastic0);
|
|
1484
|
+
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
1653
1485
|
}
|
|
1654
|
-
debugPhysics && smallStepUp && (abs(oldPos.x - o.pos.x)*2 > sx) && console.log('stepUp', oldPos.y - o.pos.y);
|
|
1655
1486
|
}
|
|
1656
1487
|
if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
|
|
1657
1488
|
{
|
|
1658
1489
|
// push outside collision
|
|
1659
|
-
this.pos.x = o.pos.x + (
|
|
1490
|
+
this.pos.x = o.pos.x + (sizeBoth.x/2 + epsilon) * sign(oldPos.x - o.pos.x);
|
|
1660
1491
|
if (o.mass)
|
|
1661
1492
|
{
|
|
1662
1493
|
// inelastic collision
|
|
@@ -1670,13 +1501,12 @@ class EngineObject
|
|
|
1670
1501
|
|
|
1671
1502
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1672
1503
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1673
|
-
this.velocity.x = lerp(elasticity,
|
|
1674
|
-
o.velocity.x = lerp(elasticity,
|
|
1504
|
+
this.velocity.x = lerp(elasticity, inelastic, elastic0);
|
|
1505
|
+
o.velocity.x = lerp(elasticity, inelastic, elastic1);
|
|
1675
1506
|
}
|
|
1676
1507
|
else // bounce if other object is fixed
|
|
1677
1508
|
this.velocity.x *= -this.elasticity;
|
|
1678
1509
|
}
|
|
1679
|
-
|
|
1680
1510
|
debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
|
|
1681
1511
|
}
|
|
1682
1512
|
}
|
|
@@ -1685,8 +1515,6 @@ class EngineObject
|
|
|
1685
1515
|
// check collision against tiles
|
|
1686
1516
|
if (tileCollisionTest(this.pos, this.size, this))
|
|
1687
1517
|
{
|
|
1688
|
-
//debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
1689
|
-
|
|
1690
1518
|
// if already was stuck in collision, don't do anything
|
|
1691
1519
|
// this should not happen unless something starts in collision
|
|
1692
1520
|
if (!tileCollisionTest(oldPos, this.size, this))
|
|
@@ -1743,19 +1571,19 @@ class EngineObject
|
|
|
1743
1571
|
|
|
1744
1572
|
/** Called to check if a tile collision should be resolved
|
|
1745
1573
|
* @param {Number} tileData - the value of the tile at the position
|
|
1746
|
-
* @param {Vector2} pos
|
|
1747
|
-
* @return {Boolean} true if the collision should be resolved */
|
|
1574
|
+
* @param {Vector2} pos - tile where the collision occured
|
|
1575
|
+
* @return {Boolean} - true if the collision should be resolved */
|
|
1748
1576
|
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
1749
1577
|
|
|
1750
1578
|
/** Called to check if a tile raycast hit
|
|
1751
1579
|
* @param {Number} tileData - the value of the tile at the position
|
|
1752
|
-
* @param {Vector2} pos
|
|
1753
|
-
* @return {Boolean} true if the raycast should hit */
|
|
1580
|
+
* @param {Vector2} pos - tile where the raycast is
|
|
1581
|
+
* @return {Boolean} - true if the raycast should hit */
|
|
1754
1582
|
collideWithTileRaycast(tileData, pos) { return tileData > 0; }
|
|
1755
1583
|
|
|
1756
1584
|
/** Called to check if a tile raycast hit
|
|
1757
1585
|
* @param {EngineObject} object - the object to test against
|
|
1758
|
-
* @return {Boolean} true if the collision should be resolved
|
|
1586
|
+
* @return {Boolean} - true if the collision should be resolved
|
|
1759
1587
|
*/
|
|
1760
1588
|
collideWithObject(o) { return 1; }
|
|
1761
1589
|
|
|
@@ -1799,41 +1627,42 @@ class EngineObject
|
|
|
1799
1627
|
|
|
1800
1628
|
/** Set how this object collides
|
|
1801
1629
|
* @param {boolean} [collideSolidObjects=0] - Does it collide with solid objects
|
|
1802
|
-
* @param {boolean} [isSolid=0]
|
|
1803
|
-
* @param {boolean} [collideTiles=1]
|
|
1630
|
+
* @param {boolean} [isSolid=0] - Does it collide with and block other objects (expensive in large numbers)
|
|
1631
|
+
* @param {boolean} [collideTiles=1] - Does it collide with the tile collision */
|
|
1804
1632
|
setCollision(collideSolidObjects=0, isSolid=0, collideTiles=1)
|
|
1805
1633
|
{
|
|
1806
1634
|
ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
|
|
1807
1635
|
|
|
1808
|
-
// track collidable objects in separate list
|
|
1809
|
-
if (collideSolidObjects && !this.collideSolidObjects)
|
|
1810
|
-
{
|
|
1811
|
-
ASSERT(!engineCollideObjects.includes(this));
|
|
1812
|
-
engineCollideObjects.push(this);
|
|
1813
|
-
}
|
|
1814
|
-
else if (!collideSolidObjects && this.collideSolidObjects)
|
|
1815
|
-
{
|
|
1816
|
-
ASSERT(engineCollideObjects.includes(this))
|
|
1817
|
-
engineCollideObjects.splice(engineCollideObjects.indexOf(this), 1);
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
1636
|
this.collideSolidObjects = collideSolidObjects;
|
|
1821
1637
|
this.isSolid = isSolid;
|
|
1822
1638
|
this.collideTiles = collideTiles;
|
|
1823
1639
|
}
|
|
1824
1640
|
}
|
|
1825
1641
|
/**
|
|
1826
|
-
*
|
|
1827
|
-
*
|
|
1828
|
-
*
|
|
1829
|
-
*
|
|
1830
|
-
*
|
|
1831
|
-
*
|
|
1642
|
+
* LittleJS Drawing System
|
|
1643
|
+
* <br> - Hybrid with both Canvas2D and WebGL available
|
|
1644
|
+
* <br> - Super fast tile sheet rendering with WebGL
|
|
1645
|
+
* <br> - Can apply rotation, mirror, color and additive color
|
|
1646
|
+
* <br> - Many useful utility functions
|
|
1647
|
+
* <br>
|
|
1648
|
+
* <br>LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
|
|
1649
|
+
* <br>There are 3 canvas/contexts available to draw to...
|
|
1650
|
+
* <br> - mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
|
|
1651
|
+
* <br> - glCanvas - Used by the accelerated WebGL batch rendering system.
|
|
1652
|
+
* <br> - overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
|
|
1653
|
+
* <br>
|
|
1654
|
+
* <br>The WebGL rendering system is very fast with some caveats...
|
|
1655
|
+
* <br> - The default setup supports only 1 tile sheet, to support more call glCreateTexture and glSetTexture
|
|
1656
|
+
* <br> - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
|
|
1657
|
+
* <br> - Group additive rendering together using renderOrder to mitigate this issue
|
|
1658
|
+
* <br>
|
|
1659
|
+
* <br>The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
|
|
1660
|
+
* @namespace Draw
|
|
1832
1661
|
*/
|
|
1833
1662
|
|
|
1834
1663
|
'use strict';
|
|
1835
1664
|
|
|
1836
|
-
/**
|
|
1665
|
+
/** Tile sheet for batch rendering system
|
|
1837
1666
|
* @type {Image}
|
|
1838
1667
|
* @memberof Draw */
|
|
1839
1668
|
const tileImage = new Image();
|
|
@@ -1858,7 +1687,7 @@ let overlayCanvas;
|
|
|
1858
1687
|
* @memberof Draw */
|
|
1859
1688
|
let overlayContext;
|
|
1860
1689
|
|
|
1861
|
-
/** The size of the main canvas (and other secondary canvases
|
|
1690
|
+
/** The size of the main canvas (and other secondary canvases)
|
|
1862
1691
|
* @type {Vector2}
|
|
1863
1692
|
* @memberof Draw */
|
|
1864
1693
|
let mainCanvasSize = vec2();
|
|
@@ -1877,42 +1706,39 @@ const screenToWorld = (screenPos)=>
|
|
|
1877
1706
|
const worldToScreen = (worldPos)=>
|
|
1878
1707
|
worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
|
|
1879
1708
|
|
|
1880
|
-
/** Draw textured tile centered
|
|
1881
|
-
* @param {Vector2} pos
|
|
1882
|
-
* @param {Vector2} [size=new Vector2(1,1)]
|
|
1883
|
-
* @param {Number} [tileIndex=-1]
|
|
1884
|
-
* @param {Vector2} [tileSize=
|
|
1885
|
-
* @param {Color} [color=new Color(1,1,1)]
|
|
1886
|
-
* @param {Number} [angle=0]
|
|
1887
|
-
* @param {Boolean} [mirror=0]
|
|
1888
|
-
* @param {Color} [additiveColor=new Color(0,0,0,0)]
|
|
1709
|
+
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
1710
|
+
* @param {Vector2} pos - Center of the tile in world space
|
|
1711
|
+
* @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space, width and height
|
|
1712
|
+
* @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
|
|
1713
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
|
|
1714
|
+
* @param {Color} [color=new Color(1,1,1)] - Color to modulate with
|
|
1715
|
+
* @param {Number} [angle=0] - Angle to rotate by
|
|
1716
|
+
* @param {Boolean} [mirror=0] - If true image is flipped along the Y axis
|
|
1717
|
+
* @param {Color} [additiveColor=new Color(0,0,0,0)] - Additive color to be applied
|
|
1718
|
+
* @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
1889
1719
|
* @memberof Draw */
|
|
1890
|
-
function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=
|
|
1891
|
-
additiveColor=new Color(0,0,0,0))
|
|
1720
|
+
function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle=0, mirror,
|
|
1721
|
+
additiveColor=new Color(0,0,0,0), useWebGL=glEnable)
|
|
1892
1722
|
{
|
|
1893
1723
|
showWatermark && ++drawCount;
|
|
1894
|
-
if (glEnable)
|
|
1724
|
+
if (glEnable && useWebGL)
|
|
1895
1725
|
{
|
|
1896
|
-
if (tileIndex < 0)
|
|
1726
|
+
if (tileIndex < 0 || !tileImage.width)
|
|
1897
1727
|
{
|
|
1898
|
-
// if negative tile index, force untextured
|
|
1728
|
+
// if negative tile index or image not found, force untextured
|
|
1899
1729
|
glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
|
|
1900
1730
|
}
|
|
1901
1731
|
else
|
|
1902
1732
|
{
|
|
1903
1733
|
// calculate uvs and render
|
|
1904
|
-
const cols =
|
|
1905
|
-
const uvSizeX = tileSize.x
|
|
1906
|
-
const uvSizeY = tileSize.y
|
|
1734
|
+
const cols = tileImageSize.x / tileSize.x |0;
|
|
1735
|
+
const uvSizeX = tileSize.x / tileImageSize.x;
|
|
1736
|
+
const uvSizeY = tileSize.y / tileImageSize.y;
|
|
1907
1737
|
const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
|
|
1908
|
-
|
|
1909
|
-
// shrink uvs to prevent bleeding
|
|
1910
|
-
const shrinkTilesX = tileBleedShrinkFix * tileImageSizeInverse.x;
|
|
1911
|
-
const shrinkTilesY = tileBleedShrinkFix * tileImageSizeInverse.y;
|
|
1912
1738
|
|
|
1913
1739
|
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
1914
|
-
uvX +
|
|
1915
|
-
uvX -
|
|
1740
|
+
uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
|
|
1741
|
+
uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
|
|
1916
1742
|
color.rgbaInt(), additiveColor.rgbaInt());
|
|
1917
1743
|
}
|
|
1918
1744
|
}
|
|
@@ -1930,11 +1756,11 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=defaultTileSize, col
|
|
|
1930
1756
|
else
|
|
1931
1757
|
{
|
|
1932
1758
|
// calculate uvs and render
|
|
1933
|
-
const cols =
|
|
1934
|
-
const sX = (tileIndex%cols)*tileSize.x +
|
|
1935
|
-
const sY = (tileIndex/cols|0)*tileSize.y +
|
|
1936
|
-
const sWidth = tileSize.x - 2*
|
|
1937
|
-
const sHeight = tileSize.y - 2*
|
|
1759
|
+
const cols = tileImageSize.x / tileSize.x |0;
|
|
1760
|
+
const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
|
|
1761
|
+
const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
|
|
1762
|
+
const sWidth = tileSize.x - 2*tileFixBleedScale;
|
|
1763
|
+
const sHeight = tileSize.y - 2*tileFixBleedScale;
|
|
1938
1764
|
context.globalAlpha = color.a; // only alpha is supported
|
|
1939
1765
|
context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
|
|
1940
1766
|
}
|
|
@@ -1942,41 +1768,44 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=defaultTileSize, col
|
|
|
1942
1768
|
}
|
|
1943
1769
|
}
|
|
1944
1770
|
|
|
1945
|
-
/** Draw colored
|
|
1771
|
+
/** Draw colored rect centered on pos
|
|
1946
1772
|
* @param {Vector2} pos
|
|
1947
1773
|
* @param {Vector2} [size=new Vector2(1,1)]
|
|
1948
1774
|
* @param {Color} [color=new Color(1,1,1)]
|
|
1949
1775
|
* @param {Number} [angle=0]
|
|
1776
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1950
1777
|
* @memberof Draw */
|
|
1951
|
-
function drawRect(pos, size, color, angle)
|
|
1778
|
+
function drawRect(pos, size, color, angle, useWebGL)
|
|
1952
1779
|
{
|
|
1953
|
-
drawTile(pos, size, -1,
|
|
1780
|
+
drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
|
|
1954
1781
|
}
|
|
1955
1782
|
|
|
1956
1783
|
/** Draw textured tile centered on pos in screen space
|
|
1957
|
-
* @param {Vector2} pos
|
|
1958
|
-
* @param {Vector2} [size=new Vector2(1,1)]
|
|
1959
|
-
* @param {Number} [tileIndex=-1]
|
|
1960
|
-
* @param {Vector2} [tileSize=
|
|
1784
|
+
* @param {Vector2} pos - Center of the tile
|
|
1785
|
+
* @param {Vector2} [size=new Vector2(1,1)] - Size of the tile
|
|
1786
|
+
* @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
|
|
1787
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
|
|
1961
1788
|
* @param {Color} [color=new Color]
|
|
1962
1789
|
* @param {Number} [angle=0]
|
|
1963
1790
|
* @param {Boolean} [mirror=0]
|
|
1964
1791
|
* @param {Color} [additiveColor=new Color(0,0,0,0)]
|
|
1792
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1965
1793
|
* @memberof Draw */
|
|
1966
|
-
function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor)
|
|
1794
|
+
function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL)
|
|
1967
1795
|
{
|
|
1968
|
-
drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor);
|
|
1796
|
+
drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL);
|
|
1969
1797
|
}
|
|
1970
1798
|
|
|
1971
|
-
/** Draw colored
|
|
1799
|
+
/** Draw colored rectangle in screen space
|
|
1972
1800
|
* @param {Vector2} pos
|
|
1973
1801
|
* @param {Vector2} [size=new Vector2(1,1)]
|
|
1974
1802
|
* @param {Color} [color=new Color(1,1,1)]
|
|
1975
1803
|
* @param {Number} [angle=0]
|
|
1804
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1976
1805
|
* @memberof Draw */
|
|
1977
|
-
function drawRectScreenSpace(pos, size, color, angle)
|
|
1806
|
+
function drawRectScreenSpace(pos, size, color, angle, useWebGL)
|
|
1978
1807
|
{
|
|
1979
|
-
|
|
1808
|
+
drawTileSrceenSpace(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
|
|
1980
1809
|
}
|
|
1981
1810
|
|
|
1982
1811
|
/** Draw colored line between two points
|
|
@@ -1984,15 +1813,16 @@ function drawRectScreenSpace(pos, size, color, angle)
|
|
|
1984
1813
|
* @param {Vector2} posB
|
|
1985
1814
|
* @param {Number} [thickness=.1]
|
|
1986
1815
|
* @param {Color} [color=new Color(1,1,1)]
|
|
1816
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1987
1817
|
* @memberof Draw */
|
|
1988
|
-
function drawLine(posA, posB, thickness=.1, color)
|
|
1818
|
+
function drawLine(posA, posB, thickness=.1, color, useWebGL)
|
|
1989
1819
|
{
|
|
1990
1820
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
1991
1821
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
1992
|
-
drawRect(posA.add(halfDelta), size, color, halfDelta.angle());
|
|
1822
|
+
drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), 0, 0, useWebGL);
|
|
1993
1823
|
}
|
|
1994
1824
|
|
|
1995
|
-
/** Draw directly to a 2d canvas context in world space
|
|
1825
|
+
/** Draw directly to a 2d canvas context in world space
|
|
1996
1826
|
* @param {Vector2} pos
|
|
1997
1827
|
* @param {Vector2} size
|
|
1998
1828
|
* @param {Number} angle
|
|
@@ -2008,7 +1838,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
|
|
|
2008
1838
|
context.save();
|
|
2009
1839
|
context.translate(pos.x+.5|0, pos.y-.5|0);
|
|
2010
1840
|
context.rotate(angle);
|
|
2011
|
-
context.scale(mirror
|
|
1841
|
+
context.scale(mirror ? -size.x : size.x, size.y);
|
|
2012
1842
|
drawFunction(context);
|
|
2013
1843
|
context.restore();
|
|
2014
1844
|
}
|
|
@@ -2022,7 +1852,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
|
|
|
2022
1852
|
* @param {Color} [lineColor=new Color(0,0,0)]
|
|
2023
1853
|
* @param {String} [textAlign='center']
|
|
2024
1854
|
* @memberof Draw */
|
|
2025
|
-
function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=
|
|
1855
|
+
function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault)
|
|
2026
1856
|
{
|
|
2027
1857
|
pos = worldToScreen(pos);
|
|
2028
1858
|
overlayContext.font = size*cameraScale + 'px '+ font;
|
|
@@ -2038,12 +1868,13 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
|
|
|
2038
1868
|
overlayContext.fillText(text, pos.x, pos.y);
|
|
2039
1869
|
}
|
|
2040
1870
|
|
|
2041
|
-
/** Enable
|
|
1871
|
+
/** Enable normal or additive blend mode
|
|
2042
1872
|
* @param {Boolean} [additive=0]
|
|
1873
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
2043
1874
|
* @memberof Draw */
|
|
2044
|
-
function setBlendMode(additive)
|
|
1875
|
+
function setBlendMode(additive, useWebGL=glEnable)
|
|
2045
1876
|
{
|
|
2046
|
-
if (glEnable)
|
|
1877
|
+
if (glEnable && useWebGL)
|
|
2047
1878
|
glSetBlendMode(additive);
|
|
2048
1879
|
else
|
|
2049
1880
|
mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
@@ -2077,11 +1908,12 @@ function toggleFullscreen()
|
|
|
2077
1908
|
}
|
|
2078
1909
|
}
|
|
2079
1910
|
/**
|
|
2080
|
-
*
|
|
2081
|
-
*
|
|
2082
|
-
*
|
|
2083
|
-
*
|
|
2084
|
-
*
|
|
1911
|
+
* LittleJS Input System
|
|
1912
|
+
* <br> - Tracks key down, pressed, and released
|
|
1913
|
+
* <br> - Also tracks mouse buttons, position, and wheel
|
|
1914
|
+
* <br> - Supports multiple gamepads
|
|
1915
|
+
* <br> - Virtual gamepad for touch devices with touchGamepadSize
|
|
1916
|
+
* @namespace Input
|
|
2085
1917
|
*/
|
|
2086
1918
|
|
|
2087
1919
|
'use strict';
|
|
@@ -2109,7 +1941,7 @@ const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][
|
|
|
2109
1941
|
|
|
2110
1942
|
/** Clears all input
|
|
2111
1943
|
* @memberof Input */
|
|
2112
|
-
const clearInput = ()=> inputData
|
|
1944
|
+
const clearInput = ()=> inputData = [[]];
|
|
2113
1945
|
|
|
2114
1946
|
/** Returns true if mouse button is down
|
|
2115
1947
|
* @param {Number} button
|
|
@@ -2145,7 +1977,7 @@ let mouseWheel = 0;
|
|
|
2145
1977
|
|
|
2146
1978
|
/** Returns true if user is using gamepad (has more recently pressed a gamepad button)
|
|
2147
1979
|
* @memberof Input */
|
|
2148
|
-
let
|
|
1980
|
+
let isUsingGamepad = 0;
|
|
2149
1981
|
|
|
2150
1982
|
/** Returns true if gamepad button is down
|
|
2151
1983
|
* @param {Number} button
|
|
@@ -2178,7 +2010,9 @@ const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepa
|
|
|
2178
2010
|
///////////////////////////////////////////////////////////////////////////////
|
|
2179
2011
|
// Input update called by engine
|
|
2180
2012
|
|
|
2181
|
-
|
|
2013
|
+
// store input as a bit field for each key: 1 = isDown, 2 = wasPressed, 4 = wasReleased
|
|
2014
|
+
// mouse and keyboard are stored together in device 0, gamepads are in devices > 0
|
|
2015
|
+
let inputData = [[]];
|
|
2182
2016
|
|
|
2183
2017
|
function inputUpdate()
|
|
2184
2018
|
{
|
|
@@ -2204,34 +2038,38 @@ function inputUpdatePost()
|
|
|
2204
2038
|
///////////////////////////////////////////////////////////////////////////////
|
|
2205
2039
|
// Keyboard event handlers
|
|
2206
2040
|
|
|
2207
|
-
onkeydown = e=>
|
|
2041
|
+
onkeydown = (e)=>
|
|
2208
2042
|
{
|
|
2209
2043
|
if (debug && e.target != document.body) return;
|
|
2210
|
-
e.repeat || (inputData[
|
|
2044
|
+
e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
|
|
2211
2045
|
debug || e.preventDefault();
|
|
2212
2046
|
}
|
|
2213
|
-
onkeyup = e=>
|
|
2047
|
+
onkeyup = (e)=>
|
|
2214
2048
|
{
|
|
2215
2049
|
if (debug && e.target != document.body) return;
|
|
2216
2050
|
inputData[0][remapKeyCode(e.keyCode)] = 4;
|
|
2217
2051
|
}
|
|
2218
|
-
const remapKeyCode = c=>
|
|
2052
|
+
const remapKeyCode = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
2219
2053
|
|
|
2220
2054
|
///////////////////////////////////////////////////////////////////////////////
|
|
2221
2055
|
// Mouse event handlers
|
|
2222
2056
|
|
|
2223
|
-
onmousedown = e=> {inputData[
|
|
2224
|
-
onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2225
|
-
onmousemove = e=>
|
|
2057
|
+
onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
2058
|
+
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2059
|
+
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2060
|
+
onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
2061
|
+
oncontextmenu = (e)=> !1; // prevent right click menu
|
|
2062
|
+
|
|
2063
|
+
// convert a mouse or touch event position to screen space
|
|
2064
|
+
const mouseToScreen = (mousePos)=>
|
|
2226
2065
|
{
|
|
2227
|
-
|
|
2228
|
-
|
|
2066
|
+
if (!mainCanvas)
|
|
2067
|
+
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
2068
|
+
|
|
2229
2069
|
const rect = mainCanvas.getBoundingClientRect();
|
|
2230
|
-
|
|
2231
|
-
|
|
2070
|
+
return mainCanvasSize.multiply(
|
|
2071
|
+
vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
|
|
2232
2072
|
}
|
|
2233
|
-
onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
2234
|
-
oncontextmenu = e=> !1; // prevent right click menu
|
|
2235
2073
|
|
|
2236
2074
|
///////////////////////////////////////////////////////////////////////////////
|
|
2237
2075
|
// Gamepad input
|
|
@@ -2239,6 +2077,21 @@ oncontextmenu = e=> !1; // prevent right click menu
|
|
|
2239
2077
|
const stickData = [];
|
|
2240
2078
|
function gamepadsUpdate()
|
|
2241
2079
|
{
|
|
2080
|
+
if (touchGamepadEnable && touchGamepadTimer.isSet())
|
|
2081
|
+
{
|
|
2082
|
+
// read virtual analog stick
|
|
2083
|
+
const sticks = stickData[0] || (stickData[0] = []);
|
|
2084
|
+
sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
|
|
2085
|
+
|
|
2086
|
+
// read virtual gamepad buttons
|
|
2087
|
+
const data = inputData[1] || (inputData[1] = []);
|
|
2088
|
+
for (let i=10; i--;)
|
|
2089
|
+
{
|
|
2090
|
+
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
2091
|
+
data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2242
2095
|
if (!gamepadsEnable || !navigator.getGamepads || !document.hasFocus() && !debug)
|
|
2243
2096
|
return;
|
|
2244
2097
|
|
|
@@ -2256,8 +2109,8 @@ function gamepadsUpdate()
|
|
|
2256
2109
|
// read clamp dead zone of analog sticks
|
|
2257
2110
|
const deadZone = .3, deadZoneMax = .8;
|
|
2258
2111
|
const applyDeadZone = (v)=>
|
|
2259
|
-
v > deadZone ? percent( v,
|
|
2260
|
-
v < -deadZone ? -percent(-v,
|
|
2112
|
+
v > deadZone ? percent( v, deadZone, deadZoneMax) :
|
|
2113
|
+
v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
|
|
2261
2114
|
|
|
2262
2115
|
// read analog sticks
|
|
2263
2116
|
for (let j = 0; j < gamepad.axes.length-1; j+=2)
|
|
@@ -2268,10 +2121,11 @@ function gamepadsUpdate()
|
|
|
2268
2121
|
{
|
|
2269
2122
|
const button = gamepad.buttons[j];
|
|
2270
2123
|
data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
|
|
2271
|
-
|
|
2124
|
+
isUsingGamepad |= !i && button.pressed;
|
|
2125
|
+
touchGamepadEnable && touchGamepadTimer.unset(); // disable touch gamepad if using real gamepad
|
|
2272
2126
|
}
|
|
2273
|
-
|
|
2274
|
-
if (
|
|
2127
|
+
|
|
2128
|
+
if (gamepadDirectionEmulateStick)
|
|
2275
2129
|
{
|
|
2276
2130
|
// copy dpad to left analog stick when pressed
|
|
2277
2131
|
const dpad = vec2(gamepadIsDown(15,i) - gamepadIsDown(14,i), gamepadIsDown(12,i) - gamepadIsDown(13,i));
|
|
@@ -2282,18 +2136,31 @@ function gamepadsUpdate()
|
|
|
2282
2136
|
}
|
|
2283
2137
|
}
|
|
2284
2138
|
|
|
2139
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2140
|
+
|
|
2141
|
+
/** Pulse the vibration hardware if it exists
|
|
2142
|
+
* @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
|
|
2143
|
+
* @memberof Input */
|
|
2144
|
+
const vibrate = (pattern)=> vibrateEnable && Navigator.vibrate && Navigator.vibrate(pattern);
|
|
2145
|
+
|
|
2146
|
+
/** Cancel any ongoing vibration
|
|
2147
|
+
* @memberof Input */
|
|
2148
|
+
const vibrateStop = ()=> vibrate(0);
|
|
2149
|
+
|
|
2285
2150
|
///////////////////////////////////////////////////////////////////////////////
|
|
2286
2151
|
// Touch input
|
|
2287
2152
|
|
|
2288
2153
|
/** True if a touch device has been detected
|
|
2289
2154
|
* @const {boolean}
|
|
2290
2155
|
* @memberof Input */
|
|
2291
|
-
const isTouchDevice =
|
|
2156
|
+
const isTouchDevice = window.ontouchstart !== undefined;
|
|
2157
|
+
|
|
2158
|
+
// try to enable touch mouse
|
|
2292
2159
|
if (isTouchDevice)
|
|
2293
2160
|
{
|
|
2294
2161
|
// handle all touch events the same way
|
|
2295
2162
|
let wasTouching, hadTouchInput;
|
|
2296
|
-
ontouchstart = ontouchmove = ontouchend = e=>
|
|
2163
|
+
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2297
2164
|
{
|
|
2298
2165
|
e.button = 0; // all touches are left click
|
|
2299
2166
|
|
|
@@ -2318,57 +2185,184 @@ if (isTouchDevice)
|
|
|
2318
2185
|
return !e.cancelable;
|
|
2319
2186
|
}
|
|
2320
2187
|
}
|
|
2321
|
-
/**
|
|
2322
|
-
* LittleJS Audio System
|
|
2323
|
-
* <br> - ZzFX Sound Effects and ZzFXM Music
|
|
2324
|
-
* <br> - Caches sounds and music for fast playback
|
|
2325
|
-
* <br> - Can attenuate and apply stereo panning to sounds
|
|
2326
|
-
* <br> - Ability to play mp3, ogg, and wave files
|
|
2327
|
-
* <br> - Speech synthesis wrapper functions
|
|
2328
|
-
* @namespace Audio
|
|
2329
|
-
*/
|
|
2330
2188
|
|
|
2331
|
-
|
|
2189
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2190
|
+
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
2332
2191
|
|
|
2333
|
-
|
|
2334
|
-
|
|
2192
|
+
// touch input internal variables
|
|
2193
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2(), touchGamepadAnalog = 1;
|
|
2194
|
+
|
|
2195
|
+
// create the touch gamepad, called automatically by the engine
|
|
2196
|
+
function touchGamepadCreate()
|
|
2335
2197
|
{
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
*/
|
|
2341
|
-
constructor(zzfxSound, range=defaultSoundRange, taper=defaultSoundTaper)
|
|
2198
|
+
if (!touchGamepadEnable || !isTouchDevice)
|
|
2199
|
+
return;
|
|
2200
|
+
|
|
2201
|
+
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2342
2202
|
{
|
|
2343
|
-
if (!
|
|
2203
|
+
if (!touchGamepadEnable)
|
|
2204
|
+
return;
|
|
2344
2205
|
|
|
2345
|
-
|
|
2346
|
-
|
|
2206
|
+
// clear touch gamepad input
|
|
2207
|
+
touchGamepadStick = vec2();
|
|
2208
|
+
touchGamepadButtons = [];
|
|
2209
|
+
|
|
2210
|
+
const touching = e.touches.length;
|
|
2211
|
+
if (touching)
|
|
2212
|
+
{
|
|
2213
|
+
touchGamepadTimer.isSet() || zzfx(0) ; // fix mobile audio, force it to play a sound the first time
|
|
2347
2214
|
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2215
|
+
// set that gamepad is active
|
|
2216
|
+
isUsingGamepad = 1;
|
|
2217
|
+
touchGamepadTimer.set();
|
|
2351
2218
|
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2219
|
+
if (paused)
|
|
2220
|
+
{
|
|
2221
|
+
// touch anywhere to press start when paused
|
|
2222
|
+
touchGamepadButtons[9] = 1;
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2355
2226
|
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
* @param {Number} [randomnessScale=1] - How much to scale randomness
|
|
2361
|
-
* @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
|
|
2362
|
-
*/
|
|
2363
|
-
play(pos, volume=1, pitch=1, randomnessScale=1)
|
|
2364
|
-
{
|
|
2365
|
-
if (!soundEnable) return;
|
|
2227
|
+
// get center of left and right sides
|
|
2228
|
+
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2229
|
+
const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
|
|
2230
|
+
const startCenter = mainCanvasSize.scale(.5);
|
|
2366
2231
|
|
|
2367
|
-
|
|
2368
|
-
|
|
2232
|
+
// check each touch point
|
|
2233
|
+
for (const touch of e.touches)
|
|
2369
2234
|
{
|
|
2370
|
-
const
|
|
2371
|
-
if (
|
|
2235
|
+
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
2236
|
+
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
2237
|
+
{
|
|
2238
|
+
// virtual analog stick
|
|
2239
|
+
if (touchGamepadAnalog)
|
|
2240
|
+
touchGamepadStick = touchPos.subtract(stickCenter).clampLength(touchGamepadSize/2).scale(2/touchGamepadSize);
|
|
2241
|
+
else
|
|
2242
|
+
{
|
|
2243
|
+
// 8 way dpad
|
|
2244
|
+
const angle = touchPos.subtract(stickCenter).angle();
|
|
2245
|
+
touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
2249
|
+
{
|
|
2250
|
+
// virtual face buttons
|
|
2251
|
+
const button = touchPos.subtract(buttonCenter).direction();
|
|
2252
|
+
touchGamepadButtons[button] = 1;
|
|
2253
|
+
}
|
|
2254
|
+
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
2255
|
+
{
|
|
2256
|
+
// virtual start button in center
|
|
2257
|
+
touchGamepadButtons[9] = 1;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
// render the touch gamepad, called automatically by the engine
|
|
2264
|
+
function touchGamepadRender()
|
|
2265
|
+
{
|
|
2266
|
+
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
2267
|
+
return;
|
|
2268
|
+
|
|
2269
|
+
// fade off when not touching or paused
|
|
2270
|
+
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
2271
|
+
if (!alpha || paused)
|
|
2272
|
+
return;
|
|
2273
|
+
|
|
2274
|
+
// setup the canvas
|
|
2275
|
+
overlayContext.save();
|
|
2276
|
+
overlayContext.globalAlpha = alpha*touchGamepadAlpha;
|
|
2277
|
+
overlayContext.strokeStyle = '#fff';
|
|
2278
|
+
overlayContext.lineWidth = 3;
|
|
2279
|
+
|
|
2280
|
+
// draw left analog stick
|
|
2281
|
+
overlayContext.fillStyle = gamepadStick(0).length() > 0 ? '#fff' : '#000';
|
|
2282
|
+
overlayContext.beginPath();
|
|
2283
|
+
overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize/2, 0,9);
|
|
2284
|
+
overlayContext.fill();
|
|
2285
|
+
overlayContext.stroke();
|
|
2286
|
+
|
|
2287
|
+
// draw right face buttons
|
|
2288
|
+
const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2289
|
+
for (let i=4; i--;)
|
|
2290
|
+
{
|
|
2291
|
+
const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize/2));
|
|
2292
|
+
overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
2293
|
+
overlayContext.beginPath();
|
|
2294
|
+
overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
2295
|
+
overlayContext.fill();
|
|
2296
|
+
overlayContext.stroke();
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
// set canvas back to normal
|
|
2300
|
+
overlayContext.restore();
|
|
2301
|
+
}
|
|
2302
|
+
/**
|
|
2303
|
+
* LittleJS Audio System
|
|
2304
|
+
* <br> - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a>
|
|
2305
|
+
* <br> - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a>
|
|
2306
|
+
* <br> - Caches sounds and music for fast playback
|
|
2307
|
+
* <br> - Can attenuate and apply stereo panning to sounds
|
|
2308
|
+
* <br> - Ability to play mp3, ogg, and wave files
|
|
2309
|
+
* <br> - Speech synthesis wrapper functions
|
|
2310
|
+
*/
|
|
2311
|
+
|
|
2312
|
+
'use strict';
|
|
2313
|
+
|
|
2314
|
+
/**
|
|
2315
|
+
* Sound Object - Stores a zzfx sound for later use and can be played positionally
|
|
2316
|
+
* <br>
|
|
2317
|
+
* <br><b><a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a></b>
|
|
2318
|
+
* @example
|
|
2319
|
+
* // create a sound
|
|
2320
|
+
* const sound_example = new Sound([.5,.5]);
|
|
2321
|
+
*
|
|
2322
|
+
* // play the sound
|
|
2323
|
+
* sound_example.play();
|
|
2324
|
+
*/
|
|
2325
|
+
class Sound
|
|
2326
|
+
{
|
|
2327
|
+
/** Create a sound object and cache the zzfx samples for later use
|
|
2328
|
+
* @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
|
|
2329
|
+
* @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
|
|
2330
|
+
* @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
|
|
2331
|
+
*/
|
|
2332
|
+
constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
|
|
2333
|
+
{
|
|
2334
|
+
if (!soundEnable) return;
|
|
2335
|
+
|
|
2336
|
+
/** @property {Number} - World space max range of sound, will not play if camera is farther away */
|
|
2337
|
+
this.range = range;
|
|
2338
|
+
|
|
2339
|
+
/** @property {Number} - At what percentage of range should it start tapering off */
|
|
2340
|
+
this.taper = taper;
|
|
2341
|
+
|
|
2342
|
+
// get randomness from sound parameters
|
|
2343
|
+
this.randomness = zzfxSound[1] || 0;
|
|
2344
|
+
zzfxSound[1] = 0;
|
|
2345
|
+
|
|
2346
|
+
// generate sound now for fast playback
|
|
2347
|
+
this.cachedSamples = zzfxG(...zzfxSound);
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
/** Play the sound
|
|
2351
|
+
* @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
|
|
2352
|
+
* @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
|
|
2353
|
+
* @param {Number} [pitch=1] - How much to scale pitch by (also adjusted by this.randomness)
|
|
2354
|
+
* @param {Number} [randomnessScale=1] - How much to scale randomness
|
|
2355
|
+
* @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
|
|
2356
|
+
*/
|
|
2357
|
+
play(pos, volume=1, pitch=1, randomnessScale=1)
|
|
2358
|
+
{
|
|
2359
|
+
if (!soundEnable) return;
|
|
2360
|
+
|
|
2361
|
+
let pan = 0;
|
|
2362
|
+
if (pos)
|
|
2363
|
+
{
|
|
2364
|
+
const range = this.range;
|
|
2365
|
+
if (range)
|
|
2372
2366
|
{
|
|
2373
2367
|
// apply range based fade
|
|
2374
2368
|
const lengthSquared = cameraPos.distanceSquared(pos);
|
|
@@ -2376,7 +2370,7 @@ class Sound
|
|
|
2376
2370
|
return; // out of range
|
|
2377
2371
|
|
|
2378
2372
|
// attenuate volume by distance
|
|
2379
|
-
volume *= percent(lengthSquared**.5, range*this.taper
|
|
2373
|
+
volume *= percent(lengthSquared**.5, range, range*this.taper);
|
|
2380
2374
|
}
|
|
2381
2375
|
|
|
2382
2376
|
// get pan from screen space coords
|
|
@@ -2402,7 +2396,36 @@ class Sound
|
|
|
2402
2396
|
}
|
|
2403
2397
|
}
|
|
2404
2398
|
|
|
2405
|
-
/**
|
|
2399
|
+
/**
|
|
2400
|
+
* Music Object - Stores a zzfx music track for later use
|
|
2401
|
+
* <br>
|
|
2402
|
+
* <br><b><a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a></b>
|
|
2403
|
+
* @example
|
|
2404
|
+
* // create some music
|
|
2405
|
+
* const music_example = new Music(
|
|
2406
|
+
* [
|
|
2407
|
+
* [ // instruments
|
|
2408
|
+
* [,0,400] // simple note
|
|
2409
|
+
* ],
|
|
2410
|
+
* [ // patterns
|
|
2411
|
+
* [ // pattern 1
|
|
2412
|
+
* [ // channel 0
|
|
2413
|
+
* 0, -1, // instrument 0, left speaker
|
|
2414
|
+
* 1, 0, 9, 1 // channel notes
|
|
2415
|
+
* ],
|
|
2416
|
+
* [ // channel 1
|
|
2417
|
+
* 0, 1, // instrument 1, right speaker
|
|
2418
|
+
* 0, 12, 17, -1 // channel notes
|
|
2419
|
+
* ]
|
|
2420
|
+
* ],
|
|
2421
|
+
* ],
|
|
2422
|
+
* [0, 0, 0, 0], // sequence, play pattern 0 four times
|
|
2423
|
+
* 90 // BPM
|
|
2424
|
+
* ]);
|
|
2425
|
+
*
|
|
2426
|
+
* // play the music
|
|
2427
|
+
* music_example.play();
|
|
2428
|
+
*/
|
|
2406
2429
|
class Music
|
|
2407
2430
|
{
|
|
2408
2431
|
/** Create a music object and cache the zzfx music samples for later use
|
|
@@ -2439,7 +2462,7 @@ function playAudioFile(url, volume=1, loop=1)
|
|
|
2439
2462
|
if (!soundEnable) return;
|
|
2440
2463
|
|
|
2441
2464
|
const audio = new Audio(url);
|
|
2442
|
-
audio.volume =
|
|
2465
|
+
audio.volume = soundVolume * volume;
|
|
2443
2466
|
audio.loop = loop;
|
|
2444
2467
|
audio.play();
|
|
2445
2468
|
return audio;
|
|
@@ -2464,7 +2487,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
2464
2487
|
// build utterance and speak
|
|
2465
2488
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
2466
2489
|
utterance.lang = language;
|
|
2467
|
-
utterance.volume = 2*volume*
|
|
2490
|
+
utterance.volume = 2*volume*soundVolume;
|
|
2468
2491
|
utterance.rate = rate;
|
|
2469
2492
|
utterance.pitch = pitch;
|
|
2470
2493
|
speechSynthesis.speak(utterance);
|
|
@@ -2473,7 +2496,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
2473
2496
|
|
|
2474
2497
|
/** Stop all queued speech
|
|
2475
2498
|
* @memberof Audio */
|
|
2476
|
-
const
|
|
2499
|
+
const speakStop = ()=> speechSynthesis && speechSynthesis.cancel();
|
|
2477
2500
|
|
|
2478
2501
|
/** Get frequency of a note on a musical scale
|
|
2479
2502
|
* @param {Number} semitoneOffset - How many semitones away from the root note
|
|
@@ -2504,6 +2527,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2504
2527
|
if (!audioContext)
|
|
2505
2528
|
audioContext = new (window.AudioContext||webkitAudioContext);
|
|
2506
2529
|
|
|
2530
|
+
// fix stalled audio
|
|
2531
|
+
audioContext.resume();
|
|
2532
|
+
|
|
2533
|
+
// prevent sounds from building up if they can't be played
|
|
2534
|
+
if (audioContext.state != 'running')
|
|
2535
|
+
return;
|
|
2536
|
+
|
|
2507
2537
|
// create buffer and source
|
|
2508
2538
|
const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, zzfxR),
|
|
2509
2539
|
source = audioContext.createBufferSource();
|
|
@@ -2514,11 +2544,18 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2514
2544
|
source.playbackRate.value = rate;
|
|
2515
2545
|
source.loop = loop;
|
|
2516
2546
|
|
|
2517
|
-
// create
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2547
|
+
// create and connect gain node (createGain is more widley spported then GainNode construtor)
|
|
2548
|
+
const gainNode = audioContext.createGain();
|
|
2549
|
+
gainNode.gain.value = soundVolume*volume;
|
|
2550
|
+
gainNode.connect(audioContext.destination);
|
|
2551
|
+
|
|
2552
|
+
// connect source to gain
|
|
2553
|
+
(
|
|
2554
|
+
window.StereoPannerNode ? // create pan node if possible
|
|
2555
|
+
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
|
|
2556
|
+
: source
|
|
2557
|
+
)
|
|
2558
|
+
.connect(gainNode);
|
|
2522
2559
|
|
|
2523
2560
|
// play and return sound
|
|
2524
2561
|
source.start();
|
|
@@ -2529,6 +2566,8 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2529
2566
|
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.1.8 by Frank Force
|
|
2530
2567
|
|
|
2531
2568
|
/** Generate and play a ZzFX sound
|
|
2569
|
+
* <br>
|
|
2570
|
+
* <br><b><a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a></b>
|
|
2532
2571
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
2533
2572
|
* @return {Array} - Array of audio samples
|
|
2534
2573
|
* @memberof Audio */
|
|
@@ -2585,7 +2624,7 @@ function zzfxG
|
|
|
2585
2624
|
1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
|
|
2586
2625
|
: 1) *
|
|
2587
2626
|
sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
|
|
2588
|
-
volume *
|
|
2627
|
+
volume * soundVolume * ( // envelope
|
|
2589
2628
|
i < attack ? i/attack : // attack
|
|
2590
2629
|
i < attack + decay ? // decay
|
|
2591
2630
|
1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
|
|
@@ -2731,27 +2770,31 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
2731
2770
|
return [leftChannelBuffer, rightChannelBuffer];
|
|
2732
2771
|
}
|
|
2733
2772
|
/**
|
|
2734
|
-
*
|
|
2735
|
-
*
|
|
2736
|
-
*
|
|
2737
|
-
*
|
|
2738
|
-
*
|
|
2739
|
-
*
|
|
2740
|
-
*
|
|
2741
|
-
*
|
|
2773
|
+
* LittleJS Tile Layer System
|
|
2774
|
+
* <br> - Caches arrays of tiles to off screen canvas for fast rendering
|
|
2775
|
+
* <br> - Unlimted numbers of layers, allocates canvases as needed
|
|
2776
|
+
* <br> - Interfaces with EngineObject for collision
|
|
2777
|
+
* <br> - Collision layer is separate from visible layers
|
|
2778
|
+
* <br> - It is recommended to have a visible layer that matches the collision
|
|
2779
|
+
* <br> - Tile layers can be drawn to using their context with canvas2d
|
|
2780
|
+
* <br> - Drawn directly to the main canvas without using WebGL
|
|
2781
|
+
* @namespace TileCollision
|
|
2742
2782
|
*/
|
|
2743
2783
|
|
|
2744
2784
|
'use strict';
|
|
2745
2785
|
|
|
2746
|
-
|
|
2747
|
-
|
|
2786
|
+
/** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access
|
|
2787
|
+
* @memberof TileCollision */
|
|
2788
|
+
let tileCollision = [];
|
|
2748
2789
|
|
|
2749
|
-
|
|
2750
|
-
|
|
2790
|
+
/** Size of the tile collision layer
|
|
2791
|
+
* @type {Vector2}
|
|
2792
|
+
* @memberof TileCollision */
|
|
2793
|
+
let tileCollisionSize = vec2();
|
|
2751
2794
|
|
|
2752
2795
|
/** Clear and initialize tile collision
|
|
2753
2796
|
* @param {Vector2} size
|
|
2754
|
-
* @memberof
|
|
2797
|
+
* @memberof TileCollision */
|
|
2755
2798
|
function initTileCollision(size)
|
|
2756
2799
|
{
|
|
2757
2800
|
tileCollisionSize = size;
|
|
@@ -2763,14 +2806,14 @@ function initTileCollision(size)
|
|
|
2763
2806
|
/** Set tile collision data
|
|
2764
2807
|
* @param {Vector2} pos
|
|
2765
2808
|
* @param {Number} [data=0]
|
|
2766
|
-
* @memberof
|
|
2809
|
+
* @memberof TileCollision */
|
|
2767
2810
|
const setTileCollisionData = (pos, data=0)=>
|
|
2768
2811
|
pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
|
|
2769
2812
|
|
|
2770
2813
|
/** Get tile collision data
|
|
2771
2814
|
* @param {Vector2} pos
|
|
2772
2815
|
* @return {Number}
|
|
2773
|
-
* @memberof
|
|
2816
|
+
* @memberof TileCollision */
|
|
2774
2817
|
const getTileCollisionData = (pos)=>
|
|
2775
2818
|
pos.arrayCheck(tileCollisionSize) ? tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] : 0;
|
|
2776
2819
|
|
|
@@ -2779,13 +2822,13 @@ const getTileCollisionData = (pos)=>
|
|
|
2779
2822
|
* @param {Vector2} [size=new Vector2(1,1)]
|
|
2780
2823
|
* @param {EngineObject} [object]
|
|
2781
2824
|
* @return {Boolean}
|
|
2782
|
-
* @memberof
|
|
2825
|
+
* @memberof TileCollision */
|
|
2783
2826
|
function tileCollisionTest(pos, size=vec2(), object)
|
|
2784
2827
|
{
|
|
2785
|
-
const minX = max(
|
|
2786
|
-
const minY = max(
|
|
2787
|
-
const maxX = min(pos.x + size.x/2, tileCollisionSize.x
|
|
2788
|
-
const maxY = min(pos.y + size.y/2, tileCollisionSize.y
|
|
2828
|
+
const minX = max(pos.x - size.x/2|0, 0);
|
|
2829
|
+
const minY = max(pos.y - size.y/2|0, 0);
|
|
2830
|
+
const maxX = min(pos.x + size.x/2, tileCollisionSize.x);
|
|
2831
|
+
const maxY = min(pos.y + size.y/2, tileCollisionSize.y);
|
|
2789
2832
|
for (let y = minY; y < maxY; ++y)
|
|
2790
2833
|
for (let x = minX; x < maxX; ++x)
|
|
2791
2834
|
{
|
|
@@ -2800,7 +2843,7 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
2800
2843
|
* @param {Vector2} posEnd
|
|
2801
2844
|
* @param {EngineObject} [object]
|
|
2802
2845
|
* @return {Vector2}
|
|
2803
|
-
* @memberof
|
|
2846
|
+
* @memberof TileCollision */
|
|
2804
2847
|
function tileCollisionRaycast(posStart, posEnd, object)
|
|
2805
2848
|
{
|
|
2806
2849
|
// test if a ray collides with tiles from start to end
|
|
@@ -2834,23 +2877,32 @@ function tileCollisionRaycast(posStart, posEnd, object)
|
|
|
2834
2877
|
///////////////////////////////////////////////////////////////////////////////
|
|
2835
2878
|
// Tile Layer Rendering System
|
|
2836
2879
|
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2880
|
+
/**
|
|
2881
|
+
* Tile layer data object stores info about how to render a tile
|
|
2882
|
+
* @example
|
|
2883
|
+
* // create tile layer data with tile index 0 and random orientation and color
|
|
2884
|
+
* const tileIndex = 0;
|
|
2885
|
+
* const direction = randInt(4)
|
|
2886
|
+
* const mirror = randInt(2);
|
|
2887
|
+
* const color = randColor();
|
|
2888
|
+
* const data = new TileLayerData(tileIndex, direction, mirror, color);
|
|
2889
|
+
*/
|
|
2841
2890
|
class TileLayerData
|
|
2842
2891
|
{
|
|
2843
|
-
/** Create a tile layer data object
|
|
2844
|
-
* @param {Number} [tile]
|
|
2845
|
-
* @param {Number} [direction=0]
|
|
2846
|
-
* @param {Boolean} [mirror=0]
|
|
2847
|
-
* @param {Color} [color=new Color(1,1,1)] - Color of the tile
|
|
2848
|
-
*/
|
|
2892
|
+
/** Create a tile layer data object, one for each tile in a TileLayer
|
|
2893
|
+
* @param {Number} [tile] - The tile to use, untextured if undefined
|
|
2894
|
+
* @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments
|
|
2895
|
+
* @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis
|
|
2896
|
+
* @param {Color} [color=new Color(1,1,1)] - Color of the tile */
|
|
2849
2897
|
constructor(tile, direction=0, mirror=0, color=new Color)
|
|
2850
2898
|
{
|
|
2899
|
+
/** @property {Number} - The tile to use, untextured if undefined */
|
|
2851
2900
|
this.tile = tile;
|
|
2901
|
+
/** @property {Number} - Integer direction of tile, in 90 degree increments */
|
|
2852
2902
|
this.direction = direction;
|
|
2903
|
+
/** @property {Boolean} - If the tile should be mirrored along the x axis */
|
|
2853
2904
|
this.mirror = mirror;
|
|
2905
|
+
/** @property {Color} - Color of the tile */
|
|
2854
2906
|
this.color = color;
|
|
2855
2907
|
}
|
|
2856
2908
|
|
|
@@ -2858,44 +2910,49 @@ class TileLayerData
|
|
|
2858
2910
|
clear() { this.tile = this.direction = this.mirror = 0; color = new Color; }
|
|
2859
2911
|
}
|
|
2860
2912
|
|
|
2861
|
-
/**
|
|
2913
|
+
/**
|
|
2914
|
+
* Tile layer object - cached rendering system for tile layers
|
|
2915
|
+
* <br> - Each Tile layer is rendered to an off screen canvas
|
|
2916
|
+
* <br> - To allow dynamic modifications, layers are rendered using canvas 2d
|
|
2917
|
+
* <br> - Some devices like mobile phones are limited to 4k texture resolution
|
|
2918
|
+
* <br> - So with 16x16 tiles this limits layers to 256x256 on mobile devices
|
|
2919
|
+
* @extends EngineObject
|
|
2920
|
+
* @example
|
|
2921
|
+
* // create tile collision and visible tile layer
|
|
2922
|
+
* initTileCollision(vec2(200,100));
|
|
2923
|
+
* const tileLayer = new TileLayer();
|
|
2924
|
+
*/
|
|
2862
2925
|
class TileLayer extends EngineObject
|
|
2863
2926
|
{
|
|
2864
|
-
/** Create a tile layer
|
|
2927
|
+
/** Create a tile layer object
|
|
2865
2928
|
* @param {Vector2} [position=new Vector2(0,0)] - World space position
|
|
2866
|
-
* @param {Vector2} [size=
|
|
2867
|
-
* @param {Vector2} [
|
|
2868
|
-
* @param {
|
|
2929
|
+
* @param {Vector2} [size=objectDefaultSize] - World space size
|
|
2930
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
|
|
2931
|
+
* @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
|
|
2932
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
2869
2933
|
*/
|
|
2870
|
-
constructor(pos, size, scale=vec2(1), renderOrder=0)
|
|
2934
|
+
constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
|
|
2871
2935
|
{
|
|
2872
|
-
super(pos, size);
|
|
2936
|
+
super(pos, size, -1, tileSize, 0, undefined, renderOrder);
|
|
2873
2937
|
|
|
2874
|
-
|
|
2875
|
-
this.canvas =
|
|
2938
|
+
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
2939
|
+
this.canvas = document.createElement('canvas');
|
|
2940
|
+
/** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
2876
2941
|
this.context = this.canvas.getContext('2d');
|
|
2942
|
+
/** @property {Vector2} - How much to scale this layer when rendered */
|
|
2877
2943
|
this.scale = scale;
|
|
2878
|
-
|
|
2879
|
-
this.
|
|
2880
|
-
this.flushGLBeforeRender = 1;
|
|
2944
|
+
/** @property {Boolean} [isOverlay=0] - If true this layer will render to overlay canvas and appear above all objects */
|
|
2945
|
+
this.isOverlay;
|
|
2881
2946
|
|
|
2882
2947
|
// init tile data
|
|
2883
2948
|
this.data = [];
|
|
2884
2949
|
for (let j = this.size.area(); j--;)
|
|
2885
2950
|
this.data.push(new TileLayerData());
|
|
2886
2951
|
}
|
|
2887
|
-
|
|
2888
|
-
/** Destroy this tile layer */
|
|
2889
|
-
destroy()
|
|
2890
|
-
{
|
|
2891
|
-
// add canvas back to the cache
|
|
2892
|
-
tileLayerCanvasCache.push(this.canvas);
|
|
2893
|
-
super.destroy();
|
|
2894
|
-
}
|
|
2895
2952
|
|
|
2896
2953
|
/** Set data at a given position in the array
|
|
2897
|
-
* @param {Vector2} position
|
|
2898
|
-
* @param {TileLayerData} data
|
|
2954
|
+
* @param {Vector2} position - Local position in array
|
|
2955
|
+
* @param {TileLayerData} data - Data to set
|
|
2899
2956
|
* @param {Boolean} [redraw=0] - Force the tile to redraw if true */
|
|
2900
2957
|
setData(layerPos, data, redraw)
|
|
2901
2958
|
{
|
|
@@ -2920,50 +2977,48 @@ class TileLayer extends EngineObject
|
|
|
2920
2977
|
{
|
|
2921
2978
|
ASSERT(mainContext != this.context); // must call redrawEnd() after drawing tiles
|
|
2922
2979
|
|
|
2923
|
-
// flush and copy gl canvas because tile canvas does not use
|
|
2924
|
-
|
|
2980
|
+
// flush and copy gl canvas because tile canvas does not use webgl
|
|
2981
|
+
glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
2925
2982
|
|
|
2926
|
-
// draw the entire cached level onto the
|
|
2983
|
+
// draw the entire cached level onto the canvas
|
|
2927
2984
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
2928
|
-
mainContext.drawImage
|
|
2985
|
+
(this.isOverlay ? overlayContext : mainContext).drawImage
|
|
2929
2986
|
(
|
|
2930
2987
|
this.canvas, pos.x, pos.y,
|
|
2931
2988
|
cameraScale*this.size.x*this.scale.x, cameraScale*this.size.y*this.scale.y
|
|
2932
2989
|
);
|
|
2933
2990
|
}
|
|
2934
2991
|
|
|
2935
|
-
/** Draw all the tile data to an offscreen canvas
|
|
2992
|
+
/** Draw all the tile data to an offscreen canvas
|
|
2993
|
+
* - This may be slow in some browsers
|
|
2994
|
+
*/
|
|
2936
2995
|
redraw()
|
|
2937
2996
|
{
|
|
2938
|
-
this.redrawStart();
|
|
2997
|
+
this.redrawStart(1);
|
|
2939
2998
|
this.drawAllTileData();
|
|
2940
2999
|
this.redrawEnd();
|
|
2941
3000
|
}
|
|
2942
3001
|
|
|
2943
3002
|
/** Call to start the redraw process
|
|
2944
|
-
* @param {Boolean} [clear=
|
|
2945
|
-
redrawStart(clear =
|
|
3003
|
+
* @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
|
|
3004
|
+
redrawStart(clear = 0)
|
|
2946
3005
|
{
|
|
2947
|
-
// clear and set size
|
|
2948
|
-
const width = this.size.x * this.tileSize.x;
|
|
2949
|
-
const height = this.size.y * this.tileSize.y;
|
|
2950
3006
|
if (clear)
|
|
2951
3007
|
{
|
|
2952
|
-
|
|
2953
|
-
this.canvas.
|
|
3008
|
+
// clear and set size
|
|
3009
|
+
this.canvas.width = this.size.x * this.tileSize.x;
|
|
3010
|
+
this.canvas.height = this.size.y * this.tileSize.y;
|
|
2954
3011
|
}
|
|
2955
3012
|
|
|
2956
3013
|
// save current render settings
|
|
2957
|
-
this.savedRenderSettings = [
|
|
3014
|
+
this.savedRenderSettings = [mainCanvas, mainContext, cameraPos, cameraScale];
|
|
2958
3015
|
|
|
2959
|
-
//
|
|
2960
|
-
cameraScale = this.tileSize.x;
|
|
2961
|
-
cameraPos = this.size.scale(.5);
|
|
3016
|
+
// use normal rendering system to render the tiles
|
|
2962
3017
|
mainCanvas = this.canvas;
|
|
2963
3018
|
mainContext = this.context;
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
3019
|
+
cameraPos = this.size.scale(.5);
|
|
3020
|
+
cameraScale = this.tileSize.x;
|
|
3021
|
+
enginePreRender();
|
|
2967
3022
|
}
|
|
2968
3023
|
|
|
2969
3024
|
/** Call to end the redraw process */
|
|
@@ -2974,7 +3029,7 @@ class TileLayer extends EngineObject
|
|
|
2974
3029
|
//debugSaveCanvas(this.canvas);
|
|
2975
3030
|
|
|
2976
3031
|
// set stuff back to normal
|
|
2977
|
-
[
|
|
3032
|
+
[mainCanvas, mainContext, cameraPos, cameraScale] = this.savedRenderSettings;
|
|
2978
3033
|
}
|
|
2979
3034
|
|
|
2980
3035
|
/** Draw the tile at a given position
|
|
@@ -3002,13 +3057,13 @@ class TileLayer extends EngineObject
|
|
|
3002
3057
|
this.drawTileData(vec2(x,y));
|
|
3003
3058
|
}
|
|
3004
3059
|
|
|
3005
|
-
/** Draw directly to the
|
|
3060
|
+
/** Draw directly to the 2D canvas in world space (bipass webgl)
|
|
3006
3061
|
* @param {Vector2} pos
|
|
3007
3062
|
* @param {Vector2} size
|
|
3008
|
-
* @param {Number} angle
|
|
3009
|
-
* @param {Boolean} mirror
|
|
3063
|
+
* @param {Number} [angle=0]
|
|
3064
|
+
* @param {Boolean} [mirror=0]
|
|
3010
3065
|
* @param {Function} drawFunction */
|
|
3011
|
-
drawCanvas2D(pos, size, angle, mirror, drawFunction)
|
|
3066
|
+
drawCanvas2D(pos, size, angle=0, mirror, drawFunction)
|
|
3012
3067
|
{
|
|
3013
3068
|
const context = this.context;
|
|
3014
3069
|
context.save();
|
|
@@ -3016,7 +3071,7 @@ class TileLayer extends EngineObject
|
|
|
3016
3071
|
size = size.multiply(this.tileSize);
|
|
3017
3072
|
context.translate(pos.x, this.canvas.height - pos.y);
|
|
3018
3073
|
context.rotate(angle);
|
|
3019
|
-
context.scale(mirror
|
|
3074
|
+
context.scale(mirror ? -size.x : size.x, size.y);
|
|
3020
3075
|
drawFunction(context);
|
|
3021
3076
|
context.restore();
|
|
3022
3077
|
}
|
|
@@ -3025,11 +3080,11 @@ class TileLayer extends EngineObject
|
|
|
3025
3080
|
* @param {Vector2} pos
|
|
3026
3081
|
* @param {Vector2} [size=new Vector2(1,1)]
|
|
3027
3082
|
* @param {Number} [tileIndex=-1]
|
|
3028
|
-
* @param {Vector2} [tileSize=
|
|
3083
|
+
* @param {Vector2} [tileSize=tileSizeDefault]
|
|
3029
3084
|
* @param {Color} [color=new Color(1,1,1)]
|
|
3030
3085
|
* @param {Number} [angle=0]
|
|
3031
3086
|
* @param {Boolean} [mirror=0] */
|
|
3032
|
-
drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=
|
|
3087
|
+
drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle, mirror)
|
|
3033
3088
|
{
|
|
3034
3089
|
this.drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
3035
3090
|
{
|
|
@@ -3042,7 +3097,7 @@ class TileLayer extends EngineObject
|
|
|
3042
3097
|
else
|
|
3043
3098
|
{
|
|
3044
3099
|
const cols = tileImage.width/tileSize.x;
|
|
3045
|
-
context.globalAlpha = color.a; //
|
|
3100
|
+
context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
|
|
3046
3101
|
context.drawImage(tileImage,
|
|
3047
3102
|
(tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.x,
|
|
3048
3103
|
tileSize.x, tileSize.y, -.5, -.5, 1, 1);
|
|
@@ -3055,7 +3110,7 @@ class TileLayer extends EngineObject
|
|
|
3055
3110
|
* @param {Vector2} [size=new Vector2(1,1)]
|
|
3056
3111
|
* @param {Color} [color=new Color(1,1,1)]
|
|
3057
3112
|
* @param {Number} [angle=0] */
|
|
3058
|
-
drawRect(pos, size, color, angle) { this.drawTile(pos, size, -1, 0, color, angle
|
|
3113
|
+
drawRect(pos, size, color, angle) { this.drawTile(pos, size, -1, 0, color, angle); }
|
|
3059
3114
|
}
|
|
3060
3115
|
/*
|
|
3061
3116
|
LittleJS Particle System
|
|
@@ -3068,37 +3123,50 @@ class TileLayer extends EngineObject
|
|
|
3068
3123
|
|
|
3069
3124
|
/**
|
|
3070
3125
|
* Particle Emitter - Spawns particles with the given settings
|
|
3126
|
+
* @extends EngineObject
|
|
3127
|
+
* @example
|
|
3128
|
+
* // create a particle emitter
|
|
3129
|
+
* let pos = vec2(2,3);
|
|
3130
|
+
* let particleEmiter = new ParticleEmitter
|
|
3131
|
+
* (
|
|
3132
|
+
* pos, 1, 0, 500, PI, // pos, emitSize, emitTime, emitRate, emiteCone
|
|
3133
|
+
* 0, vec2(16), // tileIndex, tileSize
|
|
3134
|
+
* new Color, new Color(0,0,0), // colorStartA, colorStartB
|
|
3135
|
+
* new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
|
|
3136
|
+
* 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
3137
|
+
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
3138
|
+
* .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
|
|
3139
|
+
* );
|
|
3071
3140
|
*/
|
|
3072
3141
|
class ParticleEmitter extends EngineObject
|
|
3073
3142
|
{
|
|
3074
|
-
/**
|
|
3075
|
-
*
|
|
3076
|
-
*
|
|
3077
|
-
*
|
|
3078
|
-
*
|
|
3079
|
-
*
|
|
3080
|
-
*
|
|
3081
|
-
*
|
|
3082
|
-
*
|
|
3083
|
-
*
|
|
3084
|
-
*
|
|
3085
|
-
*
|
|
3086
|
-
*
|
|
3087
|
-
*
|
|
3088
|
-
*
|
|
3089
|
-
*
|
|
3090
|
-
*
|
|
3091
|
-
*
|
|
3092
|
-
*
|
|
3093
|
-
*
|
|
3094
|
-
*
|
|
3095
|
-
*
|
|
3096
|
-
*
|
|
3097
|
-
*
|
|
3098
|
-
*
|
|
3099
|
-
*
|
|
3100
|
-
*
|
|
3101
|
-
* @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
|
|
3143
|
+
/** Create a particle system with the given settings
|
|
3144
|
+
* @param {Vector2} position - World space position of the emitter
|
|
3145
|
+
* @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
3146
|
+
* @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
|
|
3147
|
+
* @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
|
|
3148
|
+
* @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
|
|
3149
|
+
* @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied
|
|
3150
|
+
* @param {Number} [tileSize=tileSizeDefault] - Tile size for particles
|
|
3151
|
+
* @param {Color} [colorStartA=new Color(1,1,1)] - Color at start of life 1, randomized between start colors
|
|
3152
|
+
* @param {Color} [colorStartB=new Color(1,1,1)] - Color at start of life 2, randomized between start colors
|
|
3153
|
+
* @param {Color} [colorEndA=new Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
|
|
3154
|
+
* @param {Color} [colorEndB=new Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
|
|
3155
|
+
* @param {Number} [particleTime=.5] - How long particles live
|
|
3156
|
+
* @param {Number} [sizeStart=.1] - How big are particles at start
|
|
3157
|
+
* @param {Number} [sizeEnd=1] - How big are particles at end
|
|
3158
|
+
* @param {Number} [speed=.1] - How fast are particles when spawned
|
|
3159
|
+
* @param {Number} [angleSpeed=.05] - How fast are particles rotating
|
|
3160
|
+
* @param {Number} [damping=1] - How much to dampen particle speed
|
|
3161
|
+
* @param {Number} [angleDamping=1] - How much to dampen particle angular speed
|
|
3162
|
+
* @param {Number} [gravityScale=0] - How much does gravity effect particles
|
|
3163
|
+
* @param {Number} [particleConeAngle=PI] - Cone for start particle angle
|
|
3164
|
+
* @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life
|
|
3165
|
+
* @param {Number} [randomness=.2] - Apply extra randomness percent
|
|
3166
|
+
* @param {Boolean} [collideTiles=0] - Do particles collide against tiles
|
|
3167
|
+
* @param {Boolean} [additive=0] - Should particles use addtive blend
|
|
3168
|
+
* @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
|
|
3169
|
+
* @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
|
|
3102
3170
|
*/
|
|
3103
3171
|
constructor
|
|
3104
3172
|
(
|
|
@@ -3108,7 +3176,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3108
3176
|
emitRate = 100,
|
|
3109
3177
|
emitConeAngle = PI,
|
|
3110
3178
|
tileIndex = -1,
|
|
3111
|
-
tileSize =
|
|
3179
|
+
tileSize = tileSizeDefault,
|
|
3112
3180
|
colorStartA = new Color,
|
|
3113
3181
|
colorStartB = new Color,
|
|
3114
3182
|
colorEndA = new Color(1,1,1,0),
|
|
@@ -3130,37 +3198,61 @@ class ParticleEmitter extends EngineObject
|
|
|
3130
3198
|
renderOrder = additive ? 1e9 : 0
|
|
3131
3199
|
)
|
|
3132
3200
|
{
|
|
3133
|
-
super(pos, new Vector2, tileIndex, tileSize);
|
|
3201
|
+
super(pos, new Vector2, tileIndex, tileSize, 0, undefined, renderOrder);
|
|
3134
3202
|
|
|
3135
3203
|
// emitter settings
|
|
3204
|
+
/** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
3136
3205
|
this.emitSize = emitSize
|
|
3206
|
+
/** @property {Number} - How long to stay alive (0 is forever) */
|
|
3137
3207
|
this.emitTime = emitTime;
|
|
3208
|
+
/** @property {Number} - How many particles per second to spawn, does not emit if 0 */
|
|
3138
3209
|
this.emitRate = emitRate;
|
|
3210
|
+
/** @property {Number} - Local angle to apply velocity to particles from emitter */
|
|
3139
3211
|
this.emitConeAngle = emitConeAngle;
|
|
3140
3212
|
|
|
3141
3213
|
// color settings
|
|
3214
|
+
/** @property {Color} - Color at start of life 1, randomized between start colors */
|
|
3142
3215
|
this.colorStartA = colorStartA;
|
|
3216
|
+
/** @property {Color} - Color at start of life 2, randomized between start colors */
|
|
3143
3217
|
this.colorStartB = colorStartB;
|
|
3218
|
+
/** @property {Color} - Color at end of life 1, randomized between end colors */
|
|
3144
3219
|
this.colorEndA = colorEndA;
|
|
3220
|
+
/** @property {Color} - Color at end of life 2, randomized between end colors */
|
|
3145
3221
|
this.colorEndB = colorEndB;
|
|
3222
|
+
/** @property {Boolean} - Should color be randomized linearly or across each component */
|
|
3146
3223
|
this.randomColorLinear = randomColorLinear;
|
|
3147
3224
|
|
|
3148
3225
|
// particle settings
|
|
3226
|
+
/** @property {Number} - How long particles live */
|
|
3149
3227
|
this.particleTime = particleTime;
|
|
3228
|
+
/** @property {Number} - How big are particles at start */
|
|
3150
3229
|
this.sizeStart = sizeStart;
|
|
3230
|
+
/** @property {Number} - How big are particles at end */
|
|
3151
3231
|
this.sizeEnd = sizeEnd;
|
|
3232
|
+
/** @property {Number} - How fast are particles when spawned */
|
|
3152
3233
|
this.speed = speed;
|
|
3234
|
+
/** @property {Number} - How fast are particles rotating */
|
|
3153
3235
|
this.angleSpeed = angleSpeed;
|
|
3236
|
+
/** @property {Number} - How much to dampen particle speed */
|
|
3154
3237
|
this.damping = damping;
|
|
3238
|
+
/** @property {Number} - How much to dampen particle angular speed */
|
|
3155
3239
|
this.angleDamping = angleDamping;
|
|
3240
|
+
/** @property {Number} - How much does gravity effect particles */
|
|
3156
3241
|
this.gravityScale = gravityScale;
|
|
3242
|
+
/** @property {Number} - Cone for start particle angle */
|
|
3157
3243
|
this.particleConeAngle = particleConeAngle;
|
|
3244
|
+
/** @property {Number} - How quick to fade in particles at start/end in percent of life */
|
|
3158
3245
|
this.fadeRate = fadeRate;
|
|
3246
|
+
/** @property {Number} - Apply extra randomness percent */
|
|
3159
3247
|
this.randomness = randomness;
|
|
3248
|
+
/** @property {Number} - Do particles collide against tiles */
|
|
3160
3249
|
this.collideTiles = collideTiles;
|
|
3250
|
+
/** @property {Number} - Should particles use addtive blend */
|
|
3161
3251
|
this.additive = additive;
|
|
3162
|
-
|
|
3163
|
-
this.trailScale =
|
|
3252
|
+
/** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */
|
|
3253
|
+
this.trailScale = 0;
|
|
3254
|
+
|
|
3255
|
+
// internal variables
|
|
3164
3256
|
this.emitTimeBuffer = 0;
|
|
3165
3257
|
}
|
|
3166
3258
|
|
|
@@ -3174,9 +3266,9 @@ class ParticleEmitter extends EngineObject
|
|
|
3174
3266
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
3175
3267
|
{
|
|
3176
3268
|
// emit particles
|
|
3177
|
-
if (this.emitRate)
|
|
3269
|
+
if (this.emitRate * particleEmitRateScale)
|
|
3178
3270
|
{
|
|
3179
|
-
const rate = 1/this.emitRate;
|
|
3271
|
+
const rate = 1/this.emitRate/particleEmitRateScale;
|
|
3180
3272
|
for (this.emitTimeBuffer += timeDelta; this.emitTimeBuffer > 0; this.emitTimeBuffer -= rate)
|
|
3181
3273
|
this.emitParticle();
|
|
3182
3274
|
}
|
|
@@ -3203,34 +3295,34 @@ class ParticleEmitter extends EngineObject
|
|
|
3203
3295
|
const randomizeScale = (v)=> v + v*rand(randomness, -randomness);
|
|
3204
3296
|
|
|
3205
3297
|
// randomize particle settings
|
|
3206
|
-
const particleTime
|
|
3207
|
-
const sizeStart
|
|
3208
|
-
const sizeEnd
|
|
3209
|
-
const speed
|
|
3210
|
-
const angleSpeed
|
|
3211
|
-
const coneAngle
|
|
3212
|
-
const colorStart
|
|
3213
|
-
const colorEnd
|
|
3298
|
+
const particleTime = randomizeScale(this.particleTime);
|
|
3299
|
+
const sizeStart = randomizeScale(this.sizeStart);
|
|
3300
|
+
const sizeEnd = randomizeScale(this.sizeEnd);
|
|
3301
|
+
const speed = randomizeScale(this.speed);
|
|
3302
|
+
const angleSpeed = randomizeScale(this.angleSpeed) * randSign();
|
|
3303
|
+
const coneAngle = rand(this.emitConeAngle, -this.emitConeAngle);
|
|
3304
|
+
const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
|
|
3305
|
+
const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
|
|
3214
3306
|
|
|
3215
3307
|
// build particle settings
|
|
3216
|
-
particle.colorStart
|
|
3217
|
-
particle.colorEndDelta
|
|
3218
|
-
particle.velocity
|
|
3219
|
-
particle.angleVelocity
|
|
3220
|
-
particle.lifeTime
|
|
3221
|
-
particle.sizeStart
|
|
3222
|
-
particle.sizeEndDelta
|
|
3223
|
-
particle.fadeRate
|
|
3224
|
-
particle.damping
|
|
3225
|
-
particle.angleDamping
|
|
3226
|
-
particle.elasticity
|
|
3227
|
-
particle.friction
|
|
3228
|
-
particle.gravityScale
|
|
3229
|
-
particle.collideTiles
|
|
3230
|
-
particle.additive
|
|
3231
|
-
particle.renderOrder
|
|
3232
|
-
particle.trailScale
|
|
3233
|
-
particle.mirror
|
|
3308
|
+
particle.colorStart = colorStart;
|
|
3309
|
+
particle.colorEndDelta = colorEnd.subtract(colorStart);
|
|
3310
|
+
particle.velocity = (new Vector2).setAngle(this.angle + coneAngle, speed);
|
|
3311
|
+
particle.angleVelocity = angleSpeed;
|
|
3312
|
+
particle.lifeTime = particleTime;
|
|
3313
|
+
particle.sizeStart = sizeStart;
|
|
3314
|
+
particle.sizeEndDelta = sizeEnd - sizeStart;
|
|
3315
|
+
particle.fadeRate = this.fadeRate;
|
|
3316
|
+
particle.damping = this.damping;
|
|
3317
|
+
particle.angleDamping = this.angleDamping;
|
|
3318
|
+
particle.elasticity = this.elasticity;
|
|
3319
|
+
particle.friction = this.friction;
|
|
3320
|
+
particle.gravityScale = this.gravityScale;
|
|
3321
|
+
particle.collideTiles = this.collideTiles;
|
|
3322
|
+
particle.additive = this.additive;
|
|
3323
|
+
particle.renderOrder = this.renderOrder;
|
|
3324
|
+
particle.trailScale = this.trailScale;
|
|
3325
|
+
particle.mirror = rand()<.5;
|
|
3234
3326
|
|
|
3235
3327
|
// setup callbacks for particles
|
|
3236
3328
|
particle.destroyCallback = this.particleDestroyCallback;
|
|
@@ -3247,15 +3339,16 @@ class ParticleEmitter extends EngineObject
|
|
|
3247
3339
|
///////////////////////////////////////////////////////////////////////////////
|
|
3248
3340
|
/**
|
|
3249
3341
|
* Particle Object - Created automatically by Particle Emitters
|
|
3342
|
+
* @extends EngineObject
|
|
3250
3343
|
*/
|
|
3251
3344
|
class Particle extends EngineObject
|
|
3252
3345
|
{
|
|
3253
3346
|
/**
|
|
3254
3347
|
* Create a particle with the given settings
|
|
3255
3348
|
* @param {Vector2} position - World space position of the particle
|
|
3256
|
-
* @param {Number} [tileIndex=-1]
|
|
3257
|
-
* @param {Vector2} [tileSize=
|
|
3258
|
-
* @param {Number} [angle=0]
|
|
3349
|
+
* @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1
|
|
3350
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
3351
|
+
* @param {Number} [angle=0] - Angle to rotate the particle
|
|
3259
3352
|
*/
|
|
3260
3353
|
constructor(pos, tileIndex, tileSize, angle) { super(pos, new Vector2, tileIndex, tileSize, angle); }
|
|
3261
3354
|
|
|
@@ -3302,11 +3395,11 @@ class Particle extends EngineObject
|
|
|
3302
3395
|
}
|
|
3303
3396
|
}
|
|
3304
3397
|
/**
|
|
3305
|
-
*
|
|
3306
|
-
*
|
|
3307
|
-
*
|
|
3308
|
-
*
|
|
3309
|
-
*
|
|
3398
|
+
* LittleJS Medal System
|
|
3399
|
+
* <br> - Tracks and displays medals
|
|
3400
|
+
* <br> - Saves medals to local storage
|
|
3401
|
+
* <br> - Newgrounds and OS13k integration
|
|
3402
|
+
* @namespace Medals
|
|
3310
3403
|
*/
|
|
3311
3404
|
|
|
3312
3405
|
'use strict';
|
|
@@ -3325,32 +3418,42 @@ let medalsPreventUnlock;
|
|
|
3325
3418
|
let newgrounds;
|
|
3326
3419
|
|
|
3327
3420
|
// Engine internal variables not exposed to documentation
|
|
3328
|
-
let medalsDisplayQueue = [], medalsSaveName,
|
|
3421
|
+
let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
|
|
3329
3422
|
|
|
3330
3423
|
///////////////////////////////////////////////////////////////////////////////
|
|
3331
3424
|
|
|
3332
3425
|
/** Initialize medals with a save name used for storage
|
|
3333
|
-
* <br> - Checks if medals are unlocked
|
|
3334
3426
|
* <br> - Call this after creating all medals
|
|
3427
|
+
* <br> - Checks if medals are unlocked
|
|
3335
3428
|
* @param {String} saveName
|
|
3336
3429
|
* @memberof Medals */
|
|
3337
3430
|
function medalsInit(saveName)
|
|
3338
3431
|
{
|
|
3339
3432
|
// check if medals are unlocked
|
|
3340
3433
|
medalsSaveName = saveName;
|
|
3341
|
-
debugMedals || medals.forEach(medal=> medal.unlocked = localStorage[medal.storageKey()]);
|
|
3434
|
+
debugMedals || medals.forEach(medal=> medal.unlocked = 1 || localStorage[medal.storageKey()]);
|
|
3342
3435
|
}
|
|
3343
3436
|
|
|
3344
|
-
/**
|
|
3437
|
+
/**
|
|
3438
|
+
* Medal Object - Tracks an unlockable medal
|
|
3439
|
+
* @example
|
|
3440
|
+
* // create a medal
|
|
3441
|
+
* const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
|
|
3442
|
+
*
|
|
3443
|
+
* // initialize medals
|
|
3444
|
+
* medalsInit('Example Game');
|
|
3445
|
+
*
|
|
3446
|
+
* // unlock the medal
|
|
3447
|
+
* medal_example.unlock();
|
|
3448
|
+
*/
|
|
3345
3449
|
class Medal
|
|
3346
3450
|
{
|
|
3347
|
-
/**
|
|
3348
|
-
*
|
|
3349
|
-
*
|
|
3350
|
-
*
|
|
3351
|
-
*
|
|
3352
|
-
*
|
|
3353
|
-
* @param {String} [src] - Image location for the medal
|
|
3451
|
+
/** Create an medal object and adds it to the list of medals
|
|
3452
|
+
* @param {Number} id - The unique identifier of the medal
|
|
3453
|
+
* @param {String} name - Name of the medal
|
|
3454
|
+
* @param {String} [description] - Description of the medal
|
|
3455
|
+
* @param {String} [icon='🏆'] - Icon for the medal
|
|
3456
|
+
* @param {String} [src] - Image location for the medal
|
|
3354
3457
|
*/
|
|
3355
3458
|
constructor(id, name, description='', icon='🏆', src)
|
|
3356
3459
|
{
|
|
@@ -3361,13 +3464,9 @@ class Medal
|
|
|
3361
3464
|
this.name = name;
|
|
3362
3465
|
this.description = description;
|
|
3363
3466
|
this.icon = icon;
|
|
3364
|
-
|
|
3467
|
+
this.image = new Image();
|
|
3365
3468
|
if (src)
|
|
3366
|
-
{
|
|
3367
|
-
// load image
|
|
3368
|
-
this.image = new Image();
|
|
3369
3469
|
this.image.src = src;
|
|
3370
|
-
}
|
|
3371
3470
|
}
|
|
3372
3471
|
|
|
3373
3472
|
/** Unlocks a medal if not already unlocked */
|
|
@@ -3377,7 +3476,7 @@ class Medal
|
|
|
3377
3476
|
return;
|
|
3378
3477
|
|
|
3379
3478
|
// save the medal
|
|
3380
|
-
ASSERT(medalsSaveName); //
|
|
3479
|
+
ASSERT(medalsSaveName); // save name must be set
|
|
3381
3480
|
localStorage[this.storageKey()] = this.unlocked = 1;
|
|
3382
3481
|
medalsDisplayQueue.push(this);
|
|
3383
3482
|
|
|
@@ -3392,27 +3491,28 @@ class Medal
|
|
|
3392
3491
|
render(hidePercent=0)
|
|
3393
3492
|
{
|
|
3394
3493
|
const context = overlayContext;
|
|
3395
|
-
const
|
|
3494
|
+
const width = min(medalDisplayWidth, mainCanvas.width);
|
|
3495
|
+
const x = overlayCanvas.width - width;
|
|
3396
3496
|
const y = -medalDisplayHeight*hidePercent;
|
|
3397
3497
|
|
|
3398
3498
|
// draw containing rect and clip to that region
|
|
3399
3499
|
context.save();
|
|
3400
3500
|
context.beginPath();
|
|
3401
3501
|
context.fillStyle = '#ddd'
|
|
3402
|
-
context.fill(context.rect(x, y,
|
|
3403
|
-
context.strokeStyle =
|
|
3404
|
-
context.lineWidth =
|
|
3502
|
+
context.fill(context.rect(x, y, width, medalDisplayHeight));
|
|
3503
|
+
context.strokeStyle = '#000';
|
|
3504
|
+
context.lineWidth = 3;
|
|
3405
3505
|
context.stroke();
|
|
3406
3506
|
context.clip();
|
|
3407
3507
|
|
|
3508
|
+
// draw the icon and text
|
|
3408
3509
|
this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
|
|
3409
|
-
|
|
3410
|
-
// draw the text
|
|
3411
3510
|
context.textAlign = 'left';
|
|
3412
|
-
context.font = '
|
|
3413
|
-
context.fillText(this.name, x+medalDisplayIconSize+
|
|
3414
|
-
context.font = '
|
|
3415
|
-
context.
|
|
3511
|
+
context.font = '38px '+ fontDefault;
|
|
3512
|
+
context.fillText(this.name, x+medalDisplayIconSize+30, y+28);
|
|
3513
|
+
context.font = '24px '+ fontDefault;
|
|
3514
|
+
context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
|
|
3515
|
+
context.restore();
|
|
3416
3516
|
}
|
|
3417
3517
|
|
|
3418
3518
|
/** Render the icon for a medal
|
|
@@ -3424,21 +3524,18 @@ class Medal
|
|
|
3424
3524
|
{
|
|
3425
3525
|
// draw the image or icon
|
|
3426
3526
|
const context = overlayContext;
|
|
3527
|
+
context.fillStyle = '#000';
|
|
3427
3528
|
context.textAlign = 'center';
|
|
3428
3529
|
context.textBaseline = 'middle';
|
|
3429
|
-
context.font = size*.
|
|
3430
|
-
|
|
3431
|
-
if (this.image)
|
|
3530
|
+
context.font = size*.7 + 'px '+ fontDefault;
|
|
3531
|
+
if (this.image.src)
|
|
3432
3532
|
context.drawImage(this.image, x-size/2, y-size/2, size, size);
|
|
3433
3533
|
else
|
|
3434
3534
|
context.fillText(this.icon, x, y); // show icon if there is no image
|
|
3435
3535
|
}
|
|
3436
3536
|
|
|
3437
3537
|
// Get local storage key used by the medal
|
|
3438
|
-
storageKey()
|
|
3439
|
-
{
|
|
3440
|
-
return medalsSaveName + '_medal_' + this.id;
|
|
3441
|
-
}
|
|
3538
|
+
storageKey() { return medalsSaveName + '_' + this.id; }
|
|
3442
3539
|
}
|
|
3443
3540
|
|
|
3444
3541
|
// engine automatically renders medals
|
|
@@ -3449,11 +3546,11 @@ function medalsRender()
|
|
|
3449
3546
|
|
|
3450
3547
|
// update first medal in queue
|
|
3451
3548
|
const medal = medalsDisplayQueue[0];
|
|
3452
|
-
const time = timeReal -
|
|
3453
|
-
if (!
|
|
3454
|
-
|
|
3549
|
+
const time = timeReal - medalsDisplayTimeLast;
|
|
3550
|
+
if (!medalsDisplayTimeLast)
|
|
3551
|
+
medalsDisplayTimeLast = timeReal;
|
|
3455
3552
|
else if (time > medalDisplayTime)
|
|
3456
|
-
medalsDisplayQueue.shift(
|
|
3553
|
+
medalsDisplayQueue.shift(medalsDisplayTimeLast = 0);
|
|
3457
3554
|
else
|
|
3458
3555
|
{
|
|
3459
3556
|
// slide on/off medals
|
|
@@ -3467,14 +3564,19 @@ function medalsRender()
|
|
|
3467
3564
|
|
|
3468
3565
|
///////////////////////////////////////////////////////////////////////////////
|
|
3469
3566
|
|
|
3470
|
-
/**
|
|
3567
|
+
/**
|
|
3568
|
+
* Newgrounds API wrapper object
|
|
3569
|
+
* @example
|
|
3570
|
+
* // create a newgrounds object, replace the app id and cipher with your own
|
|
3571
|
+
* const app_id = '53123:1ZuSTQ9l';
|
|
3572
|
+
* const cipher = 'enF0vGH@Mj/FRASKL23Q==';
|
|
3573
|
+
* newgrounds = new Newgrounds(app_id, cipher);
|
|
3574
|
+
*/
|
|
3471
3575
|
class Newgrounds
|
|
3472
3576
|
{
|
|
3473
|
-
/**
|
|
3474
|
-
*
|
|
3475
|
-
*
|
|
3476
|
-
* @param {String} [cipher] - The encryption Key (AES-128/Base64)
|
|
3477
|
-
*/
|
|
3577
|
+
/** Create a newgrounds object
|
|
3578
|
+
* @param {Number} app_id - The newgrounds App ID
|
|
3579
|
+
* @param {String} [cipher] - The encryption Key (AES-128/Base64) */
|
|
3478
3580
|
constructor(app_id, cipher)
|
|
3479
3581
|
{
|
|
3480
3582
|
ASSERT(!newgrounds && app_id);
|
|
@@ -3502,7 +3604,6 @@ class Newgrounds
|
|
|
3502
3604
|
if (medal)
|
|
3503
3605
|
{
|
|
3504
3606
|
// copy newgrounds medal data
|
|
3505
|
-
medal.image = new Image();
|
|
3506
3607
|
medal.image.src = newgroundsMedal['icon'];
|
|
3507
3608
|
medal.name = newgroundsMedal['name'];
|
|
3508
3609
|
medal.description = newgroundsMedal['description'];
|
|
@@ -3526,7 +3627,7 @@ class Newgrounds
|
|
|
3526
3627
|
unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, 1); }
|
|
3527
3628
|
|
|
3528
3629
|
/** Send message to post score
|
|
3529
|
-
* @param {Number} id
|
|
3630
|
+
* @param {Number} id - The scoreboard id
|
|
3530
3631
|
* @param {Number} value - The score value */
|
|
3531
3632
|
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
|
|
3532
3633
|
|
|
@@ -3534,21 +3635,21 @@ class Newgrounds
|
|
|
3534
3635
|
logView() { return this.call('App.logView', {'host':this.host}, 1); }
|
|
3535
3636
|
|
|
3536
3637
|
/** Get scores from a scoreboard
|
|
3537
|
-
* @param {Number} id
|
|
3538
|
-
* @param {String} [user=0]
|
|
3638
|
+
* @param {Number} id - The scoreboard id
|
|
3639
|
+
* @param {String} [user=0] - A user's id or name
|
|
3539
3640
|
* @param {Number} [social=0] - If true, only social scores will be loaded
|
|
3540
|
-
* @param {Number} [skip=0]
|
|
3641
|
+
* @param {Number} [skip=0] - Number of scores to skip before start
|
|
3541
3642
|
* @param {Number} [limit=10] - Number of scores to include in the list
|
|
3542
|
-
* @return {Object}
|
|
3643
|
+
* @return {Object} - The response JSON object
|
|
3543
3644
|
*/
|
|
3544
3645
|
getScores(id, user=0, social=0, skip=0, limit=10)
|
|
3545
3646
|
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
3546
3647
|
|
|
3547
3648
|
/** Send a message to call a component of the Newgrounds API
|
|
3548
|
-
* @param {String} component
|
|
3649
|
+
* @param {String} component - Name of the component
|
|
3549
3650
|
* @param {Object} [parameters=0] - Parameters to use for call
|
|
3550
|
-
* @param {Boolean} [async=0]
|
|
3551
|
-
* @return {Object}
|
|
3651
|
+
* @param {Boolean} [async=0] - If true, don't wait for response before continuing (avoid stall)
|
|
3652
|
+
* @return {Object} - The response JSON object
|
|
3552
3653
|
*/
|
|
3553
3654
|
call(component, parameters=0, async=0)
|
|
3554
3655
|
{
|
|
@@ -3592,14 +3693,14 @@ class Newgrounds
|
|
|
3592
3693
|
|
|
3593
3694
|
const CryptoJS=()=>eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",...']charCodeAtUinyxpf',"for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
|
|
3594
3695
|
/**
|
|
3595
|
-
*
|
|
3596
|
-
*
|
|
3597
|
-
*
|
|
3598
|
-
*
|
|
3599
|
-
*
|
|
3600
|
-
*
|
|
3601
|
-
*
|
|
3602
|
-
*
|
|
3696
|
+
* LittleJS WebGL Interface
|
|
3697
|
+
* <br> - All webgl used by the engine is wrapped up here
|
|
3698
|
+
* <br> - For normal stuff you won't need to see or call anything in this file
|
|
3699
|
+
* <br> - For advanced stuff there are helper functions to create shaders, textures, etc
|
|
3700
|
+
* <br> - Can be disabled with glEnable to revert to 2D canvas rendering
|
|
3701
|
+
* <br> - Batches sprite rendering on GPU for incredibly fast performance
|
|
3702
|
+
* <br> - Sprite transform math is done in the shader where possible
|
|
3703
|
+
* @namespace WebGL
|
|
3603
3704
|
*/
|
|
3604
3705
|
|
|
3605
3706
|
'use strict';
|
|
@@ -3620,7 +3721,7 @@ let glContext;
|
|
|
3620
3721
|
let glTileTexture;
|
|
3621
3722
|
|
|
3622
3723
|
// WebGL internal variables not exposed to documentation
|
|
3623
|
-
let glActiveTexture, glShader, glPositionData, glColorData, glBatchCount,
|
|
3724
|
+
let glActiveTexture, glShader, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
|
|
3624
3725
|
|
|
3625
3726
|
///////////////////////////////////////////////////////////////////////////////
|
|
3626
3727
|
|
|
@@ -3631,19 +3732,16 @@ function glInit()
|
|
|
3631
3732
|
|
|
3632
3733
|
// create the canvas and tile texture
|
|
3633
3734
|
glCanvas = document.createElement('canvas');
|
|
3634
|
-
glContext = glCanvas.getContext('webgl'
|
|
3735
|
+
glContext = glCanvas.getContext('webgl');
|
|
3736
|
+
glCanvas.style = styleCanvas;
|
|
3635
3737
|
glTileTexture = glCreateTexture(tileImage);
|
|
3636
3738
|
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
// firefox is much faster without copying the gl buffer so we just overlay it with some tradeoffs
|
|
3640
|
-
document.body.appendChild(glCanvas);
|
|
3641
|
-
glCanvas.style = mainCanvas.style.cssText;
|
|
3642
|
-
}
|
|
3739
|
+
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
3740
|
+
glOverlay && document.body.appendChild(glCanvas);
|
|
3643
3741
|
|
|
3644
3742
|
// setup vertex and fragment shaders
|
|
3645
3743
|
glShader = glCreateProgram(
|
|
3646
|
-
'precision
|
|
3744
|
+
'precision highp float;'+ // use highp for better accuracy, lowp for better perf
|
|
3647
3745
|
'uniform mat4 m;'+ // transform matrix
|
|
3648
3746
|
'attribute float a;'+ // angle
|
|
3649
3747
|
'attribute vec2 p,s,t;'+ // position, size, uv
|
|
@@ -3655,7 +3753,7 @@ function glInit()
|
|
|
3655
3753
|
'v=t;d=c;e=b;'+ // pass stuff to fragment shader
|
|
3656
3754
|
'}' // end of shader
|
|
3657
3755
|
,
|
|
3658
|
-
'precision
|
|
3756
|
+
'precision highp float;'+ // use highp for better accuracy, lowp for better perf
|
|
3659
3757
|
'varying vec2 v;'+ // uv
|
|
3660
3758
|
'varying vec4 d,e;'+ // color, additiveColor
|
|
3661
3759
|
'uniform sampler2D j;'+ // texture
|
|
@@ -3671,6 +3769,7 @@ function glInit()
|
|
|
3671
3769
|
glColorData = new Uint32Array(glVertexData);
|
|
3672
3770
|
|
|
3673
3771
|
// setup the vertex data array
|
|
3772
|
+
let offset = glBatchCount = 0;
|
|
3674
3773
|
const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
|
|
3675
3774
|
{
|
|
3676
3775
|
const location = glContext.getAttribLocation(glShader, name);
|
|
@@ -3678,7 +3777,6 @@ function glInit()
|
|
|
3678
3777
|
glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
|
|
3679
3778
|
offset += size*typeSize;
|
|
3680
3779
|
}
|
|
3681
|
-
let offset = glDirty = glBatchCount = 0;
|
|
3682
3780
|
initVertexAttribArray('a', gl_FLOAT, 4, 1); // angle
|
|
3683
3781
|
initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
|
|
3684
3782
|
initVertexAttribArray('s', gl_FLOAT, 4, 2); // size
|
|
@@ -3694,14 +3792,8 @@ function glSetBlendMode(additive)
|
|
|
3694
3792
|
{
|
|
3695
3793
|
if (!glEnable) return;
|
|
3696
3794
|
|
|
3697
|
-
if (additive != glAdditive)
|
|
3698
|
-
glFlush();
|
|
3699
|
-
|
|
3700
3795
|
// setup blending
|
|
3701
3796
|
glAdditive = additive;
|
|
3702
|
-
const destBlend = additive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
|
|
3703
|
-
glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
|
|
3704
|
-
glContext.enable(gl_BLEND);
|
|
3705
3797
|
}
|
|
3706
3798
|
|
|
3707
3799
|
/** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
|
|
@@ -3711,7 +3803,8 @@ function glSetBlendMode(additive)
|
|
|
3711
3803
|
function glSetTexture(texture=glTileTexture)
|
|
3712
3804
|
{
|
|
3713
3805
|
if (!glEnable) return;
|
|
3714
|
-
|
|
3806
|
+
|
|
3807
|
+
// must flush cache with the old texture to set a new one
|
|
3715
3808
|
if (texture != glActiveTexture)
|
|
3716
3809
|
glFlush();
|
|
3717
3810
|
|
|
@@ -3776,36 +3869,35 @@ function glCreateBuffer(bufferType, size, usage)
|
|
|
3776
3869
|
return buffer;
|
|
3777
3870
|
}
|
|
3778
3871
|
|
|
3779
|
-
/** Create WebGL texture from an image
|
|
3872
|
+
/** Create WebGL texture from an image and set the texture settings
|
|
3780
3873
|
* @param {Image} image
|
|
3781
3874
|
* @return {WebGLTexture}
|
|
3782
3875
|
* @memberof WebGL */
|
|
3783
3876
|
function glCreateTexture(image)
|
|
3784
3877
|
{
|
|
3785
|
-
if (!glEnable) return;
|
|
3878
|
+
if (!glEnable || !image || !image.width) return;
|
|
3786
3879
|
|
|
3787
3880
|
// build the texture
|
|
3788
3881
|
const texture = glContext.createTexture();
|
|
3789
3882
|
glContext.bindTexture(gl_TEXTURE_2D, texture);
|
|
3790
3883
|
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
3791
|
-
|
|
3884
|
+
|
|
3792
3885
|
// use point filtering for pixelated rendering
|
|
3793
|
-
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER,
|
|
3794
|
-
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER,
|
|
3886
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
|
|
3887
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
|
|
3795
3888
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
|
|
3796
3889
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
|
|
3797
3890
|
return texture;
|
|
3798
3891
|
}
|
|
3799
3892
|
|
|
3800
3893
|
// called automatically by engine before render
|
|
3801
|
-
function glPreRender(width, height)
|
|
3894
|
+
function glPreRender(width, height, cameraX, cameraY, cameraScale)
|
|
3802
3895
|
{
|
|
3803
3896
|
if (!glEnable) return;
|
|
3804
3897
|
|
|
3805
3898
|
// clear and set to same size as main canvas
|
|
3806
|
-
glCanvas.width = width;
|
|
3807
|
-
|
|
3808
|
-
glContext.viewport(0, 0, width, height);
|
|
3899
|
+
glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
|
|
3900
|
+
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3809
3901
|
|
|
3810
3902
|
// set up the shader
|
|
3811
3903
|
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
|
|
@@ -3820,7 +3912,7 @@ function glPreRender(width, height)
|
|
|
3820
3912
|
sx, 0, 0, 0,
|
|
3821
3913
|
0, sy, 0, 0,
|
|
3822
3914
|
1, 1, -1, 1,
|
|
3823
|
-
-1-sx*
|
|
3915
|
+
-1-sx*cameraX, -1-sy*cameraY, 0, 0
|
|
3824
3916
|
])
|
|
3825
3917
|
);
|
|
3826
3918
|
}
|
|
@@ -3831,11 +3923,16 @@ function glFlush()
|
|
|
3831
3923
|
{
|
|
3832
3924
|
if (!glEnable || !glBatchCount) return;
|
|
3833
3925
|
|
|
3926
|
+
const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
|
|
3927
|
+
glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
|
|
3928
|
+
glContext.enable(gl_BLEND);
|
|
3929
|
+
|
|
3834
3930
|
// draw all the sprites in the batch and reset the buffer
|
|
3835
3931
|
glContext.bufferSubData(gl_ARRAY_BUFFER, 0,
|
|
3836
3932
|
glPositionData.subarray(0, glBatchCount * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT));
|
|
3837
3933
|
glContext.drawArrays(gl_TRIANGLES, 0, glBatchCount * gl_VERTICES_PER_QUAD);
|
|
3838
3934
|
glBatchCount = 0;
|
|
3935
|
+
glBatchAdditive = glAdditive;
|
|
3839
3936
|
}
|
|
3840
3937
|
|
|
3841
3938
|
/** Draw any sprites still in the buffer, copy to main canvas and clear
|
|
@@ -3844,29 +3941,38 @@ function glFlush()
|
|
|
3844
3941
|
* @memberof WebGL */
|
|
3845
3942
|
function glCopyToContext(context, forceDraw)
|
|
3846
3943
|
{
|
|
3847
|
-
if (!glEnable || !
|
|
3944
|
+
if (!glEnable || !glBatchCount) return;
|
|
3848
3945
|
|
|
3849
3946
|
glFlush();
|
|
3947
|
+
|
|
3948
|
+
// do not draw in overlay mode because the canvas is visible
|
|
3850
3949
|
if (!glOverlay || forceDraw)
|
|
3851
|
-
|
|
3852
|
-
// do not draw/clear in overlay mode because the canvas is visible
|
|
3853
|
-
context.drawImage(glCanvas, 0, glAdditive = glDirty = 0);
|
|
3854
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3855
|
-
}
|
|
3950
|
+
context.drawImage(glCanvas, 0, 0);
|
|
3856
3951
|
}
|
|
3857
3952
|
|
|
3858
|
-
|
|
3859
|
-
|
|
3953
|
+
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
3954
|
+
* @param x
|
|
3955
|
+
* @param y
|
|
3956
|
+
* @param sizeX
|
|
3957
|
+
* @param sizeY
|
|
3958
|
+
* @param angle
|
|
3959
|
+
* @param uv0X
|
|
3960
|
+
* @param uv0Y
|
|
3961
|
+
* @param uv1X
|
|
3962
|
+
* @param uv1Y
|
|
3963
|
+
* @param [rgba=0xffffffff]
|
|
3964
|
+
* @param [rgbaAdditive=0]
|
|
3965
|
+
* @memberof WebGL */
|
|
3966
|
+
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffffff, rgbaAdditive=0)
|
|
3860
3967
|
{
|
|
3861
3968
|
if (!glEnable) return;
|
|
3862
|
-
|
|
3863
|
-
// flush if there is no room for more verts
|
|
3864
|
-
if (glBatchCount == gl_MAX_BATCH)
|
|
3969
|
+
|
|
3970
|
+
// flush if there is no room for more verts or if different blend mode
|
|
3971
|
+
if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
|
|
3865
3972
|
glFlush();
|
|
3866
3973
|
|
|
3867
3974
|
// setup 2 triangles to form a quad
|
|
3868
3975
|
let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
|
|
3869
|
-
glDirty = 1;
|
|
3870
3976
|
|
|
3871
3977
|
// vertex 0
|
|
3872
3978
|
glPositionData[offset++] = angle;
|
|
@@ -3943,3 +4049,287 @@ gl_VERTICES_PER_QUAD = 6,
|
|
|
3943
4049
|
gl_INDICIES_PER_VERT = 9,
|
|
3944
4050
|
gl_MAX_BATCH = 1<<16,
|
|
3945
4051
|
gl_VERTEX_BYTE_STRIDE = 4 + (4 * 2) * 3 + (4) * 2; // float + vec2 * 3 + (char * 4) * 2
|
|
4052
|
+
/*
|
|
4053
|
+
LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
4054
|
+
MIT License - Copyright 2021 Frank Force
|
|
4055
|
+
|
|
4056
|
+
Engine Features
|
|
4057
|
+
- Object oriented system with base class engine object
|
|
4058
|
+
- Base class object handles update, physics, collision, rendering, etc
|
|
4059
|
+
- Engine helper classes and functions like Vector2, Color, and Timer
|
|
4060
|
+
- Super fast rendering system for tile sheets
|
|
4061
|
+
- Sound effects audio with zzfx and music with zzfxm
|
|
4062
|
+
- Input processing system with gamepad and touchscreen support
|
|
4063
|
+
- Tile layer rendering and collision system
|
|
4064
|
+
- Particle effect system
|
|
4065
|
+
- Medal system tracks and displays achievements
|
|
4066
|
+
- Debug tools and debug rendering system
|
|
4067
|
+
- Call engineInit() to start it up!
|
|
4068
|
+
*/
|
|
4069
|
+
|
|
4070
|
+
'use strict';
|
|
4071
|
+
|
|
4072
|
+
/** Name of engine */
|
|
4073
|
+
const engineName = 'LittleJS';
|
|
4074
|
+
|
|
4075
|
+
/** Version of engine */
|
|
4076
|
+
const engineVersion = '1.1.3';
|
|
4077
|
+
|
|
4078
|
+
/** Frames per second to update objects
|
|
4079
|
+
* @default */
|
|
4080
|
+
const frameRate = 60;
|
|
4081
|
+
|
|
4082
|
+
/** How many seconds each frame lasts, engine uses a fixed time step
|
|
4083
|
+
* @default 1/60 */
|
|
4084
|
+
const timeDelta = 1/frameRate;
|
|
4085
|
+
|
|
4086
|
+
/** Array containing all engine objects */
|
|
4087
|
+
let engineObjects = [];
|
|
4088
|
+
|
|
4089
|
+
/** Array containing only objects that are set to collide with other objects this frame (for optimization) */
|
|
4090
|
+
let engineObjectsCollide = [];
|
|
4091
|
+
|
|
4092
|
+
/** Current update frame, used to calculate time */
|
|
4093
|
+
let frame = 0;
|
|
4094
|
+
|
|
4095
|
+
/** Current engine time since start in seconds, derived from frame */
|
|
4096
|
+
let time = 0;
|
|
4097
|
+
|
|
4098
|
+
/** Actual clock time since start in seconds (not affected by pause or frame rate clamping) */
|
|
4099
|
+
let timeReal = 0;
|
|
4100
|
+
|
|
4101
|
+
/** Is the game paused? Causes time and objects to not be updated. */
|
|
4102
|
+
let paused = 0;
|
|
4103
|
+
|
|
4104
|
+
// Engine internal variables not exposed to documentation
|
|
4105
|
+
let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
|
|
4106
|
+
|
|
4107
|
+
// Engine stat tracking, if showWatermark is true
|
|
4108
|
+
let averageFPS, drawCount;
|
|
4109
|
+
|
|
4110
|
+
// css text used for elements created by engine
|
|
4111
|
+
const styleBody = 'margin:0;overflow:hidden;background:#000' +
|
|
4112
|
+
';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none';
|
|
4113
|
+
const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
4114
|
+
|
|
4115
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
4116
|
+
|
|
4117
|
+
/** Start up LittleJS engine with your callback functions
|
|
4118
|
+
* @param {Function} gameInit - Called once after the engine starts up, setup the game
|
|
4119
|
+
* @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
|
|
4120
|
+
* @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
|
|
4121
|
+
* @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
|
|
4122
|
+
* @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
|
|
4123
|
+
* @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading
|
|
4124
|
+
*/
|
|
4125
|
+
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
|
|
4126
|
+
{
|
|
4127
|
+
// init engine when tiles load or fail to load
|
|
4128
|
+
tileImage.onerror = tileImage.onload = ()=>
|
|
4129
|
+
{
|
|
4130
|
+
// save tile image info
|
|
4131
|
+
tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
4132
|
+
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
4133
|
+
|
|
4134
|
+
// setup html
|
|
4135
|
+
document.body.style = styleBody;
|
|
4136
|
+
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
4137
|
+
mainContext = mainCanvas.getContext('2d');
|
|
4138
|
+
mainCanvas.style = styleCanvas;
|
|
4139
|
+
|
|
4140
|
+
// init stuff and start engine
|
|
4141
|
+
debugInit();
|
|
4142
|
+
glInit();
|
|
4143
|
+
|
|
4144
|
+
// create overlay canvas for hud to appear above gl canvas
|
|
4145
|
+
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
4146
|
+
overlayContext = overlayCanvas.getContext('2d');
|
|
4147
|
+
overlayCanvas.style = styleCanvas;
|
|
4148
|
+
|
|
4149
|
+
gameInit();
|
|
4150
|
+
touchGamepadCreate();
|
|
4151
|
+
engineUpdate();
|
|
4152
|
+
};
|
|
4153
|
+
|
|
4154
|
+
// main update loop
|
|
4155
|
+
const engineUpdate = (frameTimeMS=0)=>
|
|
4156
|
+
{
|
|
4157
|
+
requestAnimationFrame(engineUpdate);
|
|
4158
|
+
|
|
4159
|
+
// update time keeping
|
|
4160
|
+
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
4161
|
+
frameTimeLastMS = frameTimeMS;
|
|
4162
|
+
if (debug || showWatermark)
|
|
4163
|
+
averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
|
|
4164
|
+
if (debug)
|
|
4165
|
+
frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
|
|
4166
|
+
timeReal += frameTimeDeltaMS / 1e3;
|
|
4167
|
+
frameTimeBufferMS = min(frameTimeBufferMS + !paused * frameTimeDeltaMS, 50); // clamp incase of slow framerate
|
|
4168
|
+
|
|
4169
|
+
if (paused)
|
|
4170
|
+
{
|
|
4171
|
+
// do post update even when paused
|
|
4172
|
+
inputUpdate();
|
|
4173
|
+
debugUpdate();
|
|
4174
|
+
gameUpdatePost();
|
|
4175
|
+
inputUpdatePost();
|
|
4176
|
+
}
|
|
4177
|
+
else
|
|
4178
|
+
{
|
|
4179
|
+
// apply time delta smoothing, improves smoothness of framerate in some browsers
|
|
4180
|
+
let deltaSmooth = 0;
|
|
4181
|
+
if (frameTimeBufferMS < 0 && frameTimeBufferMS > -9)
|
|
4182
|
+
{
|
|
4183
|
+
// force an update each frame if time is close enough (not just a fast refresh rate)
|
|
4184
|
+
deltaSmooth = frameTimeBufferMS;
|
|
4185
|
+
frameTimeBufferMS = 0;
|
|
4186
|
+
}
|
|
4187
|
+
|
|
4188
|
+
// update multiple frames if necessary in case of slow framerate
|
|
4189
|
+
for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
|
|
4190
|
+
{
|
|
4191
|
+
// update game and objects
|
|
4192
|
+
inputUpdate();
|
|
4193
|
+
gameUpdate();
|
|
4194
|
+
engineObjectsUpdate();
|
|
4195
|
+
|
|
4196
|
+
// do post update
|
|
4197
|
+
debugUpdate();
|
|
4198
|
+
gameUpdatePost();
|
|
4199
|
+
inputUpdatePost();
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
// add the time smoothing back in
|
|
4203
|
+
frameTimeBufferMS += deltaSmooth;
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
if (canvasFixedSize.x)
|
|
4207
|
+
{
|
|
4208
|
+
// clear set fixed size
|
|
4209
|
+
mainCanvas.width = canvasFixedSize.x;
|
|
4210
|
+
mainCanvas.height = canvasFixedSize.y;
|
|
4211
|
+
|
|
4212
|
+
// fit to window by adding space on top or bottom if necessary
|
|
4213
|
+
const aspect = innerWidth / innerHeight;
|
|
4214
|
+
const fixedAspect = mainCanvas.width / mainCanvas.height;
|
|
4215
|
+
mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
|
|
4216
|
+
mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
4217
|
+
if (glCanvas)
|
|
4218
|
+
{
|
|
4219
|
+
glCanvas.style.width = mainCanvas.style.width;
|
|
4220
|
+
glCanvas.style.height = mainCanvas.style.height;
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
else
|
|
4224
|
+
{
|
|
4225
|
+
// clear and set size to same as window
|
|
4226
|
+
mainCanvas.width = min(innerWidth, canvasMaxSize.x);
|
|
4227
|
+
mainCanvas.height = min(innerHeight, canvasMaxSize.y);
|
|
4228
|
+
}
|
|
4229
|
+
|
|
4230
|
+
// render sort then render while removing destroyed objects
|
|
4231
|
+
enginePreRender();
|
|
4232
|
+
gameRender();
|
|
4233
|
+
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
4234
|
+
for (const o of engineObjects)
|
|
4235
|
+
o.destroyed || o.render();
|
|
4236
|
+
gameRenderPost();
|
|
4237
|
+
medalsRender();
|
|
4238
|
+
touchGamepadRender();
|
|
4239
|
+
debugRender();
|
|
4240
|
+
glCopyToContext(mainContext);
|
|
4241
|
+
|
|
4242
|
+
if (showWatermark)
|
|
4243
|
+
{
|
|
4244
|
+
// update fps
|
|
4245
|
+
overlayContext.textAlign = 'right';
|
|
4246
|
+
overlayContext.textBaseline = 'top';
|
|
4247
|
+
overlayContext.font = '1em monospace';
|
|
4248
|
+
overlayContext.fillStyle = '#000';
|
|
4249
|
+
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
4250
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
4251
|
+
+ ' ' + (glEnable ? 'GL' : '2D') ;
|
|
4252
|
+
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
4253
|
+
overlayContext.fillStyle = '#fff';
|
|
4254
|
+
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
4255
|
+
drawCount = 0;
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
|
|
4259
|
+
// set tile image source to load the image and start the engine
|
|
4260
|
+
tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
// called by engine to setup render system
|
|
4264
|
+
function enginePreRender()
|
|
4265
|
+
{
|
|
4266
|
+
// save canvas size and clear canvases
|
|
4267
|
+
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
4268
|
+
|
|
4269
|
+
// disable smoothing for pixel art
|
|
4270
|
+
mainContext.imageSmoothingEnabled = !cavasPixelated;
|
|
4271
|
+
|
|
4272
|
+
// setup gl rendering if enabled
|
|
4273
|
+
glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
|
|
4274
|
+
}
|
|
4275
|
+
|
|
4276
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
4277
|
+
|
|
4278
|
+
/** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
|
|
4279
|
+
function engineObjectsUpdate()
|
|
4280
|
+
{
|
|
4281
|
+
// get list of solid objects for physics optimzation
|
|
4282
|
+
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
4283
|
+
|
|
4284
|
+
// recursive object update
|
|
4285
|
+
const updateObject = (o)=>
|
|
4286
|
+
{
|
|
4287
|
+
if (!o.destroyed)
|
|
4288
|
+
{
|
|
4289
|
+
o.update();
|
|
4290
|
+
for (const child of o.children)
|
|
4291
|
+
updateObject(child);
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
for (const o of engineObjects)
|
|
4295
|
+
o.parent || updateObject(o);
|
|
4296
|
+
|
|
4297
|
+
// remove destroyed objects
|
|
4298
|
+
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
4299
|
+
|
|
4300
|
+
// increment frame and update time
|
|
4301
|
+
time = ++frame / frameRate;
|
|
4302
|
+
}
|
|
4303
|
+
|
|
4304
|
+
/** Destroy and remove all objects */
|
|
4305
|
+
function engineObjectsDestroy()
|
|
4306
|
+
{
|
|
4307
|
+
for (const o of engineObjects)
|
|
4308
|
+
o.parent || o.destroy();
|
|
4309
|
+
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
4310
|
+
}
|
|
4311
|
+
|
|
4312
|
+
/** Triggers a callback for each object within a given area
|
|
4313
|
+
* @param {Vector2} [pos] - Center of test area
|
|
4314
|
+
* @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
|
|
4315
|
+
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
4316
|
+
* @param {Array} [objects=engineObjects] - List of objects to check */
|
|
4317
|
+
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
4318
|
+
{
|
|
4319
|
+
if (!pos) // all objects
|
|
4320
|
+
{
|
|
4321
|
+
for (const o of objects)
|
|
4322
|
+
callbackFunction(o);
|
|
4323
|
+
}
|
|
4324
|
+
else if (size.x != undefined) // bounding box test
|
|
4325
|
+
{
|
|
4326
|
+
for (const o of objects)
|
|
4327
|
+
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
4328
|
+
}
|
|
4329
|
+
else // circle test
|
|
4330
|
+
{
|
|
4331
|
+
const sizeSquared = size*size;
|
|
4332
|
+
for (const o of objects)
|
|
4333
|
+
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
|
4334
|
+
}
|
|
4335
|
+
}
|