littlejsengine 1.0.14 → 1.1.8
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/build.bat +5 -1
- package/docs/Audio.html +2268 -0
- package/docs/Color.html +2989 -0
- package/docs/Debug.html +2933 -0
- package/docs/Draw.html +4092 -0
- package/docs/EngineObject.html +4458 -0
- package/docs/Input.html +2482 -0
- package/docs/Medal.html +1007 -0
- package/docs/Medals.html +628 -0
- package/docs/Music.html +584 -0
- package/docs/Newgrounds.html +1399 -0
- package/docs/Particle.html +4535 -0
- package/docs/ParticleEmitter.html +7770 -0
- package/docs/Random.html +1774 -0
- package/docs/Settings.html +2679 -0
- package/docs/Sound.html +1249 -0
- package/docs/TileCollision.html +1409 -0
- package/docs/TileLayer.html +6948 -0
- package/docs/TileLayerData.html +1052 -0
- package/docs/Timer.html +1183 -0
- package/docs/Utilities.html +3281 -0
- package/docs/Vector2.html +4207 -0
- package/docs/WebGL.html +2327 -0
- package/docs/engine.js.html +429 -0
- package/docs/engineAudio.js.html +609 -0
- package/docs/engineDebug.js.html +725 -0
- package/docs/engineDraw.js.html +419 -0
- package/docs/engineInput.js.html +401 -0
- package/docs/engineMedals.js.html +461 -0
- package/docs/engineObject.js.html +518 -0
- package/docs/engineParticles.js.html +441 -0
- package/docs/engineSettings.js.html +349 -0
- package/docs/engineTileLayer.js.html +513 -0
- package/docs/engineUtilities.js.html +647 -0
- package/docs/engineWebGL.js.html +522 -0
- 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 +93 -0
- package/docs/fonts/OFL-montserrat.txt +93 -0
- package/docs/global.html +1691 -0
- package/docs/index.css +6 -0
- package/docs/index.html +247 -0
- package/docs/scripts/fix-code-block.js +53 -0
- package/docs/scripts/fix-navbar.js +25 -0
- package/docs/scripts/linenumber.js +25 -0
- package/docs/scripts/misc.js +217 -0
- package/docs/scripts/resize.js +85 -0
- package/docs/scripts/search.js +83 -0
- package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
- package/docs/scripts/third-party/fuse.js +9 -0
- package/docs/scripts/third-party/lang-css.js +2 -0
- package/docs/scripts/third-party/prettify.js +28 -0
- package/docs/static/favicon.png +0 -0
- package/docs/static/index.css +4 -0
- package/docs/styles/clean-jsdoc-theme-base.css +393 -0
- package/docs/styles/clean-jsdoc-theme-dark.css +324 -0
- package/docs/styles/clean-jsdoc-theme-light.css +319 -0
- package/docs/styles/reset.css +346 -0
- package/docs/styles/third-party/ionicons.min.css +11 -0
- package/docs/styles/third-party/prettify-jsdoc.css +111 -0
- package/docs/styles/third-party/prettify-tomorrow.css +5 -0
- package/engine/engine.all.js +1997 -744
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.release.js +1743 -569
- package/engine/engine.js +107 -39
- package/engine/engineAudio.js +152 -34
- package/engine/{build.bat → engineBuild.bat} +10 -10
- package/engine/{buildSetup.bat → engineBuildSetup.bat} +0 -0
- package/engine/engineDebug.js +105 -32
- package/engine/engineDraw.js +137 -32
- package/engine/engineInput.js +111 -37
- package/engine/{engineMedal.js → engineMedals.js} +129 -56
- package/engine/engineObject.js +117 -67
- package/engine/engineParticles.js +282 -0
- package/engine/engineRelease.js +3 -9
- package/engine/engineSettings.js +190 -0
- package/engine/engineTileLayer.js +147 -44
- package/engine/engineUtilities.js +488 -0
- package/engine/engineWebGL.js +113 -74
- package/examples/breakout/game.js +1 -1
- package/examples/breakout/gameObjects.js +3 -3
- package/examples/breakout/index.html +3 -3
- package/examples/platformer/game.js +5 -7
- package/examples/platformer/gameEffects.js +7 -7
- package/examples/platformer/gameLevel.js +2 -2
- package/examples/platformer/gameObjects.js +4 -5
- package/examples/platformer/gamePlayer.js +3 -3
- package/examples/platformer/index.html +6 -6
- package/examples/puzzle/game.js +1 -2
- package/examples/puzzle/index.html +2 -2
- package/examples/stress/index.html +2 -8
- package/game.js +5 -11
- package/index.html +13 -13
- package/package.json +4 -4
- package/tiles.png +0 -0
- package/engine/engineConfig.js +0 -64
- package/engine/engineParticle.js +0 -199
- package/engine/engineUtil.js +0 -147
package/engine/engine.all.js
CHANGED
|
@@ -1,69 +1,137 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Debug Features
|
|
9
|
-
- debug overlay with mouse pick
|
|
10
|
-
- debug primitive rendering
|
|
11
|
-
- save screenshots
|
|
12
|
-
*/
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS Debug System
|
|
3
|
+
* <br> - Debug overlay with mouse pick
|
|
4
|
+
* <br> - Debug primitive rendering
|
|
5
|
+
* <br> - Save screenshots to disk
|
|
6
|
+
* @namespace Debug
|
|
7
|
+
*/
|
|
13
8
|
|
|
14
9
|
'use strict';
|
|
15
10
|
|
|
11
|
+
/** True if debug is enabled
|
|
12
|
+
* @default
|
|
13
|
+
* @memberof Debug */
|
|
16
14
|
const debug = 1;
|
|
15
|
+
|
|
16
|
+
/** True if asserts are enaled
|
|
17
|
+
* @default
|
|
18
|
+
* @memberof Debug */
|
|
17
19
|
const enableAsserts = 1;
|
|
20
|
+
|
|
21
|
+
/** Size to render debug points by default
|
|
22
|
+
* @default
|
|
23
|
+
* @memberof Debug */
|
|
18
24
|
const debugPointSize = .5;
|
|
19
25
|
|
|
26
|
+
/** True if watermark with FPS should be down
|
|
27
|
+
* @default
|
|
28
|
+
* @memberof Debug */
|
|
20
29
|
let showWatermark = 1;
|
|
30
|
+
|
|
31
|
+
/** True if god mode is enabled, handle this however you want
|
|
32
|
+
* @default
|
|
33
|
+
* @memberof Debug */
|
|
21
34
|
let godMode = 0;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
let debugPhysics = 0
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
|
|
36
|
+
// Engine internal variables not exposed to documentation
|
|
37
|
+
let debugPrimitives = [], debugOverlay = 0, debugPhysics = 0, debugRaycast = 0,
|
|
38
|
+
debugParticles = 0, debugGamepads = 0, debugMedals = 0, debugTakeScreenshot, downloadLink;
|
|
39
|
+
|
|
40
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
41
|
+
// Debug helper functions
|
|
42
|
+
|
|
43
|
+
/** Asserts if the experssion is false, does not do anything in release builds
|
|
44
|
+
* @param {Boolean} assertion
|
|
45
|
+
* @param {Object} output
|
|
46
|
+
* @memberof Debug */
|
|
34
47
|
const ASSERT = enableAsserts ? (...assert)=> console.assert(...assert) : ()=>{};
|
|
35
|
-
|
|
48
|
+
|
|
49
|
+
/** Draw a debug rectangle in world space
|
|
50
|
+
* @param {Vector2} pos
|
|
51
|
+
* @param {Vector2} [size=new Vector2()]
|
|
52
|
+
* @param {String} [color='#fff']
|
|
53
|
+
* @param {Number} [time=0]
|
|
54
|
+
* @param {Number} [angle=0]
|
|
55
|
+
* @param {Boolean} [fill=0]
|
|
56
|
+
* @memberof Debug */
|
|
57
|
+
const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=0)=>
|
|
36
58
|
{
|
|
37
59
|
ASSERT(typeof color == 'string'); // pass in regular html strings as colors
|
|
38
60
|
debugPrimitives.push({pos, size:vec2(size), color, time:new Timer(time), angle, fill});
|
|
39
61
|
}
|
|
62
|
+
|
|
63
|
+
/** Draw a debug circle in world space
|
|
64
|
+
* @param {Vector2} pos
|
|
65
|
+
* @param {Number} [radius=0]
|
|
66
|
+
* @param {String} [color='#fff']
|
|
67
|
+
* @param {Number} [time=0]
|
|
68
|
+
* @param {Boolean} [fill=0]
|
|
69
|
+
* @memberof Debug */
|
|
40
70
|
const debugCircle = (pos, radius=0, color='#fff', time=0, fill=0)=>
|
|
41
71
|
{
|
|
42
72
|
ASSERT(typeof color == 'string'); // pass in regular html strings as colors
|
|
43
73
|
debugPrimitives.push({pos, size:radius, color, time:new Timer(time), angle:0, fill});
|
|
44
74
|
}
|
|
75
|
+
|
|
76
|
+
/** Draw a debug point in world space
|
|
77
|
+
* @param {Vector2} pos
|
|
78
|
+
* @param {String} [color='#fff']
|
|
79
|
+
* @param {Number} [time=0]
|
|
80
|
+
* @param {Number} [angle=0]
|
|
81
|
+
* @memberof Debug */
|
|
45
82
|
const debugPoint = (pos, color, time, angle)=> debugRect(pos, 0, color, time, angle);
|
|
83
|
+
|
|
84
|
+
/** Draw a debug line in world space
|
|
85
|
+
* @param {Vector2} posA
|
|
86
|
+
* @param {Vector2} posB
|
|
87
|
+
* @param {String} [color='#fff']
|
|
88
|
+
* @param {Number} [thickness=.1]
|
|
89
|
+
* @param {Number} [time=0]
|
|
90
|
+
* @memberof Debug */
|
|
46
91
|
const debugLine = (posA, posB, color, thickness=.1, time)=>
|
|
47
92
|
{
|
|
48
93
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
49
94
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
50
95
|
debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), 1);
|
|
51
96
|
}
|
|
52
|
-
|
|
97
|
+
|
|
98
|
+
/** Draw a debug axis aligned bounding box in world space
|
|
99
|
+
* @param {Vector2} posA
|
|
100
|
+
* @param {Vector2} sizeA
|
|
101
|
+
* @param {Vector2} posB
|
|
102
|
+
* @param {Vector2} sizeB
|
|
103
|
+
* @param {String} [color='#fff']
|
|
104
|
+
* @memberof Debug */
|
|
105
|
+
const debugAABB = (pA, sA, pB, sB, color)=>
|
|
53
106
|
{
|
|
54
107
|
const minPos = vec2(min(pA.x - sA.x/2, pB.x - sB.x/2), min(pA.y - sA.y/2, pB.y - sB.y/2));
|
|
55
108
|
const maxPos = vec2(max(pA.x + sA.x/2, pB.x + sB.x/2), max(pA.y + sA.y/2, pB.y + sB.y/2));
|
|
56
109
|
debugRect(minPos.lerp(maxPos,.5), maxPos.subtract(minPos), color);
|
|
57
110
|
}
|
|
58
|
-
|
|
111
|
+
|
|
112
|
+
/** Draw a debug axis aligned bounding box in world space
|
|
113
|
+
* @param {String} text
|
|
114
|
+
* @param {Vector2} pos
|
|
115
|
+
* @param {Number} [size=1]
|
|
116
|
+
* @param {String} [color='#fff']
|
|
117
|
+
* @param {Number} [time=0]
|
|
118
|
+
* @param {Number} [angle=0]
|
|
119
|
+
* @param {String} [font='monospace']
|
|
120
|
+
* @memberof Debug */
|
|
121
|
+
const debugText = (text, pos, size=1, color='#fff', time=0, angle=0, font='monospace')=>
|
|
59
122
|
{
|
|
60
123
|
ASSERT(typeof color == 'string'); // pass in regular html strings as colors
|
|
61
124
|
debugPrimitives.push({text, pos, size, color, time:new Timer(time), angle, font});
|
|
62
125
|
}
|
|
63
126
|
|
|
127
|
+
/** Clear all debug primitives in the list
|
|
128
|
+
* @memberof Debug */
|
|
64
129
|
const debugClear = ()=> debugPrimitives = [];
|
|
65
130
|
|
|
66
|
-
|
|
131
|
+
/** Save a canvas to disk
|
|
132
|
+
* @param {HTMLCanvasElement} canvas
|
|
133
|
+
* @param {String} [filename]
|
|
134
|
+
* @memberof Debug */
|
|
67
135
|
const debugSaveCanvas = (canvas, filename = engineName + '.png') =>
|
|
68
136
|
{
|
|
69
137
|
downloadLink.download = 'screenshot.png';
|
|
@@ -72,7 +140,7 @@ const debugSaveCanvas = (canvas, filename = engineName + '.png') =>
|
|
|
72
140
|
}
|
|
73
141
|
|
|
74
142
|
///////////////////////////////////////////////////////////////////////////////
|
|
75
|
-
//
|
|
143
|
+
// Engine debug function (called automatically)
|
|
76
144
|
|
|
77
145
|
const debugInit = ()=>
|
|
78
146
|
{
|
|
@@ -112,6 +180,11 @@ const debugRender = ()=>
|
|
|
112
180
|
|
|
113
181
|
if (debugTakeScreenshot)
|
|
114
182
|
{
|
|
183
|
+
// composite canvas
|
|
184
|
+
glCopyToContext(mainContext, 1);
|
|
185
|
+
mainContext.drawImage(overlayCanvas, 0, 0);
|
|
186
|
+
overlayCanvas.width |= 0;
|
|
187
|
+
|
|
115
188
|
debugSaveCanvas(mainCanvas);
|
|
116
189
|
debugTakeScreenshot = 0;
|
|
117
190
|
}
|
|
@@ -189,8 +262,8 @@ const debugRender = ()=>
|
|
|
189
262
|
const saveContext = mainContext;
|
|
190
263
|
mainContext = overlayContext
|
|
191
264
|
const raycastHitPos = tileCollisionRaycast(bestObject.pos, mousePos);
|
|
192
|
-
raycastHitPos && drawRect(raycastHitPos.
|
|
193
|
-
drawRect(mousePos.
|
|
265
|
+
raycastHitPos && drawRect(raycastHitPos.floor().add(vec2(.5)), vec2(1), new Color(0,1,1,.3));
|
|
266
|
+
drawRect(mousePos.floor().add(vec2(.5)), vec2(1), new Color(0,0,1,.5));
|
|
194
267
|
drawLine(mousePos, bestObject.pos, .1, !raycastHitPos ? new Color(0,1,0,.5) : new Color(1,0,0,.5));
|
|
195
268
|
|
|
196
269
|
let pos = mousePos.copy(), height = vec2(0,.5);
|
|
@@ -304,7 +377,7 @@ const debugRender = ()=>
|
|
|
304
377
|
}
|
|
305
378
|
|
|
306
379
|
///////////////////////////////////////////////////////////////////////////////
|
|
307
|
-
// particle system editor
|
|
380
|
+
// particle system editor (work in progress)
|
|
308
381
|
let debugParticleEditor = 0, debugParticleSystem, debugParticleSystemDiv, particleSystemCode;
|
|
309
382
|
|
|
310
383
|
const debugToggleParticleEditor = ()=>
|
|
@@ -491,104 +564,422 @@ const debugParticleSettings =
|
|
|
491
564
|
['randomColorComponents'],
|
|
492
565
|
['renderOrder'],
|
|
493
566
|
];
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
*/
|
|
567
|
+
/**
|
|
568
|
+
* LittleJS Utility Classes and Functions
|
|
569
|
+
* <br> - General purpose math library
|
|
570
|
+
* <br> - Vector2 - fast, simple, easy 2D vector class
|
|
571
|
+
* <br> - Color - holds a rgba color with some math functions
|
|
572
|
+
* <br> - Timer - tracks time automatically
|
|
573
|
+
* @namespace Utilities
|
|
574
|
+
*/
|
|
502
575
|
|
|
503
576
|
'use strict';
|
|
504
577
|
|
|
578
|
+
/** A shortcut to get Math.PI
|
|
579
|
+
* @const
|
|
580
|
+
* @memberof Utilities */
|
|
581
|
+
const PI = Math.PI;
|
|
582
|
+
|
|
583
|
+
/** True if running a Chromium based browser
|
|
584
|
+
* @const
|
|
585
|
+
* @memberof Utilities */
|
|
586
|
+
const isChrome = window['chrome'];
|
|
587
|
+
|
|
588
|
+
/** Returns absoulte value of value passed in
|
|
589
|
+
* @param {Number} value
|
|
590
|
+
* @return {Number}
|
|
591
|
+
* @memberof Utilities */
|
|
592
|
+
const abs = (a)=> a < 0 ? -a : a;
|
|
593
|
+
|
|
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
|
+
/** Returns lowest of two values passed in
|
|
601
|
+
* @param {Number} valueA
|
|
602
|
+
* @param {Number} valueB
|
|
603
|
+
* @return {Number}
|
|
604
|
+
* @memberof Utilities */
|
|
605
|
+
const min = (a, b)=> a < b ? a : b;
|
|
606
|
+
|
|
607
|
+
/** Returns highest of two values passed in
|
|
608
|
+
* @param {Number} valueA
|
|
609
|
+
* @param {Number} valueB
|
|
610
|
+
* @return {Number}
|
|
611
|
+
* @memberof Utilities */
|
|
612
|
+
const max = (a, b)=> a > b ? a : b;
|
|
613
|
+
|
|
614
|
+
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
615
|
+
* @param {Number} dividend
|
|
616
|
+
* @param {Number} divisor
|
|
617
|
+
* @return {Number}
|
|
618
|
+
* @memberof Utilities */
|
|
619
|
+
const mod = (a, b)=> ((a % b) + b) % b;
|
|
620
|
+
|
|
621
|
+
/** Clamps the value beween max and min
|
|
622
|
+
* @param {Number} value
|
|
623
|
+
* @param {Number} [max=1]
|
|
624
|
+
* @param {Number} [min=0]
|
|
625
|
+
* @return {Number}
|
|
626
|
+
* @memberof Utilities */
|
|
627
|
+
const clamp = (v, max=1, min=0)=> (ASSERT(max > min), v < min ? min : v > max ? max : v);
|
|
628
|
+
|
|
629
|
+
/** Returns what percentage the value is between max and min
|
|
630
|
+
* @param {Number} value
|
|
631
|
+
* @param {Number} [max=1]
|
|
632
|
+
* @param {Number} [min=0]
|
|
633
|
+
* @return {Number}
|
|
634
|
+
* @memberof Utilities */
|
|
635
|
+
const percent = (v, max=1, min=0)=> max-min ? clamp((v-min) / (max-min)) : 0;
|
|
636
|
+
|
|
637
|
+
/** Linearly interpolates the percent value between max and min
|
|
638
|
+
* @param {Number} percent
|
|
639
|
+
* @param {Number} [max=1]
|
|
640
|
+
* @param {Number} [min=0]
|
|
641
|
+
* @return {Number}
|
|
642
|
+
* @memberof Utilities */
|
|
643
|
+
const lerp = (p, max=1, min=0)=> min + clamp(p) * (max-min);
|
|
644
|
+
|
|
645
|
+
/** Formats seconds to mm:ss 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);
|
|
650
|
+
|
|
651
|
+
/** Returns the nearest power of two not less then the value
|
|
652
|
+
* @param {Number} value
|
|
653
|
+
* @return {Number}
|
|
654
|
+
* @memberof Utilities */
|
|
655
|
+
const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
|
|
656
|
+
|
|
657
|
+
/** Applies smoothstep function to the percentage value
|
|
658
|
+
* @param {Number} value
|
|
659
|
+
* @return {Number}
|
|
660
|
+
* @memberof Utilities */
|
|
661
|
+
const smoothStep = (p)=> p * p * (3 - 2 * p);
|
|
662
|
+
|
|
663
|
+
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
664
|
+
* @param {Vector2} pointA - Center of box A
|
|
665
|
+
* @param {Vector2} sizeA - Size of box A
|
|
666
|
+
* @param {Vector2} pointB - Center of box B
|
|
667
|
+
* @param {Vector2} sizeB - Size of box B
|
|
668
|
+
* @return {Boolean} - True if overlapping
|
|
669
|
+
* @memberof Utilities */
|
|
670
|
+
const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x & abs(pA.y - pB.y)*2 < sA.y + sB.y;
|
|
671
|
+
|
|
672
|
+
/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
|
|
673
|
+
* @param {Number} [frequency=1] - Frequency of the wave in Hz
|
|
674
|
+
* @param {Number} [amplitude=1] - Amplitude (max height) of the wave
|
|
675
|
+
* @param {Number} [t=time] - Value to use for time of the wave
|
|
676
|
+
* @return {Number} - Value waving between 0 and amplitude
|
|
677
|
+
* @memberof Utilities */
|
|
678
|
+
const wave = (frequency=1, amplitude=1, t=time)=> amplitude/2 * (1 - Math.cos(t*frequency*2*PI));
|
|
679
|
+
|
|
505
680
|
///////////////////////////////////////////////////////////////////////////////
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const randSign
|
|
681
|
+
|
|
682
|
+
/** Random global functions
|
|
683
|
+
* @namespace Random */
|
|
684
|
+
|
|
685
|
+
/** Returns a random value between the two values passed in
|
|
686
|
+
* @param {Number} [valueA=1]
|
|
687
|
+
* @param {Number} [valueB=0]
|
|
688
|
+
* @return {Number}
|
|
689
|
+
* @memberof Random */
|
|
690
|
+
const rand = (a=1, b=0)=> b + (a-b)*Math.random();
|
|
691
|
+
|
|
692
|
+
/** Returns a floored random value the two values passed in
|
|
693
|
+
* @param {Number} [valueA=1]
|
|
694
|
+
* @param {Number} [valueB=0]
|
|
695
|
+
* @return {Number}
|
|
696
|
+
* @memberof Random */
|
|
697
|
+
const randInt = (a=1, b=0)=> rand(a,b)|0;
|
|
698
|
+
|
|
699
|
+
/** Randomly returns either -1 or 1
|
|
700
|
+
* @return {Number}
|
|
701
|
+
* @memberof Random */
|
|
702
|
+
const randSign = ()=> (rand(2)|0)*2-1;
|
|
703
|
+
|
|
704
|
+
/** Returns a random Vector2 within a circular shape
|
|
705
|
+
* @param {Number} [radius=1]
|
|
706
|
+
* @param {Number} [minRadius=0]
|
|
707
|
+
* @return {Vector2}
|
|
708
|
+
* @memberof Random */
|
|
528
709
|
const randInCircle = (radius=1, minRadius=0)=> radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2;
|
|
529
|
-
const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
|
|
530
|
-
const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
|
|
531
|
-
linear ? cA.lerp(cB, rand()) : new Color(rand(cA.r,cB.r),rand(cA.g,cB.g),rand(cA.b,cB.b),rand(cA.a,cB.a));
|
|
532
710
|
|
|
533
|
-
|
|
534
|
-
|
|
711
|
+
/** Returns a random Vector2 with the passed in length
|
|
712
|
+
* @param {Number} [length=1]
|
|
713
|
+
* @return {Vector2}
|
|
714
|
+
* @memberof Random */
|
|
715
|
+
const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
|
|
716
|
+
|
|
717
|
+
/** Returns a random color between the two passed in colors, combine components if linear
|
|
718
|
+
* @param {Color} [colorA=new Color(1,1,1,1)]
|
|
719
|
+
* @param {Color} [colorB=new Color(0,0,0,1)]
|
|
720
|
+
* @param {Boolean} [linear]
|
|
721
|
+
* @return {Color}
|
|
722
|
+
* @memberof Random */
|
|
723
|
+
const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
|
|
724
|
+
linear ? cA.lerp(cB, rand()) : new Color(rand(cA.r,cB.r),rand(cA.g,cB.g),rand(cA.b,cB.b),rand(cA.a,cB.a));
|
|
725
|
+
|
|
726
|
+
/** The seed used by the randSeeded function, should not be 0
|
|
727
|
+
* @memberof Random */
|
|
728
|
+
let randSeed = 1;
|
|
729
|
+
|
|
730
|
+
/** Returns a seeded random value between the two values passed in using randSeed
|
|
731
|
+
* @param {Number} [valueA=1]
|
|
732
|
+
* @param {Number} [valueB=0]
|
|
733
|
+
* @return {Number}
|
|
734
|
+
* @memberof Random */
|
|
535
735
|
const randSeeded = (a=1, b=0)=>
|
|
536
736
|
{
|
|
537
|
-
randSeed ^= randSeed << 13; randSeed ^= randSeed >>> 17; randSeed ^= randSeed << 5;
|
|
737
|
+
randSeed ^= randSeed << 13; randSeed ^= randSeed >>> 17; randSeed ^= randSeed << 5; // xorshift
|
|
538
738
|
return b + (a-b)*abs(randSeed % 1e9)/1e9;
|
|
539
739
|
}
|
|
540
740
|
|
|
541
|
-
// create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
|
|
542
|
-
const vec2 = (x=0, y)=> x.x == undefined? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
|
|
543
|
-
|
|
544
741
|
///////////////////////////////////////////////////////////////////////////////
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
|
|
745
|
+
* @param {Number} [x=0]
|
|
746
|
+
* @param {Number} [y=0]
|
|
747
|
+
* @return {Vector2}
|
|
748
|
+
* @example
|
|
749
|
+
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
750
|
+
* let b = vec2(a); // copy a into b
|
|
751
|
+
* a = vec2(5); // set a to (5, 5)
|
|
752
|
+
* b = vec2(); // set b to (0, 0)
|
|
753
|
+
* @memberof Utilities
|
|
754
|
+
*/
|
|
755
|
+
const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* 2D Vector object with vector math library
|
|
759
|
+
* @example
|
|
760
|
+
* let a = new Vector2(2, 3); // vector with coordinates (2, 3)
|
|
761
|
+
* let b = new Vector2; // vector with coordinates (0, 0)
|
|
762
|
+
* let c = vec2(4, 2); // use the vec2 function to make a Vector2
|
|
763
|
+
*/
|
|
545
764
|
class Vector2
|
|
546
765
|
{
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
766
|
+
/** Create a 2D vector with the x and y passed in, can also be created with vec2()
|
|
767
|
+
* @param {Number} [x=0] - X axis location
|
|
768
|
+
* @param {Number} [y=0] - Y axis location */
|
|
769
|
+
constructor(x=0, y=0)
|
|
770
|
+
{
|
|
771
|
+
/** @property {Number} - X axis location */
|
|
772
|
+
this.x = x;
|
|
773
|
+
/** @property {Number} - Y axis location */
|
|
774
|
+
this.y = y;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/** Returns a new vector that is a copy of this
|
|
778
|
+
* @return {Vector2} */
|
|
779
|
+
copy() { return new Vector2(this.x, this.y); }
|
|
780
|
+
|
|
781
|
+
/** Returns a copy of this vector plus the vector passed in
|
|
782
|
+
* @param {Vector2} vector
|
|
783
|
+
* @return {Vector2} */
|
|
784
|
+
add(v) { ASSERT(v.x!=undefined); return new Vector2(this.x + v.x, this.y + v.y); }
|
|
785
|
+
|
|
786
|
+
/** Returns a copy of this vector minus the vector passed in
|
|
787
|
+
* @param {Vector2} vector
|
|
788
|
+
* @return {Vector2} */
|
|
789
|
+
subtract(v) { ASSERT(v.x!=undefined); return new Vector2(this.x - v.x, this.y - v.y); }
|
|
790
|
+
|
|
791
|
+
/** Returns a copy of this vector times the vector passed in
|
|
792
|
+
* @param {Vector2} vector
|
|
793
|
+
* @return {Vector2} */
|
|
794
|
+
multiply(v) { ASSERT(v.x!=undefined); return new Vector2(this.x * v.x, this.y * v.y); }
|
|
795
|
+
|
|
796
|
+
/** Returns a copy of this vector divided by the vector passed in
|
|
797
|
+
* @param {Vector2} vector
|
|
798
|
+
* @return {Vector2} */
|
|
799
|
+
divide(v) { ASSERT(v.x!=undefined); return new Vector2(this.x / v.x, this.y / v.y); }
|
|
800
|
+
|
|
801
|
+
/** Returns a copy of this vector scaled by the vector passed in
|
|
802
|
+
* @param {Number} scale
|
|
803
|
+
* @return {Vector2} */
|
|
804
|
+
scale(s) { ASSERT(s.x==undefined); return new Vector2(this.x * s, this.y * s); }
|
|
805
|
+
|
|
806
|
+
/** Returns the length of this vector
|
|
807
|
+
* @return {Number} */
|
|
808
|
+
length() { return this.lengthSquared()**.5; }
|
|
809
|
+
|
|
810
|
+
/** Returns the length of this vector squared
|
|
811
|
+
* @return {Number} */
|
|
812
|
+
lengthSquared() { return this.x**2 + this.y**2; }
|
|
813
|
+
|
|
814
|
+
/** Returns the distance from this vector to vector passed in
|
|
815
|
+
* @param {Vector2} vector
|
|
816
|
+
* @return {Number} */
|
|
817
|
+
distance(v) { return this.distanceSquared(v)**.5; }
|
|
818
|
+
|
|
819
|
+
/** Returns the distance squared from this vector to vector passed in
|
|
820
|
+
* @param {Vector2} vector
|
|
821
|
+
* @return {Number} */
|
|
822
|
+
distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
|
|
823
|
+
|
|
824
|
+
/** Returns a new vector in same direction as this one with the length passed in
|
|
825
|
+
* @param {Number} [length=1]
|
|
826
|
+
* @return {Vector2} */
|
|
827
|
+
normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(length); }
|
|
828
|
+
|
|
829
|
+
/** Returns a new vector clamped to length passed in
|
|
830
|
+
* @param {Number} [length=1]
|
|
831
|
+
* @return {Vector2} */
|
|
563
832
|
clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
833
|
+
|
|
834
|
+
/** Returns the dot product of this and the vector passed in
|
|
835
|
+
* @param {Vector2} vector
|
|
836
|
+
* @return {Number} */
|
|
837
|
+
dot(v) { ASSERT(v.x!=undefined); return this.x*v.x + this.y*v.y; }
|
|
838
|
+
|
|
839
|
+
/** Returns the cross product of this and the vector passed in
|
|
840
|
+
* @param {Vector2} vector
|
|
841
|
+
* @return {Number} */
|
|
842
|
+
cross(v) { ASSERT(v.x!=undefined); return this.x*v.y - this.y*v.x; }
|
|
843
|
+
|
|
844
|
+
/** Returns the angle of this vector, up is angle 0
|
|
845
|
+
* @return {Number} */
|
|
846
|
+
angle() { return Math.atan2(this.x, this.y); }
|
|
847
|
+
|
|
848
|
+
/** Sets this vector with angle and length passed in
|
|
849
|
+
* @param {Number} [angle=0]
|
|
850
|
+
* @param {Number} [length=1] */
|
|
851
|
+
setAngle(a=0, length=1) { this.x = length*Math.sin(a); this.y = length*Math.cos(a); return this; }
|
|
852
|
+
|
|
853
|
+
/** Returns copy of this vector rotated by the angle passed in
|
|
854
|
+
* @param {Number} angle
|
|
855
|
+
* @return {Vector2} */
|
|
856
|
+
rotate(a) { const c = Math.cos(a), s = Math.sin(a); return new Vector2(this.x*c-this.y*s, this.x*s+this.y*c); }
|
|
857
|
+
|
|
858
|
+
/** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
|
|
859
|
+
* @return {Number} */
|
|
860
|
+
direction() { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
|
|
861
|
+
|
|
862
|
+
/** Returns a copy of this vector that has been inverted
|
|
863
|
+
* @return {Vector2} */
|
|
864
|
+
invert() { return new Vector2(this.y, -this.x); }
|
|
865
|
+
|
|
866
|
+
/** Returns a copy of this vector with the axies flipped
|
|
867
|
+
* @return {Vector2} */
|
|
868
|
+
flip() { return new Vector2(this.y, this.x); }
|
|
869
|
+
|
|
870
|
+
/** Returns a copy of this vector with each axis floored
|
|
871
|
+
* @return {Vector2} */
|
|
872
|
+
floor() { return new Vector2(Math.floor(this.x), Math.floor(this.y)); }
|
|
873
|
+
|
|
874
|
+
/** Returns the area this vector covers as a rectangle
|
|
875
|
+
* @return {Number} */
|
|
876
|
+
area() { return this.x * this.y; }
|
|
877
|
+
|
|
878
|
+
/** Returns a new vector that is p percent between this and the vector passed in
|
|
879
|
+
* @param {Vector2} vector
|
|
880
|
+
* @param {Number} percent
|
|
881
|
+
* @return {Vector2} */
|
|
882
|
+
lerp(v, p) { ASSERT(v.x!=undefined); return this.add(v.subtract(this).scale(clamp(p))); }
|
|
883
|
+
|
|
884
|
+
/** Returns true if this vector is within the bounds of an array size passed in
|
|
885
|
+
* @param {Vector2} arraySize
|
|
886
|
+
* @return {Boolean} */
|
|
577
887
|
arrayCheck(arraySize) { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
|
|
578
888
|
}
|
|
579
889
|
|
|
580
890
|
///////////////////////////////////////////////////////////////////////////////
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Color object (red, green, blue, alpha) with some helpful functions
|
|
894
|
+
* @example
|
|
895
|
+
* let a = new Color; // white
|
|
896
|
+
* let b = new Color(1, 0, 0); // red
|
|
897
|
+
* let c = new Color(0, 0, 0, 0); // transparent black
|
|
898
|
+
*/
|
|
581
899
|
class Color
|
|
582
900
|
{
|
|
583
|
-
|
|
901
|
+
/** Create a color with the components passed in, white by default
|
|
902
|
+
* @param {Number} [red=1]
|
|
903
|
+
* @param {Number} [green=1]
|
|
904
|
+
* @param {Number} [blue=1]
|
|
905
|
+
* @param {Number} [alpha=1] */
|
|
906
|
+
constructor(r=1, g=1, b=1, a=1)
|
|
907
|
+
{
|
|
908
|
+
/** @property {Number} - Red */
|
|
909
|
+
this.r = r;
|
|
910
|
+
/** @property {Number} - Green */
|
|
911
|
+
this.g = g;
|
|
912
|
+
/** @property {Number} - Blue */
|
|
913
|
+
this.b = b;
|
|
914
|
+
/** @property {Number} - Alpha */
|
|
915
|
+
this.a = a;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/** Returns a new color that is a copy of this
|
|
919
|
+
* @return {Color} */
|
|
920
|
+
copy() { return new Color(this.r, this.g, this.b, this.a); }
|
|
584
921
|
|
|
585
|
-
|
|
586
|
-
|
|
922
|
+
/** Returns a copy of this color plus the color passed in
|
|
923
|
+
* @param {Color} color
|
|
924
|
+
* @return {Color} */
|
|
925
|
+
add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
|
|
926
|
+
|
|
927
|
+
/** Returns a copy of this color minus the color passed in
|
|
928
|
+
* @param {Color} color
|
|
929
|
+
* @return {Color} */
|
|
587
930
|
subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
|
|
931
|
+
|
|
932
|
+
/** Returns a copy of this color times the color passed in
|
|
933
|
+
* @param {Color} color
|
|
934
|
+
* @return {Color} */
|
|
588
935
|
multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
936
|
+
|
|
937
|
+
/** Returns a copy of this color divided by the color passed in
|
|
938
|
+
* @param {Color} color
|
|
939
|
+
* @return {Color} */
|
|
940
|
+
divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
|
|
941
|
+
|
|
942
|
+
/** Returns a copy of this color scaled by the value passed in, alpha can be scaled separately
|
|
943
|
+
* @param {Number} scale
|
|
944
|
+
* @param {Number} [alphaScale=scale]
|
|
945
|
+
* @return {Color} */
|
|
946
|
+
scale(s, a=s) { return new Color(this.r*s, this.g*s, this.b*s, this.a*a); }
|
|
947
|
+
|
|
948
|
+
/** Returns a copy of this color clamped to the valid range between 0 and 1
|
|
949
|
+
* @return {Color} */
|
|
950
|
+
clamp() { return new Color(clamp(this.r), clamp(this.g), clamp(this.b), clamp(this.a)); }
|
|
951
|
+
|
|
952
|
+
/** Returns a new color that is p percent between this and the color passed in
|
|
953
|
+
* @param {Color} color
|
|
954
|
+
* @param {Number} percent
|
|
955
|
+
* @return {Color} */
|
|
956
|
+
lerp(c, p) { return this.add(c.subtract(this).scale(clamp(p))); }
|
|
957
|
+
|
|
958
|
+
/** Sets this color given a hue, saturation, lightness , and alpha
|
|
959
|
+
* @param {Number} [hue=0]
|
|
960
|
+
* @param {Number} [saturation=0]
|
|
961
|
+
* @param {Number} [lightness=1]
|
|
962
|
+
* @param {Number} [alpha=1]
|
|
963
|
+
* @return {Color} */
|
|
964
|
+
setHSLA(h=0, s=0, l=1, a=1)
|
|
965
|
+
{
|
|
966
|
+
const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
|
|
967
|
+
f = (p, q, t)=>
|
|
968
|
+
(t = ((t%1)+1)%1) < 1/6 ? p+(q-p)*6*t :
|
|
969
|
+
t < 1/2 ? q :
|
|
970
|
+
t < 2/3 ? p+(q-p)*(2/3-t)*6 : p;
|
|
971
|
+
|
|
972
|
+
this.r = f(p, q, h + 1/3);
|
|
973
|
+
this.g = f(p, q, h);
|
|
974
|
+
this.b = f(p, q, h - 1/3);
|
|
975
|
+
this.a = a;
|
|
976
|
+
return this;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/** Returns a new color that has each component randomly adjusted
|
|
980
|
+
* @param {Number} [amount=.05]
|
|
981
|
+
* @param {Number} [alphaAmount=0]
|
|
982
|
+
* @return {Color} */
|
|
592
983
|
mutate(amount=.05, alphaAmount=0)
|
|
593
984
|
{
|
|
594
985
|
return new Color
|
|
@@ -599,357 +990,341 @@ class Color
|
|
|
599
990
|
this.a + rand(alphaAmount, -alphaAmount)
|
|
600
991
|
).clamp();
|
|
601
992
|
}
|
|
993
|
+
|
|
994
|
+
/** Returns this color expressed as an rgba string
|
|
995
|
+
* @return {String} */
|
|
602
996
|
rgba()
|
|
603
997
|
{
|
|
604
998
|
ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
|
|
605
999
|
return `rgb(${this.r*255|0},${this.g*255|0},${this.b*255|0},${this.a})`;
|
|
606
1000
|
}
|
|
1001
|
+
|
|
1002
|
+
/** Returns this color expressed as 32 bit integer value
|
|
1003
|
+
* @return {Number} */
|
|
607
1004
|
rgbaInt()
|
|
608
1005
|
{
|
|
609
1006
|
ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
|
|
610
1007
|
return (this.r*255|0) + (this.g*255<<8) + (this.b*255<<16) + (this.a*255<<24);
|
|
611
1008
|
}
|
|
612
|
-
setHSLA(h=0, s=0, l=1, a=1)
|
|
613
|
-
{
|
|
614
|
-
const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
|
|
615
|
-
f = (p, q, t)=>
|
|
616
|
-
(t = ((t%1)+1)%1) < 1/6 ? p+(q-p)*6*t :
|
|
617
|
-
t < 1/2 ? q :
|
|
618
|
-
t < 2/3 ? p+(q-p)*(2/3-t)*6 : p;
|
|
619
|
-
|
|
620
|
-
this.r = f(p, q, h + 1/3);
|
|
621
|
-
this.g = f(p, q, h);
|
|
622
|
-
this.b = f(p, q, h - 1/3);
|
|
623
|
-
this.a = a;
|
|
624
|
-
return this;
|
|
625
|
-
}
|
|
626
1009
|
}
|
|
627
1010
|
|
|
628
1011
|
///////////////////////////////////////////////////////////////////////////////
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* Timer object tracks how long has passed since it was set
|
|
1015
|
+
* @example
|
|
1016
|
+
* let a = new Timer; // creates a timer that is not set
|
|
1017
|
+
* a.set(3); // sets the timer to 3 seconds
|
|
1018
|
+
*
|
|
1019
|
+
* let b = new Timer(1); // creates a timer with 1 second left
|
|
1020
|
+
* b.unset(); // unsets the timer
|
|
1021
|
+
*/
|
|
629
1022
|
class Timer
|
|
630
1023
|
{
|
|
631
|
-
|
|
1024
|
+
/** Create a timer object set time passed in
|
|
1025
|
+
* @param {Number} [timeLeft] - How much time left before the timer elapses in seconds */
|
|
1026
|
+
constructor(timeLeft) { this.time = timeLeft == undefined ? undefined : time + timeLeft; this.setTime = timeLeft; }
|
|
632
1027
|
|
|
1028
|
+
/** Set the timer with seconds passed in
|
|
1029
|
+
* @param {Number} [timeLeft=0] - How much time left before the timer is elapsed in seconds */
|
|
633
1030
|
set(timeLeft=0) { this.time = time + timeLeft; this.setTime = timeLeft; }
|
|
634
|
-
unset() { this.time = undefined; }
|
|
635
|
-
isSet() { return this.time != undefined; }
|
|
636
|
-
active() { return time <= this.time; }
|
|
637
|
-
elapsed() { return time > this.time; }
|
|
638
|
-
get() { return this.isSet()? time - this.time : 0; }
|
|
639
|
-
getPercent() { return this.isSet()? percent(this.time - time, 0, this.setTime) : 0; }
|
|
640
|
-
}
|
|
641
|
-
/*
|
|
642
|
-
LittleJS Engine Configuration
|
|
643
|
-
*/
|
|
644
|
-
|
|
645
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
646
|
-
// display settings
|
|
647
1031
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
let fixedWidth = 0, fixedHeight = 0; // use native resolution
|
|
651
|
-
let fixedFitToWindow = 1; // stretch canvas to fit window
|
|
652
|
-
//const fixedWidth = 1280, fixedHeight = 720; // 720p
|
|
653
|
-
//const fixedWidth = 1920, fixedHeight = 1080; // 1080p
|
|
654
|
-
//const fixedWidth = 128, fixedHeight = 128; // PICO-8
|
|
655
|
-
//const fixedWidth = 240, fixedHeight = 136; // TIC-80
|
|
1032
|
+
/** Unset the timer */
|
|
1033
|
+
unset() { this.time = undefined; }
|
|
656
1034
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
const defaultTileSize = vec2(16); // default size of tiles in pixels
|
|
661
|
-
const tileBleedShrinkFix = .3; // prevent tile bleeding from neighbors
|
|
662
|
-
let pixelated = 1; // use crisp pixels for pixel art
|
|
663
|
-
|
|
664
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
665
|
-
// webgl config
|
|
1035
|
+
/** Returns true if set
|
|
1036
|
+
* @return {Boolean} */
|
|
1037
|
+
isSet() { return this.time != undefined; }
|
|
666
1038
|
|
|
667
|
-
|
|
668
|
-
|
|
1039
|
+
/** Returns true if set and has not elapsed
|
|
1040
|
+
* @return {Boolean} */
|
|
1041
|
+
active() { return time <= this.time; }
|
|
669
1042
|
|
|
670
|
-
|
|
671
|
-
|
|
1043
|
+
/** Returns true if set and elapsed
|
|
1044
|
+
* @return {Boolean} */
|
|
1045
|
+
elapsed() { return time > this.time; }
|
|
672
1046
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const defaultObjectAngleDamping = .99; // how much to slow angular velocity each frame 0-1
|
|
677
|
-
const defaultObjectElasticity = 0; // how much to bounce 0-1
|
|
678
|
-
const defaultObjectFriction = .8; // how much to slow when touching 0-1
|
|
679
|
-
const maxObjectSpeed = 1; // camp max speed to avoid fast objects missing collisions
|
|
1047
|
+
/** Get how long since elapsed, returns 0 if not set
|
|
1048
|
+
* @return {Number} */
|
|
1049
|
+
get() { return this.isSet()? time - this.time : 0; }
|
|
680
1050
|
|
|
681
|
-
|
|
682
|
-
|
|
1051
|
+
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
1052
|
+
* @return {Number} */
|
|
1053
|
+
getPercent() { return this.isSet()? percent(this.time - time, 0, this.setTime) : 0; }
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* LittleJS Engine Settings
|
|
1057
|
+
* @namespace Settings
|
|
1058
|
+
*/
|
|
683
1059
|
|
|
684
|
-
|
|
685
|
-
const touchInputEnable = 1; // touch input is routed to mouse
|
|
686
|
-
const copyGamepadDirectionToStick = 1; // allow players to use dpad as analog stick
|
|
687
|
-
const copyWASDToDpad = 1; // allow players to use WASD as direction keys
|
|
1060
|
+
'use strict';
|
|
688
1061
|
|
|
689
1062
|
///////////////////////////////////////////////////////////////////////////////
|
|
690
|
-
//
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
1063
|
+
// Display settings
|
|
1064
|
+
|
|
1065
|
+
/** The max size of the canvas, centered if window is larger
|
|
1066
|
+
* @type {Vector2}
|
|
1067
|
+
* @default
|
|
1068
|
+
* @memberof Settings */
|
|
1069
|
+
const maxSize = vec2(1920, 1200);
|
|
1070
|
+
|
|
1071
|
+
/** Fixed size of the canvas, if enabled cavnvas size never changes
|
|
1072
|
+
* @type {Vector2}
|
|
1073
|
+
* @default
|
|
1074
|
+
* @memberof Settings */
|
|
1075
|
+
let fixedSize = vec2();
|
|
1076
|
+
|
|
1077
|
+
/** Default font used for text rendering
|
|
1078
|
+
* @default
|
|
1079
|
+
* @memberof Settings */
|
|
1080
|
+
let fontDefault = 'arial';
|
|
1081
|
+
|
|
1082
|
+
/** Use crisp pixels for pixel art if true
|
|
1083
|
+
* @default
|
|
1084
|
+
* @memberof Settings */
|
|
1085
|
+
let pixelated = 1;
|
|
696
1086
|
|
|
697
1087
|
///////////////////////////////////////////////////////////////////////////////
|
|
698
|
-
//
|
|
1088
|
+
// Tile sheet settings
|
|
699
1089
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const
|
|
705
|
-
/*
|
|
706
|
-
LittleJS - The Tiny JavaScript Game Engine That Can
|
|
707
|
-
MIT License - Copyright 2019 Frank Force
|
|
1090
|
+
/** Default size of tiles in pixels
|
|
1091
|
+
* @type {Vector2}
|
|
1092
|
+
* @default
|
|
1093
|
+
* @memberof Settings */
|
|
1094
|
+
const tileSizeDefault = vec2(16);
|
|
708
1095
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
- Base class object handles update, physics, collision, rendering, etc
|
|
714
|
-
- Engine helper classes and functions like Vector2, Color, and Timer
|
|
715
|
-
- Super fast rendering system for tile sheets
|
|
716
|
-
- Sound effects audio with zzfx and music with zzfxm
|
|
717
|
-
- Input processing system with gamepad and touchscreen support
|
|
718
|
-
- Tile layer rendering and collision system
|
|
719
|
-
- Particle effect system
|
|
720
|
-
- Automatically calls gameInit(), gameUpdate(), gameUpdatePost(), gameRender(), gameRenderPost()
|
|
721
|
-
- Debug tools and debug rendering system
|
|
722
|
-
- Call engineInit() to start it up!
|
|
723
|
-
*/
|
|
1096
|
+
/** Prevent tile bleeding from neighbors in pixels
|
|
1097
|
+
* @default
|
|
1098
|
+
* @memberof Settings */
|
|
1099
|
+
const tileBleedShrinkFix = .3;
|
|
724
1100
|
|
|
725
|
-
|
|
1101
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1102
|
+
// Object settings
|
|
1103
|
+
|
|
1104
|
+
/** Default size of objects
|
|
1105
|
+
* @type {Vector2}
|
|
1106
|
+
* @default
|
|
1107
|
+
* @memberof Settings */
|
|
1108
|
+
const objectDefaultSize = vec2(1);
|
|
1109
|
+
|
|
1110
|
+
/** Default object mass for collison calcuations (how heavy objects are)
|
|
1111
|
+
* @default
|
|
1112
|
+
* @memberof Settings */
|
|
1113
|
+
const objectDefaultMass = 1;
|
|
1114
|
+
|
|
1115
|
+
/** How much to slow velocity by each frame (0-1)
|
|
1116
|
+
* @default
|
|
1117
|
+
* @memberof Settings */
|
|
1118
|
+
const objectDefaultDamping = .99;
|
|
1119
|
+
|
|
1120
|
+
/** How much to slow angular velocity each frame (0-1)
|
|
1121
|
+
* @default
|
|
1122
|
+
* @memberof Settings */
|
|
1123
|
+
const objectDefaultAngleDamping = .99;
|
|
1124
|
+
|
|
1125
|
+
/** How much to bounce when a collision occurs (0-1)
|
|
1126
|
+
* @default
|
|
1127
|
+
* @memberof Settings */
|
|
1128
|
+
const objectDefaultElasticity = 0;
|
|
1129
|
+
|
|
1130
|
+
/** How much to slow when touching (0-1)
|
|
1131
|
+
* @default
|
|
1132
|
+
* @memberof Settings */
|
|
1133
|
+
const objectDefaultFriction = .8;
|
|
1134
|
+
|
|
1135
|
+
/** Clamp max speed to avoid fast objects missing collisions
|
|
1136
|
+
* @default
|
|
1137
|
+
* @memberof Settings */
|
|
1138
|
+
const objectMaxSpeed = 1;
|
|
1139
|
+
|
|
1140
|
+
/** How much gravity to apply to objects along the Y axis, negative is down
|
|
1141
|
+
* @default
|
|
1142
|
+
* @memberof Settings */
|
|
1143
|
+
let gravity = 0;
|
|
726
1144
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
const FPS = 60, timeDelta = 1/FPS; // engine uses a fixed time step
|
|
730
|
-
const tileImage = new Image(); // everything uses the same tile sheet
|
|
731
|
-
|
|
732
|
-
// core engine variables
|
|
733
|
-
let mainCanvas, mainContext, overlayCanvas, overlayContext, mainCanvasSize=vec2(),
|
|
734
|
-
engineObjects=[], engineCollideObjects=[],
|
|
735
|
-
cameraPos=vec2(), cameraScale=max(defaultTileSize.x, defaultTileSize.y),
|
|
736
|
-
frame=0, time=0, realTime=0, paused=0, frameTimeLastMS=0, frameTimeBufferMS=0, debugFPS=0, gravity=0,
|
|
737
|
-
tileImageSize, tileImageSizeInverse, shrinkTilesX, shrinkTilesY, drawCount;
|
|
738
|
-
|
|
739
|
-
// call this function to start the engine
|
|
740
|
-
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
|
|
741
|
-
{
|
|
742
|
-
// init engine when tiles load
|
|
743
|
-
tileImage.onload = ()=>
|
|
744
|
-
{
|
|
745
|
-
// save tile image info
|
|
746
|
-
tileImageSizeInverse = vec2(1).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
747
|
-
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
748
|
-
shrinkTilesX = tileBleedShrinkFix/tileImageSize.x;
|
|
749
|
-
shrinkTilesY = tileBleedShrinkFix/tileImageSize.y;
|
|
1145
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1146
|
+
// Camera settings
|
|
750
1147
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
mainContext = mainCanvas.getContext('2d');
|
|
1148
|
+
/** Position of camera in world space
|
|
1149
|
+
* @type {Vector2}
|
|
1150
|
+
* @default
|
|
1151
|
+
* @memberof Settings */
|
|
1152
|
+
let cameraPos = vec2();
|
|
757
1153
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1154
|
+
/** Scale of camera in world space
|
|
1155
|
+
* @default
|
|
1156
|
+
* @memberof Settings */
|
|
1157
|
+
let cameraScale = max(tileSizeDefault.x, tileSizeDefault.y);
|
|
761
1158
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
overlayCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
765
|
-
overlayContext = overlayCanvas.getContext('2d');
|
|
1159
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1160
|
+
// WebGL settings
|
|
766
1161
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
1162
|
+
/** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
|
|
1163
|
+
* @default
|
|
1164
|
+
* @memberof Settings */
|
|
1165
|
+
const glEnable = 1;
|
|
770
1166
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1167
|
+
/** Fixes slow rendering in some browsers by not compositing the WebGL canvas
|
|
1168
|
+
* @default
|
|
1169
|
+
* @memberof Settings */
|
|
1170
|
+
let glOverlay = 1;
|
|
775
1171
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
frameTimeLastMS = frameTimeMS;
|
|
779
|
-
if (debug || showWatermark)
|
|
780
|
-
debugFPS = lerp(.05, 1e3/(frameTimeDeltaMS||1), debugFPS);
|
|
781
|
-
if (debug)
|
|
782
|
-
frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
|
|
783
|
-
realTime += frameTimeDeltaMS / 1e3;
|
|
784
|
-
frameTimeBufferMS = min(frameTimeBufferMS + !paused * frameTimeDeltaMS, 50); // clamp incase of slow framerate
|
|
1172
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1173
|
+
// Input settings
|
|
785
1174
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
debugUpdate();
|
|
791
|
-
gameUpdatePost();
|
|
792
|
-
inputUpdatePost();
|
|
793
|
-
}
|
|
794
|
-
else
|
|
795
|
-
{
|
|
796
|
-
// apply time delta smoothing, improves smoothness of framerate in some browsers
|
|
797
|
-
let deltaSmooth = 0;
|
|
798
|
-
if (frameTimeBufferMS < 0 && frameTimeBufferMS > -9)
|
|
799
|
-
{
|
|
800
|
-
// force an update each frame if time is close enough (not just a fast refresh rate)
|
|
801
|
-
deltaSmooth = frameTimeBufferMS;
|
|
802
|
-
frameTimeBufferMS = 0;
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
// update multiple frames if necessary in case of slow framerate
|
|
806
|
-
for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / FPS)
|
|
807
|
-
{
|
|
808
|
-
// update game and objects
|
|
809
|
-
inputUpdate();
|
|
810
|
-
gameUpdate();
|
|
811
|
-
engineUpdateObjects();
|
|
1175
|
+
/** Should gamepads be allowed
|
|
1176
|
+
* @default
|
|
1177
|
+
* @memberof Settings */
|
|
1178
|
+
const gamepadsEnable = 1;
|
|
812
1179
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
}
|
|
1180
|
+
/** If true, the dpad input is also routed to the left analog stick (for better accessability)
|
|
1181
|
+
* @default
|
|
1182
|
+
* @memberof Settings */
|
|
1183
|
+
const gamepadDirectionEmulateStick = 1;
|
|
818
1184
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1185
|
+
/** If true touch input is routed to mouse functions
|
|
1186
|
+
* @default
|
|
1187
|
+
* @memberof Settings */
|
|
1188
|
+
const inputTouchEnable = 1;
|
|
822
1189
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
mainCanvas.height = fixedHeight;
|
|
828
|
-
|
|
829
|
-
if (fixedFitToWindow)
|
|
830
|
-
{
|
|
831
|
-
// fit to window by adding space on top or bottom if necessary
|
|
832
|
-
const aspect = innerWidth / innerHeight;
|
|
833
|
-
const fixedAspect = fixedWidth / fixedHeight;
|
|
834
|
-
mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
|
|
835
|
-
mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
836
|
-
if (glCanvas)
|
|
837
|
-
{
|
|
838
|
-
glCanvas.style.width = mainCanvas.style.width;
|
|
839
|
-
glCanvas.style.height = mainCanvas.style.height;
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
else
|
|
844
|
-
{
|
|
845
|
-
// clear and set size to same as window
|
|
846
|
-
mainCanvas.width = min(innerWidth, maxWidth);
|
|
847
|
-
mainCanvas.height = min(innerHeight, maxHeight);
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
// save canvas size and clear overlay canvas
|
|
851
|
-
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
852
|
-
mainContext.imageSmoothingEnabled = !pixelated; // disable smoothing for pixel art
|
|
1190
|
+
/** If true the WASD keys are also routed to the direction keys (for better accessability)
|
|
1191
|
+
* @default
|
|
1192
|
+
* @memberof Settings */
|
|
1193
|
+
const inputWASDEmulateDirection = 1;
|
|
853
1194
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
gameRender();
|
|
857
|
-
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
858
|
-
for (const o of engineObjects)
|
|
859
|
-
o.destroyed || o.render();
|
|
860
|
-
gameRenderPost();
|
|
861
|
-
medalsRender();
|
|
862
|
-
debugRender();
|
|
863
|
-
glCopyToContext(mainContext);
|
|
1195
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1196
|
+
// Audio settings
|
|
864
1197
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
overlayContext.textBaseline = 'top';
|
|
870
|
-
overlayContext.font = '1em monospace';
|
|
871
|
-
overlayContext.fillStyle = '#000';
|
|
872
|
-
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
873
|
-
+ drawCount + ' / ' + engineObjects.length + ' / ' + debugFPS.toFixed(1);
|
|
874
|
-
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
875
|
-
overlayContext.fillStyle = '#fff';
|
|
876
|
-
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
877
|
-
drawCount = 0;
|
|
878
|
-
}
|
|
879
|
-
}
|
|
1198
|
+
/** Volume scale to apply to all sound, music and speech
|
|
1199
|
+
* @default
|
|
1200
|
+
* @memberof Settings */
|
|
1201
|
+
let soundVolume = .5;
|
|
880
1202
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1203
|
+
/** All audio code can be disabled and removed from build
|
|
1204
|
+
* @default
|
|
1205
|
+
* @memberof Settings */
|
|
1206
|
+
const soundEnable = 1;
|
|
884
1207
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
{
|
|
890
|
-
if (!o.destroyed)
|
|
891
|
-
{
|
|
892
|
-
o.update();
|
|
893
|
-
for (const child of o.children)
|
|
894
|
-
updateObject(child);
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
for (const o of engineObjects)
|
|
898
|
-
o.parent || updateObject(o);
|
|
1208
|
+
/** Default range where sound no longer plays
|
|
1209
|
+
* @default
|
|
1210
|
+
* @memberof Settings */
|
|
1211
|
+
const soundDefaultRange = 30;
|
|
899
1212
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1213
|
+
/** Default range percent to start tapering off sound (0-1)
|
|
1214
|
+
* @default
|
|
1215
|
+
* @memberof Settings */
|
|
1216
|
+
const soundDefaultTaper = .7;
|
|
903
1217
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1218
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1219
|
+
// Medals settings
|
|
1220
|
+
|
|
1221
|
+
/** How long to show medals for in seconds
|
|
1222
|
+
* @default
|
|
1223
|
+
* @memberof Settings */
|
|
1224
|
+
const medalDisplayTime = 5;
|
|
1225
|
+
|
|
1226
|
+
/** How quickly to slide on/off medals in seconds
|
|
1227
|
+
* @default
|
|
1228
|
+
* @memberof Settings */
|
|
1229
|
+
const medalDisplaySlideTime = .5;
|
|
1230
|
+
|
|
1231
|
+
/** Width of medal display
|
|
1232
|
+
* @default
|
|
1233
|
+
* @memberof Settings */
|
|
1234
|
+
const medalDisplayWidth = 640;
|
|
1235
|
+
|
|
1236
|
+
/** Height of medal display
|
|
1237
|
+
* @default
|
|
1238
|
+
* @memberof Settings */
|
|
1239
|
+
const medalDisplayHeight = 99;
|
|
1240
|
+
|
|
1241
|
+
/** Size of icon in medal display
|
|
1242
|
+
* @default
|
|
1243
|
+
* @memberof Settings */
|
|
1244
|
+
const medalDisplayIconSize = 80;
|
|
907
1245
|
/*
|
|
908
|
-
LittleJS Object
|
|
909
|
-
- Base object class used by the engine
|
|
910
|
-
- Automatically adds self to object list
|
|
911
|
-
- Will be updated and rendered each frame
|
|
912
|
-
- Renders as a sprite from a tilesheet by default
|
|
913
|
-
- Can have color and addtive color applied
|
|
914
|
-
- 2d Physics and collision system
|
|
915
|
-
- Sorted by renderOrder
|
|
916
|
-
- Objects can have children attached
|
|
917
|
-
- Parents are updated before children, and set child transform
|
|
918
|
-
- Call destroy() to get rid of objects
|
|
1246
|
+
LittleJS Object System
|
|
919
1247
|
*/
|
|
920
1248
|
|
|
921
1249
|
'use strict';
|
|
922
1250
|
|
|
1251
|
+
/**
|
|
1252
|
+
* LittleJS Object Base Object Class
|
|
1253
|
+
* <br> - Base object class used by the engine
|
|
1254
|
+
* <br> - Automatically adds self to object list
|
|
1255
|
+
* <br> - Will be updated and rendered each frame
|
|
1256
|
+
* <br> - Renders as a sprite from a tilesheet by default
|
|
1257
|
+
* <br> - Can have color and addtive color applied
|
|
1258
|
+
* <br> - 2d Physics and collision system
|
|
1259
|
+
* <br> - Sorted by renderOrder
|
|
1260
|
+
* <br> - Objects can have children attached
|
|
1261
|
+
* <br> - Parents are updated before children, and set child transform
|
|
1262
|
+
* <br> - Call destroy() to get rid of objects
|
|
1263
|
+
* @example
|
|
1264
|
+
* // create an engine object, normally you would first extend the class with your own
|
|
1265
|
+
* const pos = vec2(2,3);
|
|
1266
|
+
* const object = new EngineObject(pos);
|
|
1267
|
+
*/
|
|
923
1268
|
class EngineObject
|
|
924
1269
|
{
|
|
925
|
-
|
|
1270
|
+
/** Create an engine object and adds it to the list of objects
|
|
1271
|
+
* @param {Vector2} [position=new Vector2(0,0)] - World space position of the object
|
|
1272
|
+
* @param {Vector2} [size=objectDefaultSize] - World space size of the object
|
|
1273
|
+
* @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
|
|
1274
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
1275
|
+
* @param {Number} [angle=0] - Angle to rotate the object
|
|
1276
|
+
* @param {Color} [color] - Color to apply to tile when rendered
|
|
1277
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
1278
|
+
*/
|
|
1279
|
+
constructor(pos=vec2(), size=objectDefaultSize, tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
|
|
926
1280
|
{
|
|
927
1281
|
// set passed in params
|
|
928
1282
|
ASSERT(pos && pos.x != undefined && size.x != undefined); // ensure pos and size are vec2s
|
|
1283
|
+
|
|
1284
|
+
/** @property {Vector2} - World space position of the object */
|
|
929
1285
|
this.pos = pos.copy();
|
|
1286
|
+
/** @property {Vector2} - World space width and height of the object */
|
|
930
1287
|
this.size = size;
|
|
1288
|
+
/** @property {Vector2} - Size of object used for drawing, uses size if not set */
|
|
1289
|
+
this.drawSize;
|
|
1290
|
+
/** @property {Number} - Tile to use to render object, untextured if -1 */
|
|
931
1291
|
this.tileIndex = tileIndex;
|
|
1292
|
+
/** @property {Vector2} - Size of tile in source pixels */
|
|
932
1293
|
this.tileSize = tileSize;
|
|
1294
|
+
/** @property {Number} - Angle to rotate the object */
|
|
933
1295
|
this.angle = angle;
|
|
1296
|
+
/** @property {Color} - Color to apply when rendered */
|
|
934
1297
|
this.color = color;
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
this.
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1298
|
+
/** @property {Color} - Additive color to apply when rendered */
|
|
1299
|
+
this.additiveColor;
|
|
1300
|
+
|
|
1301
|
+
// set object defaults
|
|
1302
|
+
/** @property {Number} [mass=objectDefaultMass] - How heavy the object is */
|
|
1303
|
+
this.mass = objectDefaultMass;
|
|
1304
|
+
/** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
|
|
1305
|
+
this.damping = objectDefaultDamping;
|
|
1306
|
+
/** @property {Number} [angleDamping=objectDefaultAngleDamping] - How much to slow down rotation each frame (0-1) */
|
|
1307
|
+
this.angleDamping = objectDefaultAngleDamping;
|
|
1308
|
+
/** @property {Number} [elasticity=objectDefaultElasticity] - How bouncy the object is when colliding (0-1) */
|
|
1309
|
+
this.elasticity = objectDefaultElasticity;
|
|
1310
|
+
/** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
|
|
1311
|
+
this.friction = objectDefaultFriction;
|
|
1312
|
+
/** @property {Number} [gravityScale=1] - How much to scale gravity by for this object */
|
|
1313
|
+
this.gravityScale = 1;
|
|
1314
|
+
/** @property {Number} [renderOrder=0] - Objects are sorted by render order */
|
|
1315
|
+
this.renderOrder = renderOrder;
|
|
1316
|
+
|
|
1317
|
+
// init other internal object stuff
|
|
944
1318
|
this.spawnTime = time;
|
|
945
|
-
this.velocity = vec2(this.collideSolidObjects = this.
|
|
946
|
-
this.collideTiles =
|
|
1319
|
+
this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
|
|
1320
|
+
this.collideTiles = 1;
|
|
947
1321
|
this.children = [];
|
|
948
1322
|
|
|
949
1323
|
// add to list of objects
|
|
950
1324
|
engineObjects.push(this);
|
|
951
1325
|
}
|
|
952
1326
|
|
|
1327
|
+
/** Update the object transform and physics, called automatically by engine once each frame */
|
|
953
1328
|
update()
|
|
954
1329
|
{
|
|
955
1330
|
const parent = this.parent;
|
|
@@ -962,8 +1337,8 @@ class EngineObject
|
|
|
962
1337
|
}
|
|
963
1338
|
|
|
964
1339
|
// limit max speed to prevent missing collisions
|
|
965
|
-
this.velocity.x = clamp(this.velocity.x,
|
|
966
|
-
this.velocity.y = clamp(this.velocity.y,
|
|
1340
|
+
this.velocity.x = clamp(this.velocity.x, objectMaxSpeed, -objectMaxSpeed);
|
|
1341
|
+
this.velocity.y = clamp(this.velocity.y, objectMaxSpeed, -objectMaxSpeed);
|
|
967
1342
|
|
|
968
1343
|
// apply physics
|
|
969
1344
|
const oldPos = this.pos.copy();
|
|
@@ -1017,7 +1392,7 @@ class EngineObject
|
|
|
1017
1392
|
if (o.mass) // push away if not fixed
|
|
1018
1393
|
o.velocity = o.velocity.subtract(velocity);
|
|
1019
1394
|
|
|
1020
|
-
debugPhysics && debugAABB(this.pos,
|
|
1395
|
+
debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f00');
|
|
1021
1396
|
continue;
|
|
1022
1397
|
}
|
|
1023
1398
|
|
|
@@ -1083,7 +1458,7 @@ class EngineObject
|
|
|
1083
1458
|
this.velocity.x *= -this.elasticity;
|
|
1084
1459
|
}
|
|
1085
1460
|
|
|
1086
|
-
debugPhysics && debugAABB(this.pos,
|
|
1461
|
+
debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
|
|
1087
1462
|
}
|
|
1088
1463
|
}
|
|
1089
1464
|
if (this.collideTiles)
|
|
@@ -1091,8 +1466,6 @@ class EngineObject
|
|
|
1091
1466
|
// check collision against tiles
|
|
1092
1467
|
if (tileCollisionTest(this.pos, this.size, this))
|
|
1093
1468
|
{
|
|
1094
|
-
//debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
1095
|
-
|
|
1096
1469
|
// if already was stuck in collision, don't do anything
|
|
1097
1470
|
// this should not happen unless something starts in collision
|
|
1098
1471
|
if (!tileCollisionTest(oldPos, this.size, this))
|
|
@@ -1105,13 +1478,20 @@ class EngineObject
|
|
|
1105
1478
|
// set if landed on ground
|
|
1106
1479
|
this.groundObject = wasMovingDown;
|
|
1107
1480
|
|
|
1108
|
-
//
|
|
1109
|
-
this.pos.y = oldPos.y;
|
|
1481
|
+
// bounce velocity
|
|
1110
1482
|
this.velocity.y *= -this.elasticity;
|
|
1483
|
+
|
|
1484
|
+
// adjust next velocity to settle on ground
|
|
1485
|
+
const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
|
|
1486
|
+
if (o < 0 && o > -1 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
|
|
1487
|
+
this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
|
|
1488
|
+
|
|
1489
|
+
// move to previous position
|
|
1490
|
+
this.pos.y = oldPos.y;
|
|
1111
1491
|
}
|
|
1112
1492
|
if (isBlockedX)
|
|
1113
1493
|
{
|
|
1114
|
-
//
|
|
1494
|
+
// move to previous position and bounce
|
|
1115
1495
|
this.pos.x = oldPos.x;
|
|
1116
1496
|
this.velocity.x *= -this.elasticity;
|
|
1117
1497
|
}
|
|
@@ -1120,12 +1500,14 @@ class EngineObject
|
|
|
1120
1500
|
}
|
|
1121
1501
|
}
|
|
1122
1502
|
|
|
1503
|
+
/** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
|
|
1123
1504
|
render()
|
|
1124
1505
|
{
|
|
1125
1506
|
// default object render
|
|
1126
1507
|
drawTile(this.pos, this.drawSize || this.size, this.tileIndex, this.tileSize, this.color, this.angle, this.mirror, this.additiveColor);
|
|
1127
1508
|
}
|
|
1128
1509
|
|
|
1510
|
+
/** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
|
|
1129
1511
|
destroy()
|
|
1130
1512
|
{
|
|
1131
1513
|
if (this.destroyed)
|
|
@@ -1137,15 +1519,45 @@ class EngineObject
|
|
|
1137
1519
|
for (const child of this.children)
|
|
1138
1520
|
child.destroy(child.parent = 0);
|
|
1139
1521
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1522
|
+
|
|
1523
|
+
/** Called to check if a tile collision should be resolved
|
|
1524
|
+
* @param {Number} tileData - the value of the tile at the position
|
|
1525
|
+
* @param {Vector2} pos - tile where the collision occured
|
|
1526
|
+
* @return {Boolean} true if the collision should be resolved */
|
|
1527
|
+
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
1528
|
+
|
|
1529
|
+
/** Called to check if a tile raycast hit
|
|
1530
|
+
* @param {Number} tileData - the value of the tile at the position
|
|
1531
|
+
* @param {Vector2} pos - tile where the raycast is
|
|
1532
|
+
* @return {Boolean} true if the raycast should hit */
|
|
1533
|
+
collideWithTileRaycast(tileData, pos) { return tileData > 0; }
|
|
1534
|
+
|
|
1535
|
+
/** Called to check if a tile raycast hit
|
|
1536
|
+
* @param {EngineObject} object - the object to test against
|
|
1537
|
+
* @return {Boolean} true if the collision should be resolved
|
|
1538
|
+
*/
|
|
1142
1539
|
collideWithObject(o) { return 1; }
|
|
1540
|
+
|
|
1541
|
+
/** How long since the object was created
|
|
1542
|
+
* @return {Number} */
|
|
1143
1543
|
getAliveTime() { return time - this.spawnTime; }
|
|
1144
|
-
applyAcceleration(a) { ASSERT(!this.isFixed()); this.velocity = this.velocity.add(a); }
|
|
1145
|
-
applyForce(force) { this.applyAcceleration(force.scale(1/this.mass)); }
|
|
1146
|
-
isFixed() { return !this.mass; }
|
|
1147
|
-
getMirrorSign(s=1) { return this.mirror ? -s : s; }
|
|
1148
1544
|
|
|
1545
|
+
/** Apply acceleration to this object (adjust velocity, not affected by mass)
|
|
1546
|
+
* @param {Vector2} acceleration */
|
|
1547
|
+
applyAcceleration(a) { if (this.mass) this.velocity = this.velocity.add(a); }
|
|
1548
|
+
|
|
1549
|
+
/** Apply force to this object (adjust velocity, affected by mass)
|
|
1550
|
+
* @param {Vector2} force */
|
|
1551
|
+
applyForce(force) { this.applyAcceleration(force.scale(1/this.mass)); }
|
|
1552
|
+
|
|
1553
|
+
/** Get the direction of the mirror
|
|
1554
|
+
* @return {Number} -1 if this.mirror is true, or 1 if not mirrored */
|
|
1555
|
+
getMirrorSign() { return this.mirror ? -1 : 1; }
|
|
1556
|
+
|
|
1557
|
+
/** Attaches a child to this with a given local transform
|
|
1558
|
+
* @param {EngineObject} child
|
|
1559
|
+
* @param {Vector2} [localPos=new Vector2]
|
|
1560
|
+
* @param {Number} [localAngle=0] */
|
|
1149
1561
|
addChild(child, localPos=vec2(), localAngle=0)
|
|
1150
1562
|
{
|
|
1151
1563
|
ASSERT(!child.parent && !this.children.includes(child));
|
|
@@ -1154,6 +1566,9 @@ class EngineObject
|
|
|
1154
1566
|
child.localPos = localPos.copy();
|
|
1155
1567
|
child.localAngle = localAngle;
|
|
1156
1568
|
}
|
|
1569
|
+
|
|
1570
|
+
/** Removes a child from this one
|
|
1571
|
+
* @param {EngineObject} child */
|
|
1157
1572
|
removeChild(child)
|
|
1158
1573
|
{
|
|
1159
1574
|
ASSERT(child.parent == this && this.children.includes(child));
|
|
@@ -1161,7 +1576,11 @@ class EngineObject
|
|
|
1161
1576
|
child.parent = 0;
|
|
1162
1577
|
}
|
|
1163
1578
|
|
|
1164
|
-
|
|
1579
|
+
/** Set how this object collides
|
|
1580
|
+
* @param {boolean} [collideSolidObjects=0] - Does it collide with solid objects
|
|
1581
|
+
* @param {boolean} [isSolid=0] - Does it collide with and block other objects (expensive in large numbers)
|
|
1582
|
+
* @param {boolean} [collideTiles=1] - Does it collide with the tile collision */
|
|
1583
|
+
setCollision(collideSolidObjects=0, isSolid=0, collideTiles=1)
|
|
1165
1584
|
{
|
|
1166
1585
|
ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
|
|
1167
1586
|
|
|
@@ -1182,62 +1601,81 @@ class EngineObject
|
|
|
1182
1601
|
this.collideTiles = collideTiles;
|
|
1183
1602
|
}
|
|
1184
1603
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
function forEachObject(pos, size, callbackFunction, objects=engineObjects)
|
|
1195
|
-
{
|
|
1196
|
-
if (!pos)
|
|
1197
|
-
{
|
|
1198
|
-
// all objects
|
|
1199
|
-
for (const o of objects)
|
|
1200
|
-
callbackFunction(o);
|
|
1201
|
-
}
|
|
1202
|
-
else if (size.x != undefined)
|
|
1203
|
-
{
|
|
1204
|
-
// aabb test
|
|
1205
|
-
for (const o of objects)
|
|
1206
|
-
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
1207
|
-
}
|
|
1208
|
-
else
|
|
1209
|
-
{
|
|
1210
|
-
// circle test
|
|
1211
|
-
const sizeSquared = size*size;
|
|
1212
|
-
for (const o of objects)
|
|
1213
|
-
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
/*
|
|
1217
|
-
LittleJS Drawing System
|
|
1218
|
-
|
|
1219
|
-
- Super fast tile sheet rendering
|
|
1220
|
-
- Utility functions for webgl
|
|
1221
|
-
*/
|
|
1604
|
+
/**
|
|
1605
|
+
* LittleJS Drawing System
|
|
1606
|
+
* <br> - Hybrid with both Canvas2D and WebGL available
|
|
1607
|
+
* <br> - Super fast tile sheet rendering with WebGL
|
|
1608
|
+
* <br> - Can apply rotation, mirror, color and additive color
|
|
1609
|
+
* <br> - Many useful utility functions
|
|
1610
|
+
* @namespace Draw
|
|
1611
|
+
*/
|
|
1222
1612
|
|
|
1223
1613
|
'use strict';
|
|
1224
1614
|
|
|
1225
|
-
|
|
1615
|
+
/** Tile sheet for batch rendering system
|
|
1616
|
+
* @type {Image}
|
|
1617
|
+
* @memberof Draw */
|
|
1618
|
+
const tileImage = new Image();
|
|
1619
|
+
|
|
1620
|
+
/** The primary 2D canvas visible to the user
|
|
1621
|
+
* @type {HTMLCanvasElement}
|
|
1622
|
+
* @memberof Draw */
|
|
1623
|
+
let mainCanvas;
|
|
1624
|
+
|
|
1625
|
+
/** 2d context for mainCanvas
|
|
1626
|
+
* @type {CanvasRenderingContext2D}
|
|
1627
|
+
* @memberof Draw */
|
|
1628
|
+
let mainContext;
|
|
1629
|
+
|
|
1630
|
+
/** A canvas that appears on top of everything the same size as mainCanvas
|
|
1631
|
+
* @type {HTMLCanvasElement}
|
|
1632
|
+
* @memberof Draw */
|
|
1633
|
+
let overlayCanvas;
|
|
1634
|
+
|
|
1635
|
+
/** 2d context for overlayCanvas
|
|
1636
|
+
* @type {CanvasRenderingContext2D}
|
|
1637
|
+
* @memberof Draw */
|
|
1638
|
+
let overlayContext;
|
|
1639
|
+
|
|
1640
|
+
/** The size of the main canvas (and other secondary canvases)
|
|
1641
|
+
* @type {Vector2}
|
|
1642
|
+
* @memberof Draw */
|
|
1643
|
+
let mainCanvasSize = vec2();
|
|
1644
|
+
|
|
1645
|
+
/** Convert from screen to world space coordinates
|
|
1646
|
+
* @param {Vector2} screenPos
|
|
1647
|
+
* @return {Vector2}
|
|
1648
|
+
* @memberof Draw */
|
|
1226
1649
|
const screenToWorld = (screenPos)=>
|
|
1227
1650
|
screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
|
|
1651
|
+
|
|
1652
|
+
/** Convert from world to screen space coordinates
|
|
1653
|
+
* @param {Vector2} worldPos
|
|
1654
|
+
* @return {Vector2}
|
|
1655
|
+
* @memberof Draw */
|
|
1228
1656
|
const worldToScreen = (worldPos)=>
|
|
1229
1657
|
worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
|
|
1230
1658
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1659
|
+
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
1660
|
+
* @param {Vector2} pos - Center of the tile in world space
|
|
1661
|
+
* @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space, width and height
|
|
1662
|
+
* @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
|
|
1663
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
|
|
1664
|
+
* @param {Color} [color=new Color(1,1,1)] - Color to modulate with
|
|
1665
|
+
* @param {Number} [angle=0] - Angle to rotate by
|
|
1666
|
+
* @param {Boolean} [mirror=0] - If true image is flipped along the Y axis
|
|
1667
|
+
* @param {Color} [additiveColor=new Color(0,0,0,0)] - Additive color to be applied
|
|
1668
|
+
* @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
1669
|
+
* @memberof Draw */
|
|
1670
|
+
function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle=0, mirror,
|
|
1671
|
+
additiveColor=new Color(0,0,0,0), useWebGL=glEnable)
|
|
1234
1672
|
{
|
|
1235
1673
|
showWatermark && ++drawCount;
|
|
1236
|
-
if (glEnable)
|
|
1674
|
+
if (glEnable && useWebGL)
|
|
1237
1675
|
{
|
|
1238
|
-
if (tileIndex < 0)
|
|
1676
|
+
if (tileIndex < 0 || !tileImage.width)
|
|
1239
1677
|
{
|
|
1240
|
-
// if negative tile index, force untextured
|
|
1678
|
+
// if negative tile index or image not found, force untextured
|
|
1241
1679
|
glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
|
|
1242
1680
|
}
|
|
1243
1681
|
else
|
|
@@ -1284,33 +1722,68 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=defaultTileSize, col
|
|
|
1284
1722
|
}
|
|
1285
1723
|
}
|
|
1286
1724
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1725
|
+
/** Draw colored rect centered on pos
|
|
1726
|
+
* @param {Vector2} pos
|
|
1727
|
+
* @param {Vector2} [size=new Vector2(1,1)]
|
|
1728
|
+
* @param {Color} [color=new Color(1,1,1)]
|
|
1729
|
+
* @param {Number} [angle=0]
|
|
1730
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1731
|
+
* @memberof Draw */
|
|
1732
|
+
function drawRect(pos, size, color, angle, useWebGL)
|
|
1289
1733
|
{
|
|
1290
|
-
drawTile(pos, size, -1,
|
|
1734
|
+
drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
|
|
1291
1735
|
}
|
|
1292
1736
|
|
|
1293
|
-
|
|
1294
|
-
|
|
1737
|
+
/** Draw textured tile centered on pos in screen space
|
|
1738
|
+
* @param {Vector2} pos - Center of the tile
|
|
1739
|
+
* @param {Vector2} [size=new Vector2(1,1)] - Size of the tile
|
|
1740
|
+
* @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
|
|
1741
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
|
|
1742
|
+
* @param {Color} [color=new Color]
|
|
1743
|
+
* @param {Number} [angle=0]
|
|
1744
|
+
* @param {Boolean} [mirror=0]
|
|
1745
|
+
* @param {Color} [additiveColor=new Color(0,0,0,0)]
|
|
1746
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1747
|
+
* @memberof Draw */
|
|
1748
|
+
function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL)
|
|
1295
1749
|
{
|
|
1296
|
-
drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor);
|
|
1750
|
+
drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL);
|
|
1297
1751
|
}
|
|
1298
1752
|
|
|
1299
|
-
|
|
1300
|
-
|
|
1753
|
+
/** Draw colored rectangle in screen space
|
|
1754
|
+
* @param {Vector2} pos
|
|
1755
|
+
* @param {Vector2} [size=new Vector2(1,1)]
|
|
1756
|
+
* @param {Color} [color=new Color(1,1,1)]
|
|
1757
|
+
* @param {Number} [angle=0]
|
|
1758
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1759
|
+
* @memberof Draw */
|
|
1760
|
+
function drawRectScreenSpace(pos, size, color, angle, useWebGL)
|
|
1301
1761
|
{
|
|
1302
|
-
|
|
1762
|
+
drawTileSrceenSpace(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
|
|
1303
1763
|
}
|
|
1304
1764
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1765
|
+
/** Draw colored line between two points
|
|
1766
|
+
* @param {Vector2} posA
|
|
1767
|
+
* @param {Vector2} posB
|
|
1768
|
+
* @param {Number} [thickness=.1]
|
|
1769
|
+
* @param {Color} [color=new Color(1,1,1)]
|
|
1770
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1771
|
+
* @memberof Draw */
|
|
1772
|
+
function drawLine(posA, posB, thickness=.1, color, useWebGL)
|
|
1307
1773
|
{
|
|
1308
1774
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
1309
1775
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
1310
|
-
drawRect(posA.add(halfDelta), size, color, halfDelta.angle());
|
|
1776
|
+
drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), 0, 0, useWebGL);
|
|
1311
1777
|
}
|
|
1312
1778
|
|
|
1313
|
-
|
|
1779
|
+
/** Draw directly to a 2d canvas context in world space
|
|
1780
|
+
* @param {Vector2} pos
|
|
1781
|
+
* @param {Vector2} size
|
|
1782
|
+
* @param {Number} angle
|
|
1783
|
+
* @param {Boolean} mirror
|
|
1784
|
+
* @param {Function} drawFunction
|
|
1785
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
1786
|
+
* @memberof Draw */
|
|
1314
1787
|
function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainContext)
|
|
1315
1788
|
{
|
|
1316
1789
|
// create canvas transform from world space to screen space
|
|
@@ -1324,8 +1797,16 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
|
|
|
1324
1797
|
context.restore();
|
|
1325
1798
|
}
|
|
1326
1799
|
|
|
1327
|
-
|
|
1328
|
-
|
|
1800
|
+
/** Draw text on overlay canvas in world space
|
|
1801
|
+
* @param {String} text
|
|
1802
|
+
* @param {Vector2} pos
|
|
1803
|
+
* @param {Number} [size=1]
|
|
1804
|
+
* @param {Color} [color=new Color(1,1,1)]
|
|
1805
|
+
* @param {Number} [lineWidth=0]
|
|
1806
|
+
* @param {Color} [lineColor=new Color(0,0,0)]
|
|
1807
|
+
* @param {String} [textAlign='center']
|
|
1808
|
+
* @memberof Draw */
|
|
1809
|
+
function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault)
|
|
1329
1810
|
{
|
|
1330
1811
|
pos = worldToScreen(pos);
|
|
1331
1812
|
overlayContext.font = size*cameraScale + 'px '+ font;
|
|
@@ -1341,16 +1822,28 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
|
|
|
1341
1822
|
overlayContext.fillText(text, pos.x, pos.y);
|
|
1342
1823
|
}
|
|
1343
1824
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1825
|
+
/** Enable normal or additive blend mode
|
|
1826
|
+
* @param {Boolean} [additive=0]
|
|
1827
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1828
|
+
* @memberof Draw */
|
|
1829
|
+
function setBlendMode(additive, useWebGL=glEnable)
|
|
1346
1830
|
{
|
|
1347
|
-
glEnable
|
|
1831
|
+
if (glEnable && useWebGL)
|
|
1832
|
+
glSetBlendMode(additive);
|
|
1833
|
+
else
|
|
1834
|
+
mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
1348
1835
|
}
|
|
1349
1836
|
|
|
1350
1837
|
///////////////////////////////////////////////////////////////////////////////
|
|
1351
|
-
//
|
|
1838
|
+
// Fullscreen mode
|
|
1352
1839
|
|
|
1840
|
+
/** Returns true if fullscreen mode is active
|
|
1841
|
+
* @return {Boolean}
|
|
1842
|
+
* @memberof Draw */
|
|
1353
1843
|
const isFullscreen =()=> document.fullscreenElement;
|
|
1844
|
+
|
|
1845
|
+
/** Toggle fullsceen mode
|
|
1846
|
+
* @memberof Draw */
|
|
1354
1847
|
function toggleFullscreen()
|
|
1355
1848
|
{
|
|
1356
1849
|
if (isFullscreen())
|
|
@@ -1368,39 +1861,107 @@ function toggleFullscreen()
|
|
|
1368
1861
|
document.body.mozRequestFullScreen();
|
|
1369
1862
|
}
|
|
1370
1863
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1864
|
+
/**
|
|
1865
|
+
* LittleJS Input System
|
|
1866
|
+
* <br> - Tracks key down, pressed, and released
|
|
1867
|
+
* <br> - Also tracks mouse buttons, position, and wheel
|
|
1868
|
+
* <br> - Supports multiple gamepads
|
|
1869
|
+
* @namespace Input
|
|
1870
|
+
*/
|
|
1377
1871
|
|
|
1378
1872
|
'use strict';
|
|
1379
1873
|
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1874
|
+
/** Returns true if device key is down
|
|
1875
|
+
* @param {Number} key
|
|
1876
|
+
* @param {Number} [device=0]
|
|
1877
|
+
* @return {Boolean}
|
|
1878
|
+
* @memberof Input */
|
|
1879
|
+
const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1 ? 1 : 0;
|
|
1880
|
+
|
|
1881
|
+
/** Returns true if device key was pressed this frame
|
|
1882
|
+
* @param {Number} key
|
|
1883
|
+
* @param {Number} [device=0]
|
|
1884
|
+
* @return {Boolean}
|
|
1885
|
+
* @memberof Input */
|
|
1886
|
+
const keyWasPressed = (key, device=0)=> inputData[device] && inputData[device][key] & 2 ? 1 : 0;
|
|
1887
|
+
|
|
1888
|
+
/** Returns true if device key was released this frame
|
|
1889
|
+
* @param {Number} key
|
|
1890
|
+
* @param {Number} [device=0]
|
|
1891
|
+
* @return {Boolean}
|
|
1892
|
+
* @memberof Input */
|
|
1384
1893
|
const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][key] & 4 ? 1 : 0;
|
|
1385
|
-
const clearInput = ()=> inputData[0] = [];
|
|
1386
1894
|
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
const
|
|
1895
|
+
/** Clears all input
|
|
1896
|
+
* @memberof Input */
|
|
1897
|
+
const clearInput = ()=> inputData[0] = [];
|
|
1898
|
+
|
|
1899
|
+
/** Returns true if mouse button is down
|
|
1900
|
+
* @param {Number} button
|
|
1901
|
+
* @return {Boolean}
|
|
1902
|
+
* @memberof Input */
|
|
1903
|
+
const mouseIsDown = keyIsDown;
|
|
1904
|
+
|
|
1905
|
+
/** Returns true if mouse button was pressed
|
|
1906
|
+
* @param {Number} button
|
|
1907
|
+
* @return {Boolean}
|
|
1908
|
+
* @memberof Input */
|
|
1909
|
+
const mouseWasPressed = keyWasPressed;
|
|
1910
|
+
|
|
1911
|
+
/** Returns true if mouse button was released
|
|
1912
|
+
* @param {Number} button
|
|
1913
|
+
* @return {Boolean}
|
|
1914
|
+
* @memberof Input */
|
|
1390
1915
|
const mouseWasReleased = keyWasReleased;
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1916
|
+
|
|
1917
|
+
/** Mouse pos in world space
|
|
1918
|
+
* @type {Vector2}
|
|
1919
|
+
* @memberof Input */
|
|
1920
|
+
let mousePos = vec2();
|
|
1921
|
+
|
|
1922
|
+
/** Mouse pos in screen space
|
|
1923
|
+
* @type {Vector2}
|
|
1924
|
+
* @memberof Input */
|
|
1925
|
+
let mousePosScreen = vec2();
|
|
1926
|
+
|
|
1927
|
+
/** Mouse wheel delta this frame
|
|
1928
|
+
* @memberof Input */
|
|
1929
|
+
let mouseWheel = 0;
|
|
1930
|
+
|
|
1931
|
+
/** Returns true if user is using gamepad (has more recently pressed a gamepad button)
|
|
1932
|
+
* @memberof Input */
|
|
1933
|
+
let isUsingGamepad = 0;
|
|
1934
|
+
|
|
1935
|
+
/** Returns true if gamepad button is down
|
|
1936
|
+
* @param {Number} button
|
|
1937
|
+
* @param {Number} [gamepad=0]
|
|
1938
|
+
* @return {Boolean}
|
|
1939
|
+
* @memberof Input */
|
|
1940
|
+
const gamepadIsDown = (button, gamepad=0)=> keyIsDown(button, gamepad+1);
|
|
1941
|
+
|
|
1942
|
+
/** Returns true if gamepad button was pressed
|
|
1943
|
+
* @param {Number} button
|
|
1944
|
+
* @param {Number} [gamepad=0]
|
|
1945
|
+
* @return {Boolean}
|
|
1946
|
+
* @memberof Input */
|
|
1947
|
+
const gamepadWasPressed = (button, gamepad=0)=> keyWasPressed(button, gamepad+1);
|
|
1948
|
+
|
|
1949
|
+
/** Returns true if gamepad button was released
|
|
1950
|
+
* @param {Number} button
|
|
1951
|
+
* @param {Number} [gamepad=0]
|
|
1952
|
+
* @return {Boolean}
|
|
1953
|
+
* @memberof Input */
|
|
1399
1954
|
const gamepadWasReleased = (button, gamepad=0)=> keyWasReleased(button, gamepad+1);
|
|
1400
|
-
|
|
1955
|
+
|
|
1956
|
+
/** Returns gamepad stick value
|
|
1957
|
+
* @param {Number} stick
|
|
1958
|
+
* @param {Number} [gamepad=0]
|
|
1959
|
+
* @return {Vector2}
|
|
1960
|
+
* @memberof Input */
|
|
1961
|
+
const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2();
|
|
1401
1962
|
|
|
1402
1963
|
///////////////////////////////////////////////////////////////////////////////
|
|
1403
|
-
//
|
|
1964
|
+
// Input update called by engine
|
|
1404
1965
|
|
|
1405
1966
|
const inputData = [[]];
|
|
1406
1967
|
|
|
@@ -1426,23 +1987,25 @@ function inputUpdatePost()
|
|
|
1426
1987
|
}
|
|
1427
1988
|
|
|
1428
1989
|
///////////////////////////////////////////////////////////////////////////////
|
|
1429
|
-
//
|
|
1990
|
+
// Keyboard event handlers
|
|
1991
|
+
|
|
1430
1992
|
onkeydown = e=>
|
|
1431
1993
|
{
|
|
1432
1994
|
if (debug && e.target != document.body) return;
|
|
1433
|
-
e.repeat || (inputData[
|
|
1434
|
-
|
|
1995
|
+
e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
|
|
1996
|
+
debug || e.preventDefault();
|
|
1435
1997
|
}
|
|
1436
1998
|
onkeyup = e=>
|
|
1437
1999
|
{
|
|
1438
2000
|
if (debug && e.target != document.body) return;
|
|
1439
2001
|
inputData[0][remapKeyCode(e.keyCode)] = 4;
|
|
1440
2002
|
}
|
|
1441
|
-
const remapKeyCode = c=>
|
|
2003
|
+
const remapKeyCode = c=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
1442
2004
|
|
|
1443
2005
|
///////////////////////////////////////////////////////////////////////////////
|
|
1444
|
-
//
|
|
1445
|
-
|
|
2006
|
+
// Mouse event handlers
|
|
2007
|
+
|
|
2008
|
+
onmousedown = e=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
1446
2009
|
onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
1447
2010
|
onmousemove = e=>
|
|
1448
2011
|
{
|
|
@@ -1456,7 +2019,7 @@ onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
|
1456
2019
|
oncontextmenu = e=> !1; // prevent right click menu
|
|
1457
2020
|
|
|
1458
2021
|
///////////////////////////////////////////////////////////////////////////////
|
|
1459
|
-
//
|
|
2022
|
+
// Gamepad input
|
|
1460
2023
|
|
|
1461
2024
|
const stickData = [];
|
|
1462
2025
|
function gamepadsUpdate()
|
|
@@ -1490,10 +2053,10 @@ function gamepadsUpdate()
|
|
|
1490
2053
|
{
|
|
1491
2054
|
const button = gamepad.buttons[j];
|
|
1492
2055
|
data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
|
|
1493
|
-
|
|
2056
|
+
isUsingGamepad |= !i && button.pressed;
|
|
1494
2057
|
}
|
|
1495
2058
|
|
|
1496
|
-
if (
|
|
2059
|
+
if (gamepadDirectionEmulateStick)
|
|
1497
2060
|
{
|
|
1498
2061
|
// copy dpad to left analog stick when pressed
|
|
1499
2062
|
const dpad = vec2(gamepadIsDown(15,i) - gamepadIsDown(14,i), gamepadIsDown(12,i) - gamepadIsDown(13,i));
|
|
@@ -1505,12 +2068,16 @@ function gamepadsUpdate()
|
|
|
1505
2068
|
}
|
|
1506
2069
|
|
|
1507
2070
|
///////////////////////////////////////////////////////////////////////////////
|
|
1508
|
-
//
|
|
1509
|
-
|
|
2071
|
+
// Touch input
|
|
2072
|
+
|
|
2073
|
+
/** True if a touch device has been detected
|
|
2074
|
+
* @const {boolean}
|
|
2075
|
+
* @memberof Input */
|
|
2076
|
+
const isTouchDevice = inputTouchEnable && window.ontouchstart !== undefined;
|
|
1510
2077
|
if (isTouchDevice)
|
|
1511
2078
|
{
|
|
1512
2079
|
// handle all touch events the same way
|
|
1513
|
-
let wasTouching;
|
|
2080
|
+
let wasTouching, hadTouchInput;
|
|
1514
2081
|
ontouchstart = ontouchmove = ontouchend = e=>
|
|
1515
2082
|
{
|
|
1516
2083
|
e.button = 0; // all touches are left click
|
|
@@ -1519,7 +2086,7 @@ if (isTouchDevice)
|
|
|
1519
2086
|
const touching = e.touches.length;
|
|
1520
2087
|
if (touching)
|
|
1521
2088
|
{
|
|
1522
|
-
|
|
2089
|
+
hadTouchInput || zzfx(0, hadTouchInput=1) ; // fix mobile audio, force it to play a sound the first time
|
|
1523
2090
|
|
|
1524
2091
|
// set event pos and pass it along
|
|
1525
2092
|
e.x = e.touches[0].clientX;
|
|
@@ -1536,27 +2103,45 @@ if (isTouchDevice)
|
|
|
1536
2103
|
return !e.cancelable;
|
|
1537
2104
|
}
|
|
1538
2105
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
2106
|
+
/**
|
|
2107
|
+
* LittleJS Audio System
|
|
2108
|
+
* <br> - ZzFX Sound Effects and ZzFXM Music
|
|
2109
|
+
* <br> - Caches sounds and music for fast playback
|
|
2110
|
+
* <br> - Can attenuate and apply stereo panning to sounds
|
|
2111
|
+
* <br> - Ability to play mp3, ogg, and wave files
|
|
2112
|
+
* <br> - Speech synthesis wrapper functions
|
|
2113
|
+
* @namespace Audio
|
|
2114
|
+
*/
|
|
1547
2115
|
|
|
1548
2116
|
'use strict';
|
|
1549
2117
|
|
|
2118
|
+
/**
|
|
2119
|
+
* Sound Object - Stores a zzfx sound for later use and can be played positionally
|
|
2120
|
+
* @example
|
|
2121
|
+
* // create a sound
|
|
2122
|
+
* const sound_example = new Sound([.5,.5]);
|
|
2123
|
+
*
|
|
2124
|
+
* // play the sound
|
|
2125
|
+
* sound_example.play();
|
|
2126
|
+
*/
|
|
1550
2127
|
class Sound
|
|
1551
2128
|
{
|
|
1552
|
-
|
|
2129
|
+
/** Create a sound object and cache the zzfx samples for later use
|
|
2130
|
+
* @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
|
|
2131
|
+
* @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
|
|
2132
|
+
* @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
|
|
2133
|
+
*/
|
|
2134
|
+
constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
|
|
1553
2135
|
{
|
|
1554
2136
|
if (!soundEnable) return;
|
|
1555
2137
|
|
|
2138
|
+
/** @property {Number} - World space max range of sound, will not play if camera is farther away */
|
|
1556
2139
|
this.range = range;
|
|
2140
|
+
|
|
2141
|
+
/** @property {Number} - At what percentage of range should it start tapering off */
|
|
1557
2142
|
this.taper = taper;
|
|
1558
2143
|
|
|
1559
|
-
// get randomness from sound
|
|
2144
|
+
// get randomness from sound parameters
|
|
1560
2145
|
this.randomness = zzfxSound[1] || 0;
|
|
1561
2146
|
zzfxSound[1] = 0;
|
|
1562
2147
|
|
|
@@ -1564,7 +2149,14 @@ class Sound
|
|
|
1564
2149
|
this.cachedSamples = zzfxG(...zzfxSound);
|
|
1565
2150
|
}
|
|
1566
2151
|
|
|
1567
|
-
|
|
2152
|
+
/** Play the sound
|
|
2153
|
+
* @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
|
|
2154
|
+
* @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
|
|
2155
|
+
* @param {Number} [pitch=1] - How much to scale pitch by (also adjusted by this.randomness)
|
|
2156
|
+
* @param {Number} [randomnessScale=1] - How much to scale randomness
|
|
2157
|
+
* @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
|
|
2158
|
+
*/
|
|
2159
|
+
play(pos, volume=1, pitch=1, randomnessScale=1)
|
|
1568
2160
|
{
|
|
1569
2161
|
if (!soundEnable) return;
|
|
1570
2162
|
|
|
@@ -1580,7 +2172,7 @@ class Sound
|
|
|
1580
2172
|
return; // out of range
|
|
1581
2173
|
|
|
1582
2174
|
// attenuate volume by distance
|
|
1583
|
-
|
|
2175
|
+
volume *= percent(lengthSquared**.5, range*this.taper, range);
|
|
1584
2176
|
}
|
|
1585
2177
|
|
|
1586
2178
|
// get pan from screen space coords
|
|
@@ -1588,13 +2180,57 @@ class Sound
|
|
|
1588
2180
|
}
|
|
1589
2181
|
|
|
1590
2182
|
// play the sound
|
|
1591
|
-
const playbackRate =
|
|
1592
|
-
return playSamples([this.cachedSamples],
|
|
2183
|
+
const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
|
|
2184
|
+
return playSamples([this.cachedSamples], volume, playbackRate, pan);
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
/** Play the sound as a note with a semitone offset
|
|
2188
|
+
* @param {Number} semitoneOffset - How many semitones to offset pitch
|
|
2189
|
+
* @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
|
|
2190
|
+
* @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
|
|
2191
|
+
* @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
|
|
2192
|
+
*/
|
|
2193
|
+
playNote(semitoneOffset, pos, volume=1)
|
|
2194
|
+
{
|
|
2195
|
+
if (!soundEnable) return;
|
|
2196
|
+
|
|
2197
|
+
return this.play(pos, volume, 2**(semitoneOffset/12), 0);
|
|
1593
2198
|
}
|
|
1594
2199
|
}
|
|
1595
2200
|
|
|
2201
|
+
/**
|
|
2202
|
+
* Music Object - Stores a zzfx music track for later use
|
|
2203
|
+
* @example
|
|
2204
|
+
* // create some music
|
|
2205
|
+
* const music_example = new Music(
|
|
2206
|
+
* [
|
|
2207
|
+
* [ // instruments
|
|
2208
|
+
* [,0,400] // simple note
|
|
2209
|
+
* ],
|
|
2210
|
+
* [ // patterns
|
|
2211
|
+
* [ // pattern 1
|
|
2212
|
+
* [ // channel 0
|
|
2213
|
+
* 0, -1, // instrument 0, left speaker
|
|
2214
|
+
* 1, 0, 9, 1 // channel notes
|
|
2215
|
+
* ],
|
|
2216
|
+
* [ // channel 1
|
|
2217
|
+
* 0, 1, // instrument 1, right speaker
|
|
2218
|
+
* 0, 12, 17, -1 // channel notes
|
|
2219
|
+
* ]
|
|
2220
|
+
* ],
|
|
2221
|
+
* ],
|
|
2222
|
+
* [0, 0, 0, 0], // sequence, play pattern 0 four times
|
|
2223
|
+
* 90 // BPM
|
|
2224
|
+
* ]);
|
|
2225
|
+
*
|
|
2226
|
+
* // play the music
|
|
2227
|
+
* music_example.play();
|
|
2228
|
+
*/
|
|
1596
2229
|
class Music
|
|
1597
2230
|
{
|
|
2231
|
+
/** Create a music object and cache the zzfx music samples for later use
|
|
2232
|
+
* @param {Array} zzfxMusic - Array of zzfx music parameters
|
|
2233
|
+
*/
|
|
1598
2234
|
constructor(zzfxMusic)
|
|
1599
2235
|
{
|
|
1600
2236
|
if (!soundEnable) return;
|
|
@@ -1602,29 +2238,44 @@ class Music
|
|
|
1602
2238
|
this.cachedSamples = zzfxM(...zzfxMusic);
|
|
1603
2239
|
}
|
|
1604
2240
|
|
|
1605
|
-
|
|
2241
|
+
/** Play the music
|
|
2242
|
+
* @param {Number} [volume=1] - How much to scale volume by
|
|
2243
|
+
* @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
|
|
2244
|
+
* @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
|
|
2245
|
+
*/
|
|
2246
|
+
play(volume = 1, loop = 1)
|
|
1606
2247
|
{
|
|
1607
2248
|
if (!soundEnable) return;
|
|
1608
2249
|
|
|
1609
|
-
return playSamples(this.cachedSamples,
|
|
2250
|
+
return playSamples(this.cachedSamples, volume, 1, 0, loop);
|
|
1610
2251
|
}
|
|
1611
2252
|
}
|
|
1612
2253
|
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
2254
|
+
/** Play an mp3 or wav audio from a local file or url
|
|
2255
|
+
* @param {String} url - Location of sound file to play
|
|
2256
|
+
* @param {Number} [volume=1] - How much to scale volume by
|
|
2257
|
+
* @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
|
|
2258
|
+
* @return {HTMLAudioElement} - The audio element for this sound
|
|
2259
|
+
* @memberof Audio */
|
|
2260
|
+
function playAudioFile(url, volume=1, loop=1)
|
|
1617
2261
|
{
|
|
1618
2262
|
if (!soundEnable) return;
|
|
1619
2263
|
|
|
1620
2264
|
const audio = new Audio(url);
|
|
1621
|
-
audio.volume =
|
|
2265
|
+
audio.volume = soundVolume * volume;
|
|
1622
2266
|
audio.loop = loop;
|
|
1623
2267
|
audio.play();
|
|
1624
2268
|
return audio;
|
|
1625
2269
|
}
|
|
1626
2270
|
|
|
1627
|
-
|
|
2271
|
+
/** Speak text with passed in settings
|
|
2272
|
+
* @param {String} text - The text to speak
|
|
2273
|
+
* @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
2274
|
+
* @param {Number} [volume=1] - How much to scale volume by
|
|
2275
|
+
* @param {Number} [rate=1] - How quickly to speak
|
|
2276
|
+
* @param {Number} [pitch=1] - How much to change the pitch by
|
|
2277
|
+
* @return {SpeechSynthesisUtterance} - The utterance that was spoken
|
|
2278
|
+
* @memberof Audio */
|
|
1628
2279
|
function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
1629
2280
|
{
|
|
1630
2281
|
if (!soundEnable || !speechSynthesis) return;
|
|
@@ -1636,22 +2287,39 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
1636
2287
|
// build utterance and speak
|
|
1637
2288
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
1638
2289
|
utterance.lang = language;
|
|
1639
|
-
utterance.volume = volume*
|
|
2290
|
+
utterance.volume = 2*volume*soundVolume;
|
|
1640
2291
|
utterance.rate = rate;
|
|
1641
2292
|
utterance.pitch = pitch;
|
|
1642
2293
|
speechSynthesis.speak(utterance);
|
|
1643
2294
|
return utterance;
|
|
1644
2295
|
}
|
|
1645
2296
|
|
|
1646
|
-
|
|
2297
|
+
/** Stop all queued speech
|
|
2298
|
+
* @memberof Audio */
|
|
1647
2299
|
const stopSpeech = ()=> speechSynthesis && speechSynthesis.cancel();
|
|
1648
2300
|
|
|
1649
|
-
|
|
2301
|
+
/** Get frequency of a note on a musical scale
|
|
2302
|
+
* @param {Number} semitoneOffset - How many semitones away from the root note
|
|
2303
|
+
* @param {Number} [rootNoteFrequency=220] - Frequency at semitone offset 0
|
|
2304
|
+
* @return {Number} - The frequency of the note
|
|
2305
|
+
* @memberof Audio */
|
|
2306
|
+
const getNoteFrequency = (semitoneOffset, rootFrequency=220)=> rootFrequency * 2**(semitoneOffset/12);
|
|
1650
2307
|
|
|
1651
|
-
|
|
2308
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1652
2309
|
|
|
1653
|
-
|
|
1654
|
-
|
|
2310
|
+
/** Audio context used by the engine
|
|
2311
|
+
* @memberof Audio */
|
|
2312
|
+
let audioContext;
|
|
2313
|
+
|
|
2314
|
+
/** Play cached audio samples with given settings
|
|
2315
|
+
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
|
|
2316
|
+
* @param {Number} [volume=1] - How much to scale volume by
|
|
2317
|
+
* @param {Number} [rate=1] - The playback rate to use
|
|
2318
|
+
* @param {Number} [pan=0] - How much to apply stereo panning
|
|
2319
|
+
* @param {Boolean} [loop=0] - True if the sound should loop when it reaches the end
|
|
2320
|
+
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
2321
|
+
* @memberof Audio */
|
|
2322
|
+
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
1655
2323
|
{
|
|
1656
2324
|
if (!soundEnable) return;
|
|
1657
2325
|
|
|
@@ -1666,13 +2334,13 @@ function playSamples(sampleChannels, volume=1, playbackRate=1, pan=0, loop=0)
|
|
|
1666
2334
|
// copy samples to buffer and setup source
|
|
1667
2335
|
sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
|
|
1668
2336
|
source.buffer = buffer;
|
|
1669
|
-
source.playbackRate.value =
|
|
2337
|
+
source.playbackRate.value = rate;
|
|
1670
2338
|
source.loop = loop;
|
|
1671
2339
|
|
|
1672
2340
|
// create pan and gain nodes
|
|
1673
2341
|
source
|
|
1674
2342
|
.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, 1, -1)}))
|
|
1675
|
-
.connect(new GainNode(audioContext, {'gain':
|
|
2343
|
+
.connect(new GainNode(audioContext, {'gain':soundVolume*volume}))
|
|
1676
2344
|
.connect(audioContext.destination);
|
|
1677
2345
|
|
|
1678
2346
|
// play and return sound
|
|
@@ -1683,10 +2351,20 @@ function playSamples(sampleChannels, volume=1, playbackRate=1, pan=0, loop=0)
|
|
|
1683
2351
|
///////////////////////////////////////////////////////////////////////////////
|
|
1684
2352
|
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.1.8 by Frank Force
|
|
1685
2353
|
|
|
1686
|
-
|
|
1687
|
-
|
|
2354
|
+
/** Generate and play a ZzFX sound
|
|
2355
|
+
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
2356
|
+
* @return {Array} - Array of audio samples
|
|
2357
|
+
* @memberof Audio */
|
|
2358
|
+
const zzfx = (...zzfxSound) => playSamples([zzfxG(...zzfxSound)]);
|
|
1688
2359
|
|
|
1689
|
-
|
|
2360
|
+
/** Sample rate used for all ZzFX sounds
|
|
2361
|
+
* @default 44100
|
|
2362
|
+
* @memberof Audio */
|
|
2363
|
+
const zzfxR = 44100;
|
|
2364
|
+
|
|
2365
|
+
/** Generate samples for a ZzFX sound
|
|
2366
|
+
* @memberof Audio */
|
|
2367
|
+
function zzfxG
|
|
1690
2368
|
(
|
|
1691
2369
|
// parameters
|
|
1692
2370
|
volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
|
|
@@ -1730,7 +2408,7 @@ function zzfxG // generate samples
|
|
|
1730
2408
|
1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
|
|
1731
2409
|
: 1) *
|
|
1732
2410
|
sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
|
|
1733
|
-
volume *
|
|
2411
|
+
volume * soundVolume * ( // envelope
|
|
1734
2412
|
i < attack ? i/attack : // attack
|
|
1735
2413
|
i < attack + decay ? // decay
|
|
1736
2414
|
1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
|
|
@@ -1771,6 +2449,13 @@ function zzfxG // generate samples
|
|
|
1771
2449
|
///////////////////////////////////////////////////////////////////////////////
|
|
1772
2450
|
// ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
|
|
1773
2451
|
|
|
2452
|
+
/** Generate samples for a ZzFM song with given parameters
|
|
2453
|
+
* @param {Array} instruments - Array of ZzFX sound paramaters
|
|
2454
|
+
* @param {Array} patterns - Array of pattern data
|
|
2455
|
+
* @param {Array} sequence - Array of pattern indexes
|
|
2456
|
+
* @param {Number} [BPM=125] - Playback speed of the song in BPM
|
|
2457
|
+
* @returns {Array} - Left and right channel sample data
|
|
2458
|
+
* @memberof Audio */
|
|
1774
2459
|
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
1775
2460
|
{
|
|
1776
2461
|
let instrumentParameters;
|
|
@@ -1868,25 +2553,31 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
1868
2553
|
|
|
1869
2554
|
return [leftChannelBuffer, rightChannelBuffer];
|
|
1870
2555
|
}
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2556
|
+
/**
|
|
2557
|
+
* LittleJS Tile Layer System
|
|
2558
|
+
* <br> - Caches arrays of tiles to offscreen canvas for fast rendering
|
|
2559
|
+
* <br> - Unlimted numbers of layers, allocates canvases as needed
|
|
2560
|
+
* <br> - Interfaces with EngineObject for collision
|
|
2561
|
+
* <br> - Collision layer is separate from visible layers
|
|
2562
|
+
* <br> - Tile layers can be drawn to using their context with canvas2d
|
|
2563
|
+
* <br> - It is recommended to have a visible layer that matches the collision
|
|
2564
|
+
* @namespace TileCollision
|
|
2565
|
+
*/
|
|
1880
2566
|
|
|
1881
2567
|
'use strict';
|
|
1882
2568
|
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
2569
|
+
/** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access
|
|
2570
|
+
* @memberof TileCollision */
|
|
1886
2571
|
let tileCollision = [];
|
|
2572
|
+
|
|
2573
|
+
/** Size of the tile collision layer
|
|
2574
|
+
* @type {Vector2}
|
|
2575
|
+
* @memberof TileCollision */
|
|
1887
2576
|
let tileCollisionSize = vec2();
|
|
1888
|
-
const tileLayerCanvasCache = [];
|
|
1889
2577
|
|
|
2578
|
+
/** Clear and initialize tile collision
|
|
2579
|
+
* @param {Vector2} size
|
|
2580
|
+
* @memberof TileCollision */
|
|
1890
2581
|
function initTileCollision(size)
|
|
1891
2582
|
{
|
|
1892
2583
|
tileCollisionSize = size;
|
|
@@ -1895,21 +2586,34 @@ function initTileCollision(size)
|
|
|
1895
2586
|
tileCollision[i] = 0;
|
|
1896
2587
|
}
|
|
1897
2588
|
|
|
1898
|
-
|
|
2589
|
+
/** Set tile collision data
|
|
2590
|
+
* @param {Vector2} pos
|
|
2591
|
+
* @param {Number} [data=0]
|
|
2592
|
+
* @memberof TileCollision */
|
|
1899
2593
|
const setTileCollisionData = (pos, data=0)=>
|
|
1900
2594
|
pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
|
|
2595
|
+
|
|
2596
|
+
/** Get tile collision data
|
|
2597
|
+
* @param {Vector2} pos
|
|
2598
|
+
* @return {Number}
|
|
2599
|
+
* @memberof TileCollision */
|
|
1901
2600
|
const getTileCollisionData = (pos)=>
|
|
1902
2601
|
pos.arrayCheck(tileCollisionSize) ? tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] : 0;
|
|
1903
2602
|
|
|
1904
|
-
|
|
2603
|
+
/** Check if collision with another object should occur
|
|
2604
|
+
* @param {Vector2} pos
|
|
2605
|
+
* @param {Vector2} [size=new Vector2(1,1)]
|
|
2606
|
+
* @param {EngineObject} [object]
|
|
2607
|
+
* @return {Boolean}
|
|
2608
|
+
* @memberof TileCollision */
|
|
1905
2609
|
function tileCollisionTest(pos, size=vec2(), object)
|
|
1906
2610
|
{
|
|
1907
2611
|
const minX = max(Math.floor(pos.x - size.x/2), 0);
|
|
1908
2612
|
const minY = max(Math.floor(pos.y - size.y/2), 0);
|
|
1909
|
-
const maxX = min(
|
|
1910
|
-
const maxY = min(
|
|
1911
|
-
for (let y = minY; y
|
|
1912
|
-
for (let x = minX; x
|
|
2613
|
+
const maxX = min(pos.x + size.x/2, tileCollisionSize.x-1);
|
|
2614
|
+
const maxY = min(pos.y + size.y/2, tileCollisionSize.y-1);
|
|
2615
|
+
for (let y = minY; y < maxY; ++y)
|
|
2616
|
+
for (let x = minX; x < maxX; ++x)
|
|
1913
2617
|
{
|
|
1914
2618
|
const tileData = tileCollision[y*tileCollisionSize.x+x];
|
|
1915
2619
|
if (tileData && (!object || object.collideWithTile(tileData, new Vector2(x, y))))
|
|
@@ -1917,13 +2621,18 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
1917
2621
|
}
|
|
1918
2622
|
}
|
|
1919
2623
|
|
|
1920
|
-
|
|
1921
|
-
|
|
2624
|
+
/** Return the center of tile if any that is hit (this does not return the exact hit point)
|
|
2625
|
+
* @param {Vector2} posStart
|
|
2626
|
+
* @param {Vector2} posEnd
|
|
2627
|
+
* @param {EngineObject} [object]
|
|
2628
|
+
* @return {Vector2}
|
|
2629
|
+
* @memberof TileCollision */
|
|
1922
2630
|
function tileCollisionRaycast(posStart, posEnd, object)
|
|
1923
2631
|
{
|
|
1924
2632
|
// test if a ray collides with tiles from start to end
|
|
1925
|
-
|
|
1926
|
-
|
|
2633
|
+
// todo: a way to get the exact hit point, it must still register as inside the hit tile
|
|
2634
|
+
posStart = posStart.floor();
|
|
2635
|
+
posEnd = posEnd.floor();
|
|
1927
2636
|
const posDelta = posEnd.subtract(posStart);
|
|
1928
2637
|
const dx = abs(posDelta.x), dy = -abs(posDelta.y);
|
|
1929
2638
|
const sx = sign(posDelta.x), sy = sign(posDelta.y);
|
|
@@ -1951,32 +2660,74 @@ function tileCollisionRaycast(posStart, posEnd, object)
|
|
|
1951
2660
|
///////////////////////////////////////////////////////////////////////////////
|
|
1952
2661
|
// Tile Layer Rendering System
|
|
1953
2662
|
|
|
2663
|
+
// Reuse canvas autmatically when destroyed
|
|
2664
|
+
const tileLayerCanvasCache = [];
|
|
2665
|
+
|
|
2666
|
+
/**
|
|
2667
|
+
* Tile layer data object stores info about how to render a tile
|
|
2668
|
+
* @example
|
|
2669
|
+
* // create tile layer data with tile index 0 and random orientation and color
|
|
2670
|
+
* const tileIndex = 0;
|
|
2671
|
+
* const direction = randInt(4)
|
|
2672
|
+
* const mirror = randInt(2);
|
|
2673
|
+
* const color = randColor();
|
|
2674
|
+
* const data = new TileLayerData(tileIndex, direction, mirror, color);
|
|
2675
|
+
*/
|
|
1954
2676
|
class TileLayerData
|
|
1955
2677
|
{
|
|
1956
|
-
|
|
2678
|
+
/** Create a tile layer data object, one for each tile in a TileLayer
|
|
2679
|
+
* @param {Number} [tile] - The tile to use, untextured if undefined
|
|
2680
|
+
* @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments
|
|
2681
|
+
* @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis
|
|
2682
|
+
* @param {Color} [color=new Color(1,1,1)] - Color of the tile */
|
|
2683
|
+
constructor(tile, direction=0, mirror=0, color=new Color)
|
|
1957
2684
|
{
|
|
2685
|
+
/** @property {Number} - The tile to use, untextured if undefined */
|
|
1958
2686
|
this.tile = tile;
|
|
2687
|
+
/** @property {Number} - Integer direction of tile, in 90 degree increments */
|
|
1959
2688
|
this.direction = direction;
|
|
2689
|
+
/** @property {Boolean} - If the tile should be mirrored along the x axis */
|
|
1960
2690
|
this.mirror = mirror;
|
|
2691
|
+
/** @property {Color} - Color of the tile */
|
|
1961
2692
|
this.color = color;
|
|
1962
2693
|
}
|
|
2694
|
+
|
|
2695
|
+
/** Set this tile to clear, it will not be rendered */
|
|
1963
2696
|
clear() { this.tile = this.direction = this.mirror = 0; color = new Color; }
|
|
1964
2697
|
}
|
|
1965
2698
|
|
|
2699
|
+
/**
|
|
2700
|
+
* Tile layer object - cached rendering system for tile layers
|
|
2701
|
+
* <br> - Each Tile layer is rendered to an off screen canvas
|
|
2702
|
+
* <br> - Tile layers are not rendered using WebGL to allow modifications at run time
|
|
2703
|
+
* <br> - Tile layers are sorted
|
|
2704
|
+
* @extends EngineObject
|
|
2705
|
+
* @example
|
|
2706
|
+
* // create tile collision and visible tile layer
|
|
2707
|
+
* initTileCollision(vec2(200,100));
|
|
2708
|
+
* const tileLayer = new TileLayer();
|
|
2709
|
+
*/
|
|
1966
2710
|
class TileLayer extends EngineObject
|
|
1967
2711
|
{
|
|
1968
|
-
|
|
2712
|
+
/** Create a tile layer object
|
|
2713
|
+
* @param {Vector2} [position=new Vector2(0,0)] - World space position
|
|
2714
|
+
* @param {Vector2} [size=objectDefaultSize] - World space size
|
|
2715
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
|
|
2716
|
+
* @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
|
|
2717
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
2718
|
+
*/
|
|
2719
|
+
constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
|
|
1969
2720
|
{
|
|
1970
|
-
super(pos, size);
|
|
2721
|
+
super(pos, size, -1, tileSize, 0, undefined, renderOrder);
|
|
1971
2722
|
|
|
1972
|
-
|
|
2723
|
+
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
1973
2724
|
this.canvas = tileLayerCanvasCache.length ? tileLayerCanvasCache.pop() : document.createElement('canvas');
|
|
2725
|
+
/** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
1974
2726
|
this.context = this.canvas.getContext('2d');
|
|
2727
|
+
/** @property {Vector2} - How much to scale this layer when rendered */
|
|
1975
2728
|
this.scale = scale;
|
|
1976
|
-
|
|
1977
|
-
this.
|
|
1978
|
-
this.renderOrder = layer;
|
|
1979
|
-
this.flushGLBeforeRender = 1;
|
|
2729
|
+
/** @property {Boolean} [isOverlay=0] - If true this layer will render to overlay canvas and appear above all objects */
|
|
2730
|
+
this.isOverlay;
|
|
1980
2731
|
|
|
1981
2732
|
// init tile data
|
|
1982
2733
|
this.data = [];
|
|
@@ -1984,6 +2735,7 @@ class TileLayer extends EngineObject
|
|
|
1984
2735
|
this.data.push(new TileLayerData());
|
|
1985
2736
|
}
|
|
1986
2737
|
|
|
2738
|
+
/** Destroy this tile layer */
|
|
1987
2739
|
destroy()
|
|
1988
2740
|
{
|
|
1989
2741
|
// add canvas back to the cache
|
|
@@ -1991,6 +2743,10 @@ class TileLayer extends EngineObject
|
|
|
1991
2743
|
super.destroy();
|
|
1992
2744
|
}
|
|
1993
2745
|
|
|
2746
|
+
/** Set data at a given position in the array
|
|
2747
|
+
* @param {Vector2} position - Local position in array
|
|
2748
|
+
* @param {TileLayerData} data - Data to set
|
|
2749
|
+
* @param {Boolean} [redraw=0] - Force the tile to redraw if true */
|
|
1994
2750
|
setData(layerPos, data, redraw)
|
|
1995
2751
|
{
|
|
1996
2752
|
if (layerPos.arrayCheck(this.size))
|
|
@@ -2000,38 +2756,46 @@ class TileLayer extends EngineObject
|
|
|
2000
2756
|
}
|
|
2001
2757
|
}
|
|
2002
2758
|
|
|
2759
|
+
/** Get data at a given position in the array
|
|
2760
|
+
* @param {Vector2} layerPos - Local position in array
|
|
2761
|
+
* @return {TileLayerData} */
|
|
2003
2762
|
getData(layerPos)
|
|
2004
2763
|
{ return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]; }
|
|
2005
2764
|
|
|
2006
|
-
|
|
2765
|
+
// Tile layers are not updated
|
|
2766
|
+
update() {}
|
|
2767
|
+
|
|
2768
|
+
// Render the tile layer, called automatically by the engine
|
|
2007
2769
|
render()
|
|
2008
2770
|
{
|
|
2009
2771
|
ASSERT(mainContext != this.context); // must call redrawEnd() after drawing tiles
|
|
2010
2772
|
|
|
2011
|
-
// flush and copy gl canvas because tile canvas does not use
|
|
2012
|
-
|
|
2773
|
+
// flush and copy gl canvas because tile canvas does not use webgl
|
|
2774
|
+
glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
2013
2775
|
|
|
2014
2776
|
// draw the entire cached level onto the main canvas
|
|
2015
2777
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
2016
|
-
mainContext.drawImage
|
|
2778
|
+
(this.isOverlay ? overlayContext : mainContext).drawImage
|
|
2017
2779
|
(
|
|
2018
2780
|
this.canvas, pos.x, pos.y,
|
|
2019
2781
|
cameraScale*this.size.x*this.scale.x, cameraScale*this.size.y*this.scale.y
|
|
2020
2782
|
);
|
|
2021
2783
|
}
|
|
2022
2784
|
|
|
2785
|
+
/** Draw all the tile data to an offscreen canvas using webgl if possible */
|
|
2023
2786
|
redraw()
|
|
2024
2787
|
{
|
|
2025
|
-
// draw all the tile data to an offscreen canvas using webgl if possible
|
|
2026
2788
|
this.redrawStart();
|
|
2027
2789
|
this.drawAllTileData();
|
|
2028
2790
|
this.redrawEnd();
|
|
2029
2791
|
}
|
|
2030
2792
|
|
|
2793
|
+
/** Call to start the redraw process
|
|
2794
|
+
* @param {Boolean} [clear=1] - Should it clear the canvas before drawing */
|
|
2031
2795
|
redrawStart(clear = 1)
|
|
2032
2796
|
{
|
|
2033
2797
|
// clear and set size
|
|
2034
|
-
const width
|
|
2798
|
+
const width = this.size.x * this.tileSize.x;
|
|
2035
2799
|
const height = this.size.y * this.tileSize.y;
|
|
2036
2800
|
if (clear)
|
|
2037
2801
|
{
|
|
@@ -2052,6 +2816,7 @@ class TileLayer extends EngineObject
|
|
|
2052
2816
|
glPreRender(width, height);
|
|
2053
2817
|
}
|
|
2054
2818
|
|
|
2819
|
+
/** Call to end the redraw process */
|
|
2055
2820
|
redrawEnd()
|
|
2056
2821
|
{
|
|
2057
2822
|
ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
|
|
@@ -2062,18 +2827,24 @@ class TileLayer extends EngineObject
|
|
|
2062
2827
|
[mainCanvasSize, mainCanvas, mainContext, cameraScale, cameraPos] = this.savedRenderSettings;
|
|
2063
2828
|
}
|
|
2064
2829
|
|
|
2830
|
+
/** Draw the tile at a given position
|
|
2831
|
+
* @param {Vector2} layerPos */
|
|
2065
2832
|
drawTileData(layerPos)
|
|
2066
2833
|
{
|
|
2067
2834
|
// first clear out where the tile was
|
|
2068
|
-
const pos = layerPos.
|
|
2835
|
+
const pos = layerPos.floor().add(this.pos).add(vec2(.5));
|
|
2069
2836
|
this.drawCanvas2D(pos, vec2(1), 0, 0, (context)=>context.clearRect(-.5, -.5, 1, 1));
|
|
2070
2837
|
|
|
2071
|
-
// draw the tile
|
|
2838
|
+
// draw the tile if not undefined
|
|
2072
2839
|
const d = this.getData(layerPos);
|
|
2073
|
-
|
|
2074
|
-
|
|
2840
|
+
if (d.tile != undefined)
|
|
2841
|
+
{
|
|
2842
|
+
ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
|
|
2843
|
+
drawTile(pos, vec2(1), d.tile, this.tileSize, d.color, d.direction*PI/2, d.mirror);
|
|
2844
|
+
}
|
|
2075
2845
|
}
|
|
2076
2846
|
|
|
2847
|
+
/** Draw all the tiles in this layer */
|
|
2077
2848
|
drawAllTileData()
|
|
2078
2849
|
{
|
|
2079
2850
|
for (let x = this.size.x; x--;)
|
|
@@ -2081,7 +2852,12 @@ class TileLayer extends EngineObject
|
|
|
2081
2852
|
this.drawTileData(vec2(x,y));
|
|
2082
2853
|
}
|
|
2083
2854
|
|
|
2084
|
-
|
|
2855
|
+
/** Draw directly to the 2d canvas in world space (bipass webgl)
|
|
2856
|
+
* @param {Vector2} pos
|
|
2857
|
+
* @param {Vector2} size
|
|
2858
|
+
* @param {Number} angle
|
|
2859
|
+
* @param {Boolean} mirror
|
|
2860
|
+
* @param {Function} drawFunction */
|
|
2085
2861
|
drawCanvas2D(pos, size, angle, mirror, drawFunction)
|
|
2086
2862
|
{
|
|
2087
2863
|
const context = this.context;
|
|
@@ -2095,8 +2871,15 @@ class TileLayer extends EngineObject
|
|
|
2095
2871
|
context.restore();
|
|
2096
2872
|
}
|
|
2097
2873
|
|
|
2098
|
-
|
|
2099
|
-
|
|
2874
|
+
/** Draw a tile directly onto the layer canvas
|
|
2875
|
+
* @param {Vector2} pos
|
|
2876
|
+
* @param {Vector2} [size=new Vector2(1,1)]
|
|
2877
|
+
* @param {Number} [tileIndex=-1]
|
|
2878
|
+
* @param {Vector2} [tileSize=tileSizeDefault]
|
|
2879
|
+
* @param {Color} [color=new Color(1,1,1)]
|
|
2880
|
+
* @param {Number} [angle=0]
|
|
2881
|
+
* @param {Boolean} [mirror=0] */
|
|
2882
|
+
drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle=0, mirror)
|
|
2100
2883
|
{
|
|
2101
2884
|
this.drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
2102
2885
|
{
|
|
@@ -2117,6 +2900,11 @@ class TileLayer extends EngineObject
|
|
|
2117
2900
|
});
|
|
2118
2901
|
}
|
|
2119
2902
|
|
|
2903
|
+
/** Draw a rectangle directly onto the layer canvas
|
|
2904
|
+
* @param {Vector2} pos
|
|
2905
|
+
* @param {Vector2} [size=new Vector2(1,1)]
|
|
2906
|
+
* @param {Color} [color=new Color(1,1,1)]
|
|
2907
|
+
* @param {Number} [angle=0] */
|
|
2120
2908
|
drawRect(pos, size, color, angle) { this.drawTile(pos, size, -1, 0, color, angle, 0); }
|
|
2121
2909
|
}
|
|
2122
2910
|
/*
|
|
@@ -2128,72 +2916,142 @@ class TileLayer extends EngineObject
|
|
|
2128
2916
|
|
|
2129
2917
|
'use strict';
|
|
2130
2918
|
|
|
2919
|
+
/**
|
|
2920
|
+
* Particle Emitter - Spawns particles with the given settings
|
|
2921
|
+
* @extends EngineObject
|
|
2922
|
+
* @example
|
|
2923
|
+
* // create a particle emitter
|
|
2924
|
+
* let pos = vec2(2,3);
|
|
2925
|
+
* let particleEmiter = new ParticleEmitter
|
|
2926
|
+
* (
|
|
2927
|
+
* pos, 1, 0, 500, PI, // pos, emitSize, emitTime, emitRate, emiteCone
|
|
2928
|
+
* 0, vec2(16), // tileIndex, tileSize
|
|
2929
|
+
* new Color, new Color(0,0,0), // colorStartA, colorStartB
|
|
2930
|
+
* new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
|
|
2931
|
+
* 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
2932
|
+
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
2933
|
+
* .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
|
|
2934
|
+
* );
|
|
2935
|
+
*/
|
|
2131
2936
|
class ParticleEmitter extends EngineObject
|
|
2132
2937
|
{
|
|
2938
|
+
/** Create a particle system with the given settings
|
|
2939
|
+
* @param {Vector2} position - World space position of the emitter
|
|
2940
|
+
* @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
2941
|
+
* @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
|
|
2942
|
+
* @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
|
|
2943
|
+
* @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
|
|
2944
|
+
* @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied
|
|
2945
|
+
* @param {Number} [tileSize=tileSizeDefault] - Tile size for particles
|
|
2946
|
+
* @param {Color} [colorStartA=new Color(1,1,1)] - Color at start of life 1, randomized between start colors
|
|
2947
|
+
* @param {Color} [colorStartB=new Color(1,1,1)] - Color at start of life 2, randomized between start colors
|
|
2948
|
+
* @param {Color} [colorEndA=new Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
|
|
2949
|
+
* @param {Color} [colorEndB=new Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
|
|
2950
|
+
* @param {Number} [particleTime=.5] - How long particles live
|
|
2951
|
+
* @param {Number} [sizeStart=.1] - How big are particles at start
|
|
2952
|
+
* @param {Number} [sizeEnd=1] - How big are particles at end
|
|
2953
|
+
* @param {Number} [speed=.1] - How fast are particles when spawned
|
|
2954
|
+
* @param {Number} [angleSpeed=.05] - How fast are particles rotating
|
|
2955
|
+
* @param {Number} [damping=1] - How much to dampen particle speed
|
|
2956
|
+
* @param {Number} [angleDamping=1] - How much to dampen particle angular speed
|
|
2957
|
+
* @param {Number} [gravityScale=0] - How much does gravity effect particles
|
|
2958
|
+
* @param {Number} [particleConeAngle=PI] - Cone for start particle angle
|
|
2959
|
+
* @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life
|
|
2960
|
+
* @param {Number} [randomness=.2] - Apply extra randomness percent
|
|
2961
|
+
* @param {Boolean} [collideTiles=0] - Do particles collide against tiles
|
|
2962
|
+
* @param {Boolean} [additive=0] - Should particles use addtive blend
|
|
2963
|
+
* @param {Boolean} [randomColorLinear=0] - Should color be randomized linearly or across each component
|
|
2964
|
+
* @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
|
|
2965
|
+
*/
|
|
2133
2966
|
constructor
|
|
2134
2967
|
(
|
|
2135
|
-
pos,
|
|
2136
|
-
emitSize = 0,
|
|
2137
|
-
emitTime = 0,
|
|
2138
|
-
emitRate = 100,
|
|
2139
|
-
emitConeAngle = PI,
|
|
2140
|
-
tileIndex = -1,
|
|
2141
|
-
tileSize =
|
|
2142
|
-
colorStartA = new Color,
|
|
2143
|
-
colorStartB = new Color,
|
|
2144
|
-
colorEndA = new Color(1,1,1,0),
|
|
2145
|
-
colorEndB = new Color(1,1,1,0),
|
|
2146
|
-
particleTime = .5,
|
|
2147
|
-
sizeStart = .1,
|
|
2148
|
-
sizeEnd = 1,
|
|
2149
|
-
speed = .1,
|
|
2150
|
-
angleSpeed = .05,
|
|
2151
|
-
damping = 1,
|
|
2152
|
-
angleDamping = 1,
|
|
2153
|
-
gravityScale = 0,
|
|
2154
|
-
particleConeAngle = PI,
|
|
2155
|
-
fadeRate = .1,
|
|
2156
|
-
randomness = .2,
|
|
2157
|
-
collideTiles,
|
|
2158
|
-
additive,
|
|
2159
|
-
randomColorLinear = 1,
|
|
2160
|
-
renderOrder = additive ? 1e9 : 0
|
|
2968
|
+
pos,
|
|
2969
|
+
emitSize = 0,
|
|
2970
|
+
emitTime = 0,
|
|
2971
|
+
emitRate = 100,
|
|
2972
|
+
emitConeAngle = PI,
|
|
2973
|
+
tileIndex = -1,
|
|
2974
|
+
tileSize = tileSizeDefault,
|
|
2975
|
+
colorStartA = new Color,
|
|
2976
|
+
colorStartB = new Color,
|
|
2977
|
+
colorEndA = new Color(1,1,1,0),
|
|
2978
|
+
colorEndB = new Color(1,1,1,0),
|
|
2979
|
+
particleTime = .5,
|
|
2980
|
+
sizeStart = .1,
|
|
2981
|
+
sizeEnd = 1,
|
|
2982
|
+
speed = .1,
|
|
2983
|
+
angleSpeed = .05,
|
|
2984
|
+
damping = 1,
|
|
2985
|
+
angleDamping = 1,
|
|
2986
|
+
gravityScale = 0,
|
|
2987
|
+
particleConeAngle = PI,
|
|
2988
|
+
fadeRate = .1,
|
|
2989
|
+
randomness = .2,
|
|
2990
|
+
collideTiles,
|
|
2991
|
+
additive,
|
|
2992
|
+
randomColorLinear = 1,
|
|
2993
|
+
renderOrder = additive ? 1e9 : 0
|
|
2161
2994
|
)
|
|
2162
2995
|
{
|
|
2163
|
-
super(pos, new Vector2, tileIndex, tileSize);
|
|
2996
|
+
super(pos, new Vector2, tileIndex, tileSize, 0, undefined, renderOrder);
|
|
2164
2997
|
|
|
2165
2998
|
// emitter settings
|
|
2999
|
+
/** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
2166
3000
|
this.emitSize = emitSize
|
|
3001
|
+
/** @property {Number} - How long to stay alive (0 is forever) */
|
|
2167
3002
|
this.emitTime = emitTime;
|
|
3003
|
+
/** @property {Number} - How many particles per second to spawn, does not emit if 0 */
|
|
2168
3004
|
this.emitRate = emitRate;
|
|
3005
|
+
/** @property {Number} - Local angle to apply velocity to particles from emitter */
|
|
2169
3006
|
this.emitConeAngle = emitConeAngle;
|
|
2170
3007
|
|
|
2171
3008
|
// color settings
|
|
3009
|
+
/** @property {Color} - Color at start of life 1, randomized between start colors */
|
|
2172
3010
|
this.colorStartA = colorStartA;
|
|
3011
|
+
/** @property {Color} - Color at start of life 2, randomized between start colors */
|
|
2173
3012
|
this.colorStartB = colorStartB;
|
|
3013
|
+
/** @property {Color} - Color at end of life 1, randomized between end colors */
|
|
2174
3014
|
this.colorEndA = colorEndA;
|
|
3015
|
+
/** @property {Color} - Color at end of life 2, randomized between end colors */
|
|
2175
3016
|
this.colorEndB = colorEndB;
|
|
3017
|
+
/** @property {Boolean} - Should color be randomized linearly or across each component */
|
|
2176
3018
|
this.randomColorLinear = randomColorLinear;
|
|
2177
3019
|
|
|
2178
3020
|
// particle settings
|
|
3021
|
+
/** @property {Number} - How long particles live */
|
|
2179
3022
|
this.particleTime = particleTime;
|
|
3023
|
+
/** @property {Number} - How big are particles at start */
|
|
2180
3024
|
this.sizeStart = sizeStart;
|
|
3025
|
+
/** @property {Number} - How big are particles at end */
|
|
2181
3026
|
this.sizeEnd = sizeEnd;
|
|
3027
|
+
/** @property {Number} - How fast are particles when spawned */
|
|
2182
3028
|
this.speed = speed;
|
|
3029
|
+
/** @property {Number} - How fast are particles rotating */
|
|
2183
3030
|
this.angleSpeed = angleSpeed;
|
|
3031
|
+
/** @property {Number} - How much to dampen particle speed */
|
|
2184
3032
|
this.damping = damping;
|
|
3033
|
+
/** @property {Number} - How much to dampen particle angular speed */
|
|
2185
3034
|
this.angleDamping = angleDamping;
|
|
3035
|
+
/** @property {Number} - How much does gravity effect particles */
|
|
2186
3036
|
this.gravityScale = gravityScale;
|
|
3037
|
+
/** @property {Number} - Cone for start particle angle */
|
|
2187
3038
|
this.particleConeAngle = particleConeAngle;
|
|
3039
|
+
/** @property {Number} - How quick to fade in particles at start/end in percent of life */
|
|
2188
3040
|
this.fadeRate = fadeRate;
|
|
3041
|
+
/** @property {Number} - Apply extra randomness percent */
|
|
2189
3042
|
this.randomness = randomness;
|
|
3043
|
+
/** @property {Number} - Do particles collide against tiles */
|
|
2190
3044
|
this.collideTiles = collideTiles;
|
|
3045
|
+
/** @property {Number} - Should particles use addtive blend */
|
|
2191
3046
|
this.additive = additive;
|
|
2192
|
-
|
|
2193
|
-
this.trailScale =
|
|
3047
|
+
/** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */
|
|
3048
|
+
this.trailScale = 0;
|
|
3049
|
+
|
|
3050
|
+
// internal variables
|
|
2194
3051
|
this.emitTimeBuffer = 0;
|
|
2195
3052
|
}
|
|
2196
3053
|
|
|
3054
|
+
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
2197
3055
|
update()
|
|
2198
3056
|
{
|
|
2199
3057
|
// only do default update to apply parent transforms
|
|
@@ -2216,6 +3074,8 @@ class ParticleEmitter extends EngineObject
|
|
|
2216
3074
|
debugParticles && debugRect(this.pos, vec2(this.emitSize), '#0f0', 0, this.angle);
|
|
2217
3075
|
}
|
|
2218
3076
|
|
|
3077
|
+
/** Spawn one particle
|
|
3078
|
+
* @return {Particle} */
|
|
2219
3079
|
emitParticle()
|
|
2220
3080
|
{
|
|
2221
3081
|
// spawn a particle
|
|
@@ -2267,16 +3127,27 @@ class ParticleEmitter extends EngineObject
|
|
|
2267
3127
|
return particle;
|
|
2268
3128
|
}
|
|
2269
3129
|
|
|
2270
|
-
|
|
3130
|
+
// Particle emitters are not rendered, only the particles are
|
|
3131
|
+
render() {}
|
|
2271
3132
|
}
|
|
2272
3133
|
|
|
2273
3134
|
///////////////////////////////////////////////////////////////////////////////
|
|
2274
|
-
|
|
2275
|
-
|
|
3135
|
+
/**
|
|
3136
|
+
* Particle Object - Created automatically by Particle Emitters
|
|
3137
|
+
* @extends EngineObject
|
|
3138
|
+
*/
|
|
2276
3139
|
class Particle extends EngineObject
|
|
2277
3140
|
{
|
|
3141
|
+
/**
|
|
3142
|
+
* Create a particle with the given settings
|
|
3143
|
+
* @param {Vector2} position - World space position of the particle
|
|
3144
|
+
* @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1
|
|
3145
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
3146
|
+
* @param {Number} [angle=0] - Angle to rotate the particle
|
|
3147
|
+
*/
|
|
2278
3148
|
constructor(pos, tileIndex, tileSize, angle) { super(pos, new Vector2, tileIndex, tileSize, angle); }
|
|
2279
3149
|
|
|
3150
|
+
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
2280
3151
|
render()
|
|
2281
3152
|
{
|
|
2282
3153
|
// modulate size and color
|
|
@@ -2318,27 +3189,64 @@ class Particle extends EngineObject
|
|
|
2318
3189
|
}
|
|
2319
3190
|
}
|
|
2320
3191
|
}
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
3192
|
+
/**
|
|
3193
|
+
* LittleJS Medal System
|
|
3194
|
+
* <br> - Tracks and displays medals
|
|
3195
|
+
* <br> - Saves medals to local storage
|
|
3196
|
+
* <br> - Newgrounds and OS13k integration
|
|
3197
|
+
* @namespace Medals
|
|
3198
|
+
*/
|
|
2327
3199
|
|
|
2328
3200
|
'use strict';
|
|
2329
3201
|
|
|
2330
|
-
|
|
2331
|
-
|
|
3202
|
+
/** List of all medals
|
|
3203
|
+
* @memberof Medals */
|
|
3204
|
+
const medals = [];
|
|
3205
|
+
|
|
3206
|
+
/** Set to stop medals from being unlockable (like if cheats are enabled)
|
|
3207
|
+
* @memberof Medals */
|
|
3208
|
+
let medalsPreventUnlock;
|
|
3209
|
+
|
|
3210
|
+
/** This can used to enable Newgrounds functionality
|
|
3211
|
+
* @type {Newgrounds}
|
|
3212
|
+
* @memberof Medals */
|
|
3213
|
+
let newgrounds;
|
|
3214
|
+
|
|
3215
|
+
// Engine internal variables not exposed to documentation
|
|
3216
|
+
let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimer;
|
|
3217
|
+
|
|
3218
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2332
3219
|
|
|
2333
|
-
|
|
3220
|
+
/** Initialize medals with a save name used for storage
|
|
3221
|
+
* <br> - Checks if medals are unlocked
|
|
3222
|
+
* <br> - Call this after creating all medals
|
|
3223
|
+
* @param {String} saveName
|
|
3224
|
+
* @memberof Medals */
|
|
3225
|
+
function medalsInit(saveName)
|
|
2334
3226
|
{
|
|
2335
3227
|
// check if medals are unlocked
|
|
2336
|
-
|
|
3228
|
+
medalsSaveName = saveName;
|
|
2337
3229
|
debugMedals || medals.forEach(medal=> medal.unlocked = localStorage[medal.storageKey()]);
|
|
2338
3230
|
}
|
|
2339
3231
|
|
|
3232
|
+
/**
|
|
3233
|
+
* Medal Object - Tracks an unlockable medal
|
|
3234
|
+
* @example
|
|
3235
|
+
* // create a medal
|
|
3236
|
+
* const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
|
|
3237
|
+
*
|
|
3238
|
+
* // unlock the medal
|
|
3239
|
+
* medal_example.unlock();
|
|
3240
|
+
*/
|
|
2340
3241
|
class Medal
|
|
2341
3242
|
{
|
|
3243
|
+
/** Create an medal object and adds it to the list of medals
|
|
3244
|
+
* @param {Number} id - The unique identifier of the medal
|
|
3245
|
+
* @param {String} name - Name of the medal
|
|
3246
|
+
* @param {String} [description] - Description of the medal
|
|
3247
|
+
* @param {String} [icon='🏆'] - Icon for the medal
|
|
3248
|
+
* @param {String} [src] - Image location for the medal
|
|
3249
|
+
*/
|
|
2342
3250
|
constructor(id, name, description='', icon='🏆', src)
|
|
2343
3251
|
{
|
|
2344
3252
|
ASSERT(id >= 0 && !medals[id]);
|
|
@@ -2357,26 +3265,25 @@ class Medal
|
|
|
2357
3265
|
}
|
|
2358
3266
|
}
|
|
2359
3267
|
|
|
3268
|
+
/** Unlocks a medal if not already unlocked */
|
|
2360
3269
|
unlock()
|
|
2361
3270
|
{
|
|
2362
3271
|
if (medalsPreventUnlock || this.unlocked)
|
|
2363
3272
|
return;
|
|
2364
3273
|
|
|
2365
3274
|
// save the medal
|
|
2366
|
-
ASSERT(
|
|
3275
|
+
ASSERT(medalsSaveName); // game name must be set
|
|
2367
3276
|
localStorage[this.storageKey()] = this.unlocked = 1;
|
|
2368
3277
|
medalsDisplayQueue.push(this);
|
|
2369
3278
|
|
|
2370
3279
|
// save for newgrounds and OS13K
|
|
2371
3280
|
newgrounds && newgrounds.unlockMedal(this.id);
|
|
2372
|
-
localStorage['OS13kTrophy,' + this.icon + ',' +
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
|
-
storageKey()
|
|
2376
|
-
{
|
|
2377
|
-
return medalsGameName + '_medal_' + this.id;
|
|
3281
|
+
localStorage['OS13kTrophy,' + this.icon + ',' + medalsSaveName + ',' + this.name] = this.description;
|
|
2378
3282
|
}
|
|
2379
3283
|
|
|
3284
|
+
/** Render a medal
|
|
3285
|
+
* @param {Number} [hidePercent=0] - How much to slide the medal off screen
|
|
3286
|
+
*/
|
|
2380
3287
|
render(hidePercent=0)
|
|
2381
3288
|
{
|
|
2382
3289
|
const context = overlayContext;
|
|
@@ -2397,25 +3304,36 @@ class Medal
|
|
|
2397
3304
|
|
|
2398
3305
|
// draw the text
|
|
2399
3306
|
context.textAlign = 'left';
|
|
2400
|
-
context.font = '3em '+
|
|
3307
|
+
context.font = '3em '+ fontDefault;
|
|
2401
3308
|
context.fillText(this.name, x+medalDisplayIconSize+25, y+35);
|
|
2402
|
-
context.font = '1.5em '+
|
|
3309
|
+
context.font = '1.5em '+ fontDefault;
|
|
2403
3310
|
context.restore(context.fillText(this.description, x+medalDisplayIconSize+25, y+70));
|
|
2404
3311
|
}
|
|
2405
3312
|
|
|
3313
|
+
/** Render the icon for a medal
|
|
3314
|
+
* @param {Number} x - Screen space X position
|
|
3315
|
+
* @param {Number} y - Screen space Y position
|
|
3316
|
+
* @param {Number} [size=medalDisplayIconSize] - Screen space size
|
|
3317
|
+
*/
|
|
2406
3318
|
renderIcon(x, y, size=medalDisplayIconSize)
|
|
2407
3319
|
{
|
|
2408
3320
|
// draw the image or icon
|
|
2409
3321
|
const context = overlayContext;
|
|
2410
3322
|
context.textAlign = 'center';
|
|
2411
3323
|
context.textBaseline = 'middle';
|
|
2412
|
-
context.font = size*.6 + 'px '+
|
|
3324
|
+
context.font = size*.6 + 'px '+ fontDefault;
|
|
2413
3325
|
context.fillStyle = '#000';
|
|
2414
3326
|
if (this.image)
|
|
2415
3327
|
context.drawImage(this.image, x-size/2, y-size/2, size, size);
|
|
2416
3328
|
else
|
|
2417
3329
|
context.fillText(this.icon, x, y); // show icon if there is no image
|
|
2418
3330
|
}
|
|
3331
|
+
|
|
3332
|
+
// Get local storage key used by the medal
|
|
3333
|
+
storageKey()
|
|
3334
|
+
{
|
|
3335
|
+
return medalsSaveName + '_medal_' + this.id;
|
|
3336
|
+
}
|
|
2419
3337
|
}
|
|
2420
3338
|
|
|
2421
3339
|
// engine automatically renders medals
|
|
@@ -2426,9 +3344,9 @@ function medalsRender()
|
|
|
2426
3344
|
|
|
2427
3345
|
// update first medal in queue
|
|
2428
3346
|
const medal = medalsDisplayQueue[0];
|
|
2429
|
-
const time =
|
|
3347
|
+
const time = timeReal - medalsDisplayTimer;
|
|
2430
3348
|
if (!medalsDisplayTimer)
|
|
2431
|
-
medalsDisplayTimer =
|
|
3349
|
+
medalsDisplayTimer = timeReal;
|
|
2432
3350
|
else if (time > medalDisplayTime)
|
|
2433
3351
|
medalsDisplayQueue.shift(medalsDisplayTimer = 0);
|
|
2434
3352
|
else
|
|
@@ -2443,15 +3361,26 @@ function medalsRender()
|
|
|
2443
3361
|
}
|
|
2444
3362
|
|
|
2445
3363
|
///////////////////////////////////////////////////////////////////////////////
|
|
2446
|
-
// Newgrounds API wrapper
|
|
2447
3364
|
|
|
3365
|
+
/**
|
|
3366
|
+
* Newgrounds API wrapper object
|
|
3367
|
+
* @example
|
|
3368
|
+
* // create a newgrounds object, replace the app id and cipher with your own
|
|
3369
|
+
* const app_id = '53123:1ZuSTQ9l';
|
|
3370
|
+
* const cipher = 'enF0vGH@Mj/FRASKL23Q==';
|
|
3371
|
+
* newgrounds = new Newgrounds(app_id, cipher);
|
|
3372
|
+
*/
|
|
2448
3373
|
class Newgrounds
|
|
2449
3374
|
{
|
|
3375
|
+
/** Create a newgrounds object
|
|
3376
|
+
* @param {Number} app_id - The newgrounds App ID
|
|
3377
|
+
* @param {String} [cipher] - The encryption Key (AES-128/Base64) */
|
|
2450
3378
|
constructor(app_id, cipher)
|
|
2451
3379
|
{
|
|
2452
3380
|
ASSERT(!newgrounds && app_id);
|
|
2453
3381
|
this.app_id = app_id;
|
|
2454
3382
|
this.cipher = cipher;
|
|
3383
|
+
this.host = location ? location.hostname : '';
|
|
2455
3384
|
|
|
2456
3385
|
// create an instance of CryptoJS for encrypted calls
|
|
2457
3386
|
cipher && (this.cryptoJS = CryptoJS());
|
|
@@ -2473,11 +3402,16 @@ class Newgrounds
|
|
|
2473
3402
|
if (medal)
|
|
2474
3403
|
{
|
|
2475
3404
|
// copy newgrounds medal data
|
|
2476
|
-
medal.
|
|
3405
|
+
medal.image = new Image();
|
|
3406
|
+
medal.image.src = newgroundsMedal['icon'];
|
|
3407
|
+
medal.name = newgroundsMedal['name'];
|
|
2477
3408
|
medal.description = newgroundsMedal['description'];
|
|
2478
|
-
medal.unlocked =
|
|
2479
|
-
medal.
|
|
2480
|
-
medal.
|
|
3409
|
+
medal.unlocked = newgroundsMedal['unlocked'];
|
|
3410
|
+
medal.difficulty = newgroundsMedal['difficulty'];
|
|
3411
|
+
medal.value = newgroundsMedal['value'];
|
|
3412
|
+
|
|
3413
|
+
if (medal.value)
|
|
3414
|
+
medal.description = medal.description + ' (' + medal.value + ')';
|
|
2481
3415
|
}
|
|
2482
3416
|
}
|
|
2483
3417
|
|
|
@@ -2487,30 +3421,55 @@ class Newgrounds
|
|
|
2487
3421
|
debugMedals && console.log(this.scoreboards);
|
|
2488
3422
|
}
|
|
2489
3423
|
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
3424
|
+
/** Send message to unlock a medal by id
|
|
3425
|
+
* @param {Number} id - The medal id */
|
|
3426
|
+
unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, 1); }
|
|
3427
|
+
|
|
3428
|
+
/** Send message to post score
|
|
3429
|
+
* @param {Number} id - The scoreboard id
|
|
3430
|
+
* @param {Number} value - The score value */
|
|
3431
|
+
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
|
|
3432
|
+
|
|
3433
|
+
/** Send message to log a view */
|
|
3434
|
+
logView() { return this.call('App.logView', {'host':this.host}, 1); }
|
|
3435
|
+
|
|
3436
|
+
/** Get scores from a scoreboard
|
|
3437
|
+
* @param {Number} id - The scoreboard id
|
|
3438
|
+
* @param {String} [user=0] - A user's id or name
|
|
3439
|
+
* @param {Number} [social=0] - If true, only social scores will be loaded
|
|
3440
|
+
* @param {Number} [skip=0] - Number of scores to skip before start
|
|
3441
|
+
* @param {Number} [limit=10] - Number of scores to include in the list
|
|
3442
|
+
* @return {Object} - The response JSON object
|
|
3443
|
+
*/
|
|
2500
3444
|
getScores(id, user=0, social=0, skip=0, limit=10)
|
|
2501
|
-
{
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
3445
|
+
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
3446
|
+
|
|
3447
|
+
/** Send a message to call a component of the Newgrounds API
|
|
3448
|
+
* @param {String} component - Name of the component
|
|
3449
|
+
* @param {Object} [parameters=0] - Parameters to use for call
|
|
3450
|
+
* @param {Boolean} [async=0] - If true, wait for response before continuing (will cause stall)
|
|
3451
|
+
* @return {Object} - The response JSON object
|
|
3452
|
+
*/
|
|
2506
3453
|
call(component, parameters=0, async=0)
|
|
2507
3454
|
{
|
|
3455
|
+
const call = {'component':component, 'parameters':parameters};
|
|
3456
|
+
if (this.cipher)
|
|
3457
|
+
{
|
|
3458
|
+
// encrypt using AES-128 Base64 with cryptoJS
|
|
3459
|
+
const cryptoJS = this.cryptoJS;
|
|
3460
|
+
const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
|
|
3461
|
+
const iv = cryptoJS['lib']['WordArray']['random'](16);
|
|
3462
|
+
const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
|
|
3463
|
+
call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
|
|
3464
|
+
call['parameters'] = 0;
|
|
3465
|
+
}
|
|
3466
|
+
|
|
2508
3467
|
// build the input object
|
|
2509
3468
|
const input =
|
|
2510
3469
|
{
|
|
2511
3470
|
'app_id': this.app_id,
|
|
2512
3471
|
'session_id': this.session_id,
|
|
2513
|
-
'call':
|
|
3472
|
+
'call': call
|
|
2514
3473
|
};
|
|
2515
3474
|
|
|
2516
3475
|
// build post data
|
|
@@ -2525,21 +3484,6 @@ class Newgrounds
|
|
|
2525
3484
|
debugMedals && console.log(xmlHttp.responseText);
|
|
2526
3485
|
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
2527
3486
|
}
|
|
2528
|
-
|
|
2529
|
-
encryptCall(call)
|
|
2530
|
-
{
|
|
2531
|
-
if (!this.cipher)
|
|
2532
|
-
return call;
|
|
2533
|
-
|
|
2534
|
-
// encrypt using AES-128 Base64 with cryptoJS
|
|
2535
|
-
const cryptoJS = this.cryptoJS;
|
|
2536
|
-
const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
|
|
2537
|
-
const iv = cryptoJS['lib']['WordArray']['random'](16);
|
|
2538
|
-
const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
|
|
2539
|
-
call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
|
|
2540
|
-
call['parameters'] = 0;
|
|
2541
|
-
return call;
|
|
2542
|
-
}
|
|
2543
3487
|
}
|
|
2544
3488
|
|
|
2545
3489
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -2547,21 +3491,40 @@ class Newgrounds
|
|
|
2547
3491
|
// Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
|
|
2548
3492
|
|
|
2549
3493
|
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));
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
3494
|
+
/**
|
|
3495
|
+
* LittleJS WebGL Interface
|
|
3496
|
+
* <br> - All webgl used by the engine is wrapped up here
|
|
3497
|
+
* <br> - Can be disabled with glEnable to revert to 2D canvas rendering
|
|
3498
|
+
* <br> - Batches sprite rendering on GPU for incredibly fast performance
|
|
3499
|
+
* <br> - Sprite transform math is done in the shader where possible
|
|
3500
|
+
* <br> - For normal stuff you won't need to call any functions in this file
|
|
3501
|
+
* <br> - For advanced stuff there are helper functions to create shaders, textures, etc
|
|
3502
|
+
* @namespace WebGL
|
|
3503
|
+
*/
|
|
2559
3504
|
|
|
2560
3505
|
'use strict';
|
|
2561
3506
|
|
|
2562
|
-
|
|
2563
|
-
|
|
3507
|
+
/** The WebGL canvas which appears above the main canvas and below the overlay canvas
|
|
3508
|
+
* @type {HTMLCanvasElement}
|
|
3509
|
+
* @memberof WebGL */
|
|
3510
|
+
let glCanvas;
|
|
3511
|
+
|
|
3512
|
+
/** 2d context for glCanvas
|
|
3513
|
+
* @type {WebGLRenderingContext}
|
|
3514
|
+
* @memberof WebGL */
|
|
3515
|
+
let glContext;
|
|
3516
|
+
|
|
3517
|
+
/** Main tile sheet texture automatically loaded by engine
|
|
3518
|
+
* @type {WebGLTexture}
|
|
3519
|
+
* @memberof WebGL */
|
|
3520
|
+
let glTileTexture;
|
|
3521
|
+
|
|
3522
|
+
// WebGL internal variables not exposed to documentation
|
|
3523
|
+
let glActiveTexture, glShader, glPositionData, glColorData, glBatchCount, glBatchAdditive, glDirty, glAdditive;
|
|
2564
3524
|
|
|
3525
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3526
|
+
|
|
3527
|
+
// Init WebGL, called automatically by the engine
|
|
2565
3528
|
function glInit()
|
|
2566
3529
|
{
|
|
2567
3530
|
if (!glEnable) return;
|
|
@@ -2573,7 +3536,7 @@ function glInit()
|
|
|
2573
3536
|
|
|
2574
3537
|
if (glOverlay)
|
|
2575
3538
|
{
|
|
2576
|
-
//
|
|
3539
|
+
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
2577
3540
|
document.body.appendChild(glCanvas);
|
|
2578
3541
|
glCanvas.style = mainCanvas.style.cssText;
|
|
2579
3542
|
}
|
|
@@ -2624,20 +3587,21 @@ function glInit()
|
|
|
2624
3587
|
initVertexAttribArray('b', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
2625
3588
|
}
|
|
2626
3589
|
|
|
3590
|
+
/** Set the WebGl blend mode, normally you should call setBlendMode instead
|
|
3591
|
+
* @param {Boolean} [additive=0]
|
|
3592
|
+
* @memberof WebGL */
|
|
2627
3593
|
function glSetBlendMode(additive)
|
|
2628
3594
|
{
|
|
2629
3595
|
if (!glEnable) return;
|
|
2630
3596
|
|
|
2631
|
-
if (additive != glAdditive)
|
|
2632
|
-
glFlush();
|
|
2633
|
-
|
|
2634
3597
|
// setup blending
|
|
2635
3598
|
glAdditive = additive;
|
|
2636
|
-
const destBlend = additive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
|
|
2637
|
-
glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
|
|
2638
|
-
glContext.enable(gl_BLEND);
|
|
2639
3599
|
}
|
|
2640
3600
|
|
|
3601
|
+
/** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
|
|
3602
|
+
* <br> - This may also flush the gl buffer resulting in more draw calls and worse performance
|
|
3603
|
+
* @param {WebGLTexture} [texture=glTileTexture]
|
|
3604
|
+
* @memberof WebGL */
|
|
2641
3605
|
function glSetTexture(texture=glTileTexture)
|
|
2642
3606
|
{
|
|
2643
3607
|
if (!glEnable) return;
|
|
@@ -2648,6 +3612,11 @@ function glSetTexture(texture=glTileTexture)
|
|
|
2648
3612
|
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
|
|
2649
3613
|
}
|
|
2650
3614
|
|
|
3615
|
+
/** Compile WebGL shader of the given type, will throw errors if in debug mode
|
|
3616
|
+
* @param {String} source
|
|
3617
|
+
* @param type
|
|
3618
|
+
* @return {WebGLShader}
|
|
3619
|
+
* @memberof WebGL */
|
|
2651
3620
|
function glCompileShader(source, type)
|
|
2652
3621
|
{
|
|
2653
3622
|
if (!glEnable) return;
|
|
@@ -2663,6 +3632,11 @@ function glCompileShader(source, type)
|
|
|
2663
3632
|
return shader;
|
|
2664
3633
|
}
|
|
2665
3634
|
|
|
3635
|
+
/** Create WebGL program with given shaders
|
|
3636
|
+
* @param {WebGLShader} vsSource
|
|
3637
|
+
* @param {WebGLShader} fsSource
|
|
3638
|
+
* @return {WebGLProgram}
|
|
3639
|
+
* @memberof WebGL */
|
|
2666
3640
|
function glCreateProgram(vsSource, fsSource)
|
|
2667
3641
|
{
|
|
2668
3642
|
if (!glEnable) return;
|
|
@@ -2679,6 +3653,12 @@ function glCreateProgram(vsSource, fsSource)
|
|
|
2679
3653
|
return program;
|
|
2680
3654
|
}
|
|
2681
3655
|
|
|
3656
|
+
/** Create WebGL buffer
|
|
3657
|
+
* @param bufferType
|
|
3658
|
+
* @param size
|
|
3659
|
+
* @param usage
|
|
3660
|
+
* @return {WebGLBuffer}
|
|
3661
|
+
* @memberof WebGL */
|
|
2682
3662
|
function glCreateBuffer(bufferType, size, usage)
|
|
2683
3663
|
{
|
|
2684
3664
|
if (!glEnable) return;
|
|
@@ -2690,15 +3670,19 @@ function glCreateBuffer(bufferType, size, usage)
|
|
|
2690
3670
|
return buffer;
|
|
2691
3671
|
}
|
|
2692
3672
|
|
|
3673
|
+
/** Create WebGL texture from an image and set the texture settings
|
|
3674
|
+
* @param {Image} image
|
|
3675
|
+
* @return {WebGLTexture}
|
|
3676
|
+
* @memberof WebGL */
|
|
2693
3677
|
function glCreateTexture(image)
|
|
2694
3678
|
{
|
|
2695
|
-
if (!glEnable) return;
|
|
3679
|
+
if (!glEnable || !image || !image.width) return;
|
|
2696
3680
|
|
|
2697
3681
|
// build the texture
|
|
2698
3682
|
const texture = glContext.createTexture();
|
|
2699
3683
|
glContext.bindTexture(gl_TEXTURE_2D, texture);
|
|
2700
3684
|
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
2701
|
-
|
|
3685
|
+
|
|
2702
3686
|
// use point filtering for pixelated rendering
|
|
2703
3687
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, pixelated ? gl_NEAREST : gl_LINEAR);
|
|
2704
3688
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, pixelated ? gl_NEAREST : gl_LINEAR);
|
|
@@ -2707,6 +3691,7 @@ function glCreateTexture(image)
|
|
|
2707
3691
|
return texture;
|
|
2708
3692
|
}
|
|
2709
3693
|
|
|
3694
|
+
// called automatically by engine before render
|
|
2710
3695
|
function glPreRender(width, height)
|
|
2711
3696
|
{
|
|
2712
3697
|
if (!glEnable) return;
|
|
@@ -2734,24 +3719,33 @@ function glPreRender(width, height)
|
|
|
2734
3719
|
);
|
|
2735
3720
|
}
|
|
2736
3721
|
|
|
3722
|
+
/** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
|
|
3723
|
+
* @memberof WebGL */
|
|
2737
3724
|
function glFlush()
|
|
2738
3725
|
{
|
|
2739
3726
|
if (!glEnable || !glBatchCount) return;
|
|
2740
3727
|
|
|
3728
|
+
const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
|
|
3729
|
+
glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
|
|
3730
|
+
glContext.enable(gl_BLEND);
|
|
3731
|
+
|
|
2741
3732
|
// draw all the sprites in the batch and reset the buffer
|
|
2742
3733
|
glContext.bufferSubData(gl_ARRAY_BUFFER, 0,
|
|
2743
3734
|
glPositionData.subarray(0, glBatchCount * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT));
|
|
2744
3735
|
glContext.drawArrays(gl_TRIANGLES, 0, glBatchCount * gl_VERTICES_PER_QUAD);
|
|
2745
3736
|
glBatchCount = 0;
|
|
3737
|
+
glBatchAdditive = glAdditive;
|
|
2746
3738
|
}
|
|
2747
3739
|
|
|
3740
|
+
/** Draw any sprites still in the buffer, copy to main canvas and clear
|
|
3741
|
+
* @param {CanvasRenderingContext2D} context
|
|
3742
|
+
* @param {Boolean} [forceDraw=0]
|
|
3743
|
+
* @memberof WebGL */
|
|
2748
3744
|
function glCopyToContext(context, forceDraw)
|
|
2749
3745
|
{
|
|
2750
3746
|
if (!glEnable || !glDirty) return;
|
|
2751
3747
|
|
|
2752
|
-
// draw any sprites still in the buffer, copy to main canvas and clear
|
|
2753
3748
|
glFlush();
|
|
2754
|
-
|
|
2755
3749
|
if (!glOverlay || forceDraw)
|
|
2756
3750
|
{
|
|
2757
3751
|
// do not draw/clear in overlay mode because the canvas is visible
|
|
@@ -2760,12 +3754,25 @@ function glCopyToContext(context, forceDraw)
|
|
|
2760
3754
|
}
|
|
2761
3755
|
}
|
|
2762
3756
|
|
|
2763
|
-
|
|
3757
|
+
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
3758
|
+
* @param x
|
|
3759
|
+
* @param y
|
|
3760
|
+
* @param sizeX
|
|
3761
|
+
* @param sizeY
|
|
3762
|
+
* @param [angle=0]
|
|
3763
|
+
* @param [uv0X=0]
|
|
3764
|
+
* @param [uv0Y=0]
|
|
3765
|
+
* @param [uv1X=1]
|
|
3766
|
+
* @param [uv1Y=1]
|
|
3767
|
+
* @param [rgba=0xffffffff]
|
|
3768
|
+
* @param [rgbaAdditive=0]
|
|
3769
|
+
* @memberof WebGL */
|
|
3770
|
+
function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgba=0xffffffff, rgbaAdditive=0)
|
|
2764
3771
|
{
|
|
2765
3772
|
if (!glEnable) return;
|
|
2766
|
-
|
|
2767
|
-
// flush if there is no room for more verts
|
|
2768
|
-
if (glBatchCount == gl_MAX_BATCH)
|
|
3773
|
+
|
|
3774
|
+
// flush if there is no room for more verts or if different blend mode
|
|
3775
|
+
if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
|
|
2769
3776
|
glFlush();
|
|
2770
3777
|
|
|
2771
3778
|
// setup 2 triangles to form a quad
|
|
@@ -2774,69 +3781,45 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
|
|
|
2774
3781
|
|
|
2775
3782
|
// vertex 0
|
|
2776
3783
|
glPositionData[offset++] = angle;
|
|
2777
|
-
glPositionData[offset++] = x;
|
|
2778
|
-
glPositionData[offset++] =
|
|
2779
|
-
glPositionData[offset++] =
|
|
2780
|
-
|
|
2781
|
-
glPositionData[offset++] = uv0X;
|
|
2782
|
-
glPositionData[offset++] = uv1Y;
|
|
2783
|
-
glColorData[offset++] = rgba;
|
|
2784
|
-
glColorData[offset++] = rgbaAdditive;
|
|
3784
|
+
glPositionData[offset++] = x; glPositionData[offset++] = y;
|
|
3785
|
+
glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
|
|
3786
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3787
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
2785
3788
|
|
|
2786
3789
|
// vertex 1
|
|
2787
3790
|
glPositionData[offset++] = angle;
|
|
2788
|
-
glPositionData[offset++] = x;
|
|
2789
|
-
glPositionData[offset++] =
|
|
2790
|
-
glPositionData[offset++] =
|
|
2791
|
-
|
|
2792
|
-
glPositionData[offset++] = uv1X;
|
|
2793
|
-
glPositionData[offset++] = uv0Y;
|
|
2794
|
-
glColorData[offset++] = rgba;
|
|
2795
|
-
glColorData[offset++] = rgbaAdditive;
|
|
3791
|
+
glPositionData[offset++] = x; glPositionData[offset++] = y;
|
|
3792
|
+
glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
|
|
3793
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3794
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
2796
3795
|
|
|
2797
3796
|
// vertex 2
|
|
2798
3797
|
glPositionData[offset++] = angle;
|
|
2799
|
-
glPositionData[offset++] = x;
|
|
2800
|
-
glPositionData[offset++] =
|
|
2801
|
-
glPositionData[offset++] =
|
|
2802
|
-
|
|
2803
|
-
glPositionData[offset++] = uv0X;
|
|
2804
|
-
glPositionData[offset++] = uv0Y;
|
|
2805
|
-
glColorData[offset++] = rgba;
|
|
2806
|
-
glColorData[offset++] = rgbaAdditive;
|
|
3798
|
+
glPositionData[offset++] = x; glPositionData[offset++] = y;
|
|
3799
|
+
glPositionData[offset++] = -sizeX; glPositionData[offset++] = sizeY;
|
|
3800
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
|
|
3801
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
2807
3802
|
|
|
2808
3803
|
// vertex 0
|
|
2809
3804
|
glPositionData[offset++] = angle;
|
|
2810
|
-
glPositionData[offset++] = x;
|
|
2811
|
-
glPositionData[offset++] =
|
|
2812
|
-
glPositionData[offset++] =
|
|
2813
|
-
|
|
2814
|
-
glPositionData[offset++] = uv0X;
|
|
2815
|
-
glPositionData[offset++] = uv1Y;
|
|
2816
|
-
glColorData[offset++] = rgba;
|
|
2817
|
-
glColorData[offset++] = rgbaAdditive;
|
|
3805
|
+
glPositionData[offset++] = x; glPositionData[offset++] = y;
|
|
3806
|
+
glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
|
|
3807
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3808
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
2818
3809
|
|
|
2819
3810
|
// vertex 3
|
|
2820
3811
|
glPositionData[offset++] = angle;
|
|
2821
|
-
glPositionData[offset++] = x;
|
|
2822
|
-
glPositionData[offset++] =
|
|
2823
|
-
glPositionData[offset++] =
|
|
2824
|
-
|
|
2825
|
-
glPositionData[offset++] = uv1X;
|
|
2826
|
-
glPositionData[offset++] = uv1Y;
|
|
2827
|
-
glColorData[offset++] = rgba;
|
|
2828
|
-
glColorData[offset++] = rgbaAdditive;
|
|
3812
|
+
glPositionData[offset++] = x; glPositionData[offset++] = y;
|
|
3813
|
+
glPositionData[offset++] = sizeX; glPositionData[offset++] = -sizeY;
|
|
3814
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
|
|
3815
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
2829
3816
|
|
|
2830
3817
|
// vertex 1
|
|
2831
3818
|
glPositionData[offset++] = angle;
|
|
2832
|
-
glPositionData[offset++] = x;
|
|
2833
|
-
glPositionData[offset++] =
|
|
2834
|
-
glPositionData[offset++] =
|
|
2835
|
-
|
|
2836
|
-
glPositionData[offset++] = uv1X;
|
|
2837
|
-
glPositionData[offset++] = uv0Y;
|
|
2838
|
-
glColorData[offset++] = rgba;
|
|
2839
|
-
glColorData[offset++] = rgbaAdditive;
|
|
3819
|
+
glPositionData[offset++] = x; glPositionData[offset++] = y;
|
|
3820
|
+
glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
|
|
3821
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3822
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
2840
3823
|
}
|
|
2841
3824
|
|
|
2842
3825
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -2871,3 +3854,273 @@ gl_VERTICES_PER_QUAD = 6,
|
|
|
2871
3854
|
gl_INDICIES_PER_VERT = 9,
|
|
2872
3855
|
gl_MAX_BATCH = 1<<16,
|
|
2873
3856
|
gl_VERTEX_BYTE_STRIDE = 4 + (4 * 2) * 3 + (4) * 2; // float + vec2 * 3 + (char * 4) * 2
|
|
3857
|
+
/*
|
|
3858
|
+
LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
3859
|
+
MIT License - Copyright 2021 Frank Force
|
|
3860
|
+
|
|
3861
|
+
Engine Features
|
|
3862
|
+
- Object oriented system with base class engine object
|
|
3863
|
+
- Base class object handles update, physics, collision, rendering, etc
|
|
3864
|
+
- Engine helper classes and functions like Vector2, Color, and Timer
|
|
3865
|
+
- Super fast rendering system for tile sheets
|
|
3866
|
+
- Sound effects audio with zzfx and music with zzfxm
|
|
3867
|
+
- Input processing system with gamepad and touchscreen support
|
|
3868
|
+
- Tile layer rendering and collision system
|
|
3869
|
+
- Particle effect system
|
|
3870
|
+
- Medal system tracks and displays achievements
|
|
3871
|
+
- Debug tools and debug rendering system
|
|
3872
|
+
- Call engineInit() to start it up!
|
|
3873
|
+
*/
|
|
3874
|
+
|
|
3875
|
+
'use strict';
|
|
3876
|
+
|
|
3877
|
+
/** Name of engine */
|
|
3878
|
+
const engineName = 'LittleJS';
|
|
3879
|
+
|
|
3880
|
+
/** Version of engine */
|
|
3881
|
+
const engineVersion = '1.1.8';
|
|
3882
|
+
|
|
3883
|
+
/** Frames per second to update objects
|
|
3884
|
+
* @default */
|
|
3885
|
+
const FPS = 60;
|
|
3886
|
+
|
|
3887
|
+
/** How many seconds each frame lasts, engine uses a fixed time step
|
|
3888
|
+
* @default 1/60 */
|
|
3889
|
+
const timeDelta = 1/FPS;
|
|
3890
|
+
|
|
3891
|
+
/** Array containing all engine objects */
|
|
3892
|
+
let engineObjects = [];
|
|
3893
|
+
|
|
3894
|
+
/** Array containing only objects that are set to collide with other objects (for optimization) */
|
|
3895
|
+
let engineCollideObjects = [];
|
|
3896
|
+
|
|
3897
|
+
/** Current update frame, used to calculate time */
|
|
3898
|
+
let frame = 0;
|
|
3899
|
+
|
|
3900
|
+
/** Current engine time since start in seconds, derived from frame */
|
|
3901
|
+
let time = 0;
|
|
3902
|
+
|
|
3903
|
+
/** Actual clock time since start in seconds (not affected by pause or frame rate clamping) */
|
|
3904
|
+
let timeReal = 0;
|
|
3905
|
+
|
|
3906
|
+
/** Is the game paused? Causes time and objects to not be updated. */
|
|
3907
|
+
let paused = 0;
|
|
3908
|
+
|
|
3909
|
+
// Engine internal variables not exposed to documentation
|
|
3910
|
+
let frameTimeLastMS = 0, frameTimeBufferMS = 0, debugFPS = 0,
|
|
3911
|
+
shrinkTilesX, shrinkTilesY, drawCount, tileImageSize, tileImageSizeInverse;
|
|
3912
|
+
|
|
3913
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3914
|
+
|
|
3915
|
+
/** Start up LittleJS engine with your callback functions
|
|
3916
|
+
* @param {Function} gameInit - Called once after the engine starts up, setup the game
|
|
3917
|
+
* @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
|
|
3918
|
+
* @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
|
|
3919
|
+
* @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
|
|
3920
|
+
* @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
|
|
3921
|
+
* @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading
|
|
3922
|
+
*/
|
|
3923
|
+
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
|
|
3924
|
+
{
|
|
3925
|
+
// init engine when tiles load or fail to load
|
|
3926
|
+
tileImage.onerror = tileImage.onload = ()=>
|
|
3927
|
+
{
|
|
3928
|
+
// save tile image info
|
|
3929
|
+
tileImageSizeInverse = vec2(1).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
3930
|
+
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
3931
|
+
shrinkTilesX = tileBleedShrinkFix/tileImageSize.x;
|
|
3932
|
+
shrinkTilesY = tileBleedShrinkFix/tileImageSize.y;
|
|
3933
|
+
|
|
3934
|
+
// setup html
|
|
3935
|
+
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
3936
|
+
document.body.style = 'margin:0;overflow:hidden;background:#000';
|
|
3937
|
+
mainCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)' +
|
|
3938
|
+
(pixelated ? ';image-rendering:crisp-edges;image-rendering:pixelated' : ''); // pixelated rendering
|
|
3939
|
+
mainContext = mainCanvas.getContext('2d');
|
|
3940
|
+
|
|
3941
|
+
// init stuff and start engine
|
|
3942
|
+
debugInit();
|
|
3943
|
+
glInit();
|
|
3944
|
+
|
|
3945
|
+
// create overlay canvas for hud to appear above gl canvas
|
|
3946
|
+
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
3947
|
+
overlayCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
3948
|
+
overlayContext = overlayCanvas.getContext('2d');
|
|
3949
|
+
|
|
3950
|
+
gameInit();
|
|
3951
|
+
engineUpdate();
|
|
3952
|
+
};
|
|
3953
|
+
|
|
3954
|
+
// main update loop
|
|
3955
|
+
const engineUpdate = (frameTimeMS=0)=>
|
|
3956
|
+
{
|
|
3957
|
+
requestAnimationFrame(engineUpdate);
|
|
3958
|
+
|
|
3959
|
+
// update time keeping
|
|
3960
|
+
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
3961
|
+
frameTimeLastMS = frameTimeMS;
|
|
3962
|
+
if (debug || showWatermark)
|
|
3963
|
+
debugFPS = lerp(.05, 1e3/(frameTimeDeltaMS||1), debugFPS);
|
|
3964
|
+
if (debug)
|
|
3965
|
+
frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
|
|
3966
|
+
timeReal += frameTimeDeltaMS / 1e3;
|
|
3967
|
+
frameTimeBufferMS = min(frameTimeBufferMS + !paused * frameTimeDeltaMS, 50); // clamp incase of slow framerate
|
|
3968
|
+
|
|
3969
|
+
if (paused)
|
|
3970
|
+
{
|
|
3971
|
+
// do post update even when paused
|
|
3972
|
+
inputUpdate();
|
|
3973
|
+
debugUpdate();
|
|
3974
|
+
gameUpdatePost();
|
|
3975
|
+
inputUpdatePost();
|
|
3976
|
+
}
|
|
3977
|
+
else
|
|
3978
|
+
{
|
|
3979
|
+
// apply time delta smoothing, improves smoothness of framerate in some browsers
|
|
3980
|
+
let deltaSmooth = 0;
|
|
3981
|
+
if (frameTimeBufferMS < 0 && frameTimeBufferMS > -9)
|
|
3982
|
+
{
|
|
3983
|
+
// force an update each frame if time is close enough (not just a fast refresh rate)
|
|
3984
|
+
deltaSmooth = frameTimeBufferMS;
|
|
3985
|
+
frameTimeBufferMS = 0;
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
// update multiple frames if necessary in case of slow framerate
|
|
3989
|
+
for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / FPS)
|
|
3990
|
+
{
|
|
3991
|
+
// update game and objects
|
|
3992
|
+
inputUpdate();
|
|
3993
|
+
gameUpdate();
|
|
3994
|
+
engineObjectsUpdate();
|
|
3995
|
+
|
|
3996
|
+
// do post update
|
|
3997
|
+
debugUpdate();
|
|
3998
|
+
gameUpdatePost();
|
|
3999
|
+
inputUpdatePost();
|
|
4000
|
+
}
|
|
4001
|
+
|
|
4002
|
+
// add the time smoothing back in
|
|
4003
|
+
frameTimeBufferMS += deltaSmooth;
|
|
4004
|
+
}
|
|
4005
|
+
|
|
4006
|
+
if (fixedSize.x)
|
|
4007
|
+
{
|
|
4008
|
+
// clear set fixed size
|
|
4009
|
+
mainCanvas.width = fixedSize.x;
|
|
4010
|
+
mainCanvas.height = fixedSize.y;
|
|
4011
|
+
|
|
4012
|
+
// fit to window by adding space on top or bottom if necessary
|
|
4013
|
+
const aspect = innerWidth / innerHeight;
|
|
4014
|
+
const fixedAspect = mainCanvas.width / mainCanvas.height;
|
|
4015
|
+
mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
|
|
4016
|
+
mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
4017
|
+
if (glCanvas)
|
|
4018
|
+
{
|
|
4019
|
+
glCanvas.style.width = mainCanvas.style.width;
|
|
4020
|
+
glCanvas.style.height = mainCanvas.style.height;
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
else
|
|
4024
|
+
{
|
|
4025
|
+
// clear and set size to same as window
|
|
4026
|
+
mainCanvas.width = min(innerWidth, maxSize.x);
|
|
4027
|
+
mainCanvas.height = min(innerHeight, maxSize.y);
|
|
4028
|
+
}
|
|
4029
|
+
|
|
4030
|
+
// save canvas size and clear overlay canvas
|
|
4031
|
+
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
4032
|
+
mainContext.imageSmoothingEnabled = !pixelated; // disable smoothing for pixel art
|
|
4033
|
+
|
|
4034
|
+
// render sort then render while removing destroyed objects
|
|
4035
|
+
glPreRender(mainCanvas.width, mainCanvas.height);
|
|
4036
|
+
gameRender();
|
|
4037
|
+
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
4038
|
+
for (const o of engineObjects)
|
|
4039
|
+
o.destroyed || o.render();
|
|
4040
|
+
gameRenderPost();
|
|
4041
|
+
medalsRender();
|
|
4042
|
+
debugRender();
|
|
4043
|
+
glCopyToContext(mainContext);
|
|
4044
|
+
|
|
4045
|
+
if (showWatermark)
|
|
4046
|
+
{
|
|
4047
|
+
// update fps
|
|
4048
|
+
overlayContext.textAlign = 'right';
|
|
4049
|
+
overlayContext.textBaseline = 'top';
|
|
4050
|
+
overlayContext.font = '1em monospace';
|
|
4051
|
+
overlayContext.fillStyle = '#000';
|
|
4052
|
+
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
4053
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + debugFPS.toFixed(1);
|
|
4054
|
+
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
4055
|
+
overlayContext.fillStyle = '#fff';
|
|
4056
|
+
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
4057
|
+
drawCount = 0;
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
|
|
4061
|
+
// set tile image source to load the image and start the engine
|
|
4062
|
+
tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
|
|
4063
|
+
}
|
|
4064
|
+
|
|
4065
|
+
|
|
4066
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
4067
|
+
|
|
4068
|
+
/** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
|
|
4069
|
+
function engineObjectsUpdate()
|
|
4070
|
+
{
|
|
4071
|
+
// recursive object update
|
|
4072
|
+
const updateObject = (o)=>
|
|
4073
|
+
{
|
|
4074
|
+
if (!o.destroyed)
|
|
4075
|
+
{
|
|
4076
|
+
o.update();
|
|
4077
|
+
for (const child of o.children)
|
|
4078
|
+
updateObject(child);
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
for (const o of engineObjects)
|
|
4082
|
+
o.parent || updateObject(o);
|
|
4083
|
+
|
|
4084
|
+
// remove destroyed objects
|
|
4085
|
+
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
4086
|
+
engineCollideObjects = engineCollideObjects.filter(o=>!o.destroyed);
|
|
4087
|
+
|
|
4088
|
+
// increment frame and update time
|
|
4089
|
+
time = ++frame / FPS;
|
|
4090
|
+
}
|
|
4091
|
+
|
|
4092
|
+
/** Detroy and remove all objects that are not persistent or descendants of a persistent object */
|
|
4093
|
+
function engineObjectsDestroy()
|
|
4094
|
+
{
|
|
4095
|
+
for (const o of engineObjects)
|
|
4096
|
+
o.persistent || o.parent || o.destroy();
|
|
4097
|
+
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
4098
|
+
}
|
|
4099
|
+
|
|
4100
|
+
/** Triggers a callback for each object within a given area
|
|
4101
|
+
* @param {Vector2} [pos] - Center of test area
|
|
4102
|
+
* @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
|
|
4103
|
+
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
4104
|
+
* @param {Array} [objects=engineObjects] - List of objects to check */
|
|
4105
|
+
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
4106
|
+
{
|
|
4107
|
+
if (!pos)
|
|
4108
|
+
{
|
|
4109
|
+
// all objects
|
|
4110
|
+
for (const o of objects)
|
|
4111
|
+
callbackFunction(o);
|
|
4112
|
+
}
|
|
4113
|
+
else if (size.x != undefined)
|
|
4114
|
+
{
|
|
4115
|
+
// aabb test
|
|
4116
|
+
for (const o of objects)
|
|
4117
|
+
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
4118
|
+
}
|
|
4119
|
+
else
|
|
4120
|
+
{
|
|
4121
|
+
// circle test
|
|
4122
|
+
const sizeSquared = size*size;
|
|
4123
|
+
for (const o of objects)
|
|
4124
|
+
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
|
4125
|
+
}
|
|
4126
|
+
}
|