littlejsengine 1.6.0 → 1.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -34
- package/build/littlejs.d.ts +104 -92
- package/build/littlejs.esm.js +414 -342
- package/build/littlejs.esm.min.js +1 -1
- package/build/littlejs.js +365 -296
- package/build/littlejs.min.js +1 -1
- package/build/littlejs.release.js +359 -291
- package/examples/breakout/game.js +5 -0
- package/examples/breakout/gameObjects.js +41 -48
- package/examples/breakout/index.html +3 -3
- package/examples/breakoutTutorial/README.md +514 -0
- package/examples/breakoutTutorial/game.js +181 -0
- package/examples/breakoutTutorial/images/1.png +0 -0
- package/examples/breakoutTutorial/images/10.png +0 -0
- package/examples/breakoutTutorial/images/11.png +0 -0
- package/examples/breakoutTutorial/images/2.png +0 -0
- package/examples/breakoutTutorial/images/3.png +0 -0
- package/examples/breakoutTutorial/images/4.png +0 -0
- package/examples/breakoutTutorial/images/5.png +0 -0
- package/examples/breakoutTutorial/images/6.png +0 -0
- package/examples/breakoutTutorial/images/7.png +0 -0
- package/examples/breakoutTutorial/images/8.png +0 -0
- package/examples/breakoutTutorial/images/9.png +0 -0
- package/examples/breakoutTutorial/index.html +10 -0
- package/examples/empty/game.js +10 -0
- package/examples/favicon.png +0 -0
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +1 -1
- package/examples/platformer/gameLevel.js +1 -1
- package/examples/platformer/gamePlayer.js +1 -1
- package/examples/platformer/index.html +6 -6
- package/examples/puzzle/game.js +1 -1
- package/examples/puzzle/index.html +2 -2
- package/examples/starter/build.bat +3 -2
- package/examples/starter/game.js +9 -9
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +1 -1
- package/examples/typescript/game.js +89 -89
- package/examples/typescript/game.ts +10 -10
- package/examples/typescript/index.html +1 -1
- package/package.json +4 -4
- package/src/engine.js +29 -31
- package/src/engineAudio.js +53 -29
- package/src/engineBuild.bat +3 -1
- package/src/engineDebug.js +25 -24
- package/src/engineDraw.js +43 -39
- package/src/engineExport.js +49 -46
- package/src/engineInput.js +83 -74
- package/src/engineMedals.js +31 -8
- package/src/engineObject.js +25 -25
- package/src/engineParticles.js +3 -6
- package/src/engineRelease.js +19 -19
- package/src/engineSettings.js +4 -4
- package/src/engineTileLayer.js +18 -14
- package/src/engineUtilities.js +43 -34
- package/src/engineWebGL.js +8 -8
package/build/littlejs.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
/*
|
|
2
|
-
LittleJS - Debug Build
|
|
3
|
-
MIT License - Copyright 2021 Frank Force
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
2
|
* LittleJS Debug System
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
3
|
+
* - Press ~ to show debug overlay with mouse pick
|
|
4
|
+
* - Number keys toggle debug functions
|
|
5
|
+
* - +/- apply time scale
|
|
6
|
+
* - Debug primitive rendering
|
|
7
|
+
* - Save a 2d canvas as an image
|
|
13
8
|
* @namespace Debug
|
|
14
9
|
*/
|
|
15
10
|
|
|
@@ -45,6 +40,12 @@ let showWatermark = 1;
|
|
|
45
40
|
* @memberof Debug */
|
|
46
41
|
let godMode = 0;
|
|
47
42
|
|
|
43
|
+
/** Key code used to toggle debug mode, Esc by default
|
|
44
|
+
* @type {Boolean}
|
|
45
|
+
* @default
|
|
46
|
+
* @memberof Debug */
|
|
47
|
+
let debugKey = 27;
|
|
48
|
+
|
|
48
49
|
// Engine internal variables not exposed to documentation
|
|
49
50
|
let debugPrimitives = [], debugOverlay = 0, debugPhysics = 0, debugRaycast = 0,
|
|
50
51
|
debugParticles = 0, debugGamepads = 0, debugMedals = 0, debugTakeScreenshot, downloadLink;
|
|
@@ -56,7 +57,7 @@ debugParticles = 0, debugGamepads = 0, debugMedals = 0, debugTakeScreenshot, dow
|
|
|
56
57
|
* @param {Boolean} assertion
|
|
57
58
|
* @param {Object} output
|
|
58
59
|
* @memberof Debug */
|
|
59
|
-
|
|
60
|
+
function ASSERT(...assert) { enableAsserts && console.assert(...assert); }
|
|
60
61
|
|
|
61
62
|
/** Draw a debug rectangle in world space
|
|
62
63
|
* @param {Vector2} pos
|
|
@@ -66,7 +67,7 @@ const ASSERT = enableAsserts ? (...assert)=> console.assert(...assert) : ()=>{};
|
|
|
66
67
|
* @param {Number} [angle=0]
|
|
67
68
|
* @param {Boolean} [fill=false]
|
|
68
69
|
* @memberof Debug */
|
|
69
|
-
|
|
70
|
+
function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
|
|
70
71
|
{
|
|
71
72
|
ASSERT(typeof color == 'string'); // pass in regular html strings as colors
|
|
72
73
|
debugPrimitives.push({pos, size:vec2(size), color, time:new Timer(time), angle, fill});
|
|
@@ -79,7 +80,7 @@ const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)=
|
|
|
79
80
|
* @param {Number} [time=0]
|
|
80
81
|
* @param {Boolean} [fill=false]
|
|
81
82
|
* @memberof Debug */
|
|
82
|
-
|
|
83
|
+
function debugCircle(pos, radius=0, color='#fff', time=0, fill=false)
|
|
83
84
|
{
|
|
84
85
|
ASSERT(typeof color == 'string'); // pass in regular html strings as colors
|
|
85
86
|
debugPrimitives.push({pos, size:radius, color, time:new Timer(time), angle:0, fill});
|
|
@@ -91,7 +92,7 @@ const debugCircle = (pos, radius=0, color='#fff', time=0, fill=false)=>
|
|
|
91
92
|
* @param {Number} [time=0]
|
|
92
93
|
* @param {Number} [angle=0]
|
|
93
94
|
* @memberof Debug */
|
|
94
|
-
|
|
95
|
+
function debugPoint(pos, color, time, angle) {debugRect(pos, 0, color, time, angle);}
|
|
95
96
|
|
|
96
97
|
/** Draw a debug line in world space
|
|
97
98
|
* @param {Vector2} posA
|
|
@@ -100,7 +101,7 @@ const debugPoint = (pos, color, time, angle)=> debugRect(pos, 0, color, time, an
|
|
|
100
101
|
* @param {Number} [thickness=.1]
|
|
101
102
|
* @param {Number} [time=0]
|
|
102
103
|
* @memberof Debug */
|
|
103
|
-
|
|
104
|
+
function debugLine(posA, posB, color, thickness=.1, time)
|
|
104
105
|
{
|
|
105
106
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
106
107
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
@@ -114,7 +115,7 @@ const debugLine = (posA, posB, color, thickness=.1, time)=>
|
|
|
114
115
|
* @param {Vector2} sizeB
|
|
115
116
|
* @param {String} [color='#fff']
|
|
116
117
|
* @memberof Debug */
|
|
117
|
-
|
|
118
|
+
function debugAABB(pA, sA, pB, sB, color)
|
|
118
119
|
{
|
|
119
120
|
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));
|
|
120
121
|
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));
|
|
@@ -130,7 +131,7 @@ const debugAABB = (pA, sA, pB, sB, color)=>
|
|
|
130
131
|
* @param {Number} [angle=0]
|
|
131
132
|
* @param {String} [font='monospace']
|
|
132
133
|
* @memberof Debug */
|
|
133
|
-
|
|
134
|
+
function debugText(text, pos, size=1, color='#fff', time=0, angle=0, font='monospace')
|
|
134
135
|
{
|
|
135
136
|
ASSERT(typeof color == 'string'); // pass in regular html strings as colors
|
|
136
137
|
debugPrimitives.push({text, pos, size, color, time:new Timer(time), angle, font});
|
|
@@ -138,13 +139,13 @@ const debugText = (text, pos, size=1, color='#fff', time=0, angle=0, font='monos
|
|
|
138
139
|
|
|
139
140
|
/** Clear all debug primitives in the list
|
|
140
141
|
* @memberof Debug */
|
|
141
|
-
|
|
142
|
+
function debugClear() { debugPrimitives = []; }
|
|
142
143
|
|
|
143
144
|
/** Save a canvas to disk
|
|
144
145
|
* @param {HTMLCanvasElement} canvas
|
|
145
146
|
* @param {String} [filename]
|
|
146
147
|
* @memberof Debug */
|
|
147
|
-
|
|
148
|
+
function debugSaveCanvas(canvas, filename = engineName + '.png')
|
|
148
149
|
{
|
|
149
150
|
downloadLink.download = 'screenshot.png';
|
|
150
151
|
downloadLink.href = canvas.toDataURL('image/png').replace('image/png','image/octet-stream');
|
|
@@ -154,19 +155,19 @@ const debugSaveCanvas = (canvas, filename = engineName + '.png') =>
|
|
|
154
155
|
///////////////////////////////////////////////////////////////////////////////
|
|
155
156
|
// Engine debug function (called automatically)
|
|
156
157
|
|
|
157
|
-
|
|
158
|
+
function debugInit()
|
|
158
159
|
{
|
|
159
160
|
// create link for saving screenshots
|
|
160
161
|
document.body.appendChild(downloadLink = document.createElement('a'));
|
|
161
162
|
downloadLink.style.display = 'none';
|
|
162
163
|
}
|
|
163
164
|
|
|
164
|
-
|
|
165
|
+
function debugUpdate()
|
|
165
166
|
{
|
|
166
167
|
if (!debug)
|
|
167
168
|
return;
|
|
168
169
|
|
|
169
|
-
if (keyWasPressed(
|
|
170
|
+
if (keyWasPressed(debugKey)) // Esc
|
|
170
171
|
debugOverlay = !debugOverlay;
|
|
171
172
|
if (debugOverlay)
|
|
172
173
|
{
|
|
@@ -189,7 +190,7 @@ const debugUpdate = ()=>
|
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
192
|
|
|
192
|
-
|
|
193
|
+
function debugRender()
|
|
193
194
|
{
|
|
194
195
|
glCopyToContext(mainContext);
|
|
195
196
|
|
|
@@ -351,7 +352,7 @@ const debugRender = ()=>
|
|
|
351
352
|
overlayContext.fillText('Time: ' + formatTime(time), x, y += h);
|
|
352
353
|
overlayContext.fillText('---------', x, y += h);
|
|
353
354
|
overlayContext.fillStyle = '#f00';
|
|
354
|
-
overlayContext.fillText('
|
|
355
|
+
overlayContext.fillText('ESC: Debug Overlay', x, y += h);
|
|
355
356
|
overlayContext.fillStyle = debugPhysics ? '#f00' : '#fff';
|
|
356
357
|
overlayContext.fillText('1: Debug Physics', x, y += h);
|
|
357
358
|
overlayContext.fillStyle = debugParticles ? '#f00' : '#fff';
|
|
@@ -393,10 +394,10 @@ const debugRender = ()=>
|
|
|
393
394
|
}
|
|
394
395
|
/**
|
|
395
396
|
* LittleJS Utility Classes and Functions
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
397
|
+
* - General purpose math library
|
|
398
|
+
* - Vector2 - fast, simple, easy 2D vector class
|
|
399
|
+
* - Color - holds a rgba color with some math functions
|
|
400
|
+
* - Timer - tracks time automatically
|
|
400
401
|
* @namespace Utilities
|
|
401
402
|
*/
|
|
402
403
|
|
|
@@ -412,34 +413,34 @@ const PI = Math.PI;
|
|
|
412
413
|
* @param {Number} value
|
|
413
414
|
* @return {Number}
|
|
414
415
|
* @memberof Utilities */
|
|
415
|
-
|
|
416
|
+
function abs(a) { return a < 0 ? -a : a; }
|
|
416
417
|
|
|
417
418
|
/** Returns lowest of two values passed in
|
|
418
419
|
* @param {Number} valueA
|
|
419
420
|
* @param {Number} valueB
|
|
420
421
|
* @return {Number}
|
|
421
422
|
* @memberof Utilities */
|
|
422
|
-
|
|
423
|
+
function min(a, b) { return a < b ? a : b; }
|
|
423
424
|
|
|
424
425
|
/** Returns highest of two values passed in
|
|
425
426
|
* @param {Number} valueA
|
|
426
427
|
* @param {Number} valueB
|
|
427
428
|
* @return {Number}
|
|
428
429
|
* @memberof Utilities */
|
|
429
|
-
|
|
430
|
+
function max(a, b) { return a > b ? a : b; }
|
|
430
431
|
|
|
431
432
|
/** Returns the sign of value passed in (also returns 1 if 0)
|
|
432
433
|
* @param {Number} value
|
|
433
434
|
* @return {Number}
|
|
434
435
|
* @memberof Utilities */
|
|
435
|
-
|
|
436
|
+
function sign(a) { return a < 0 ? -1 : 1; }
|
|
436
437
|
|
|
437
438
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
438
439
|
* @param {Number} dividend
|
|
439
440
|
* @param {Number} [divisor=1]
|
|
440
441
|
* @return {Number}
|
|
441
442
|
* @memberof Utilities */
|
|
442
|
-
|
|
443
|
+
function mod(a, b=1) { return ((a % b) + b) % b; }
|
|
443
444
|
|
|
444
445
|
/** Clamps the value beween max and min
|
|
445
446
|
* @param {Number} value
|
|
@@ -447,7 +448,8 @@ const mod = (a, b=1)=> ((a % b) + b) % b;
|
|
|
447
448
|
* @param {Number} [max=1]
|
|
448
449
|
* @return {Number}
|
|
449
450
|
* @memberof Utilities */
|
|
450
|
-
|
|
451
|
+
function clamp(v, min=0, max=1)
|
|
452
|
+
{ return v < min ? min : v > max ? max : v; }
|
|
451
453
|
|
|
452
454
|
/** Returns what percentage the value is between max and min
|
|
453
455
|
* @param {Number} value
|
|
@@ -455,7 +457,8 @@ const clamp = (v, min=0, max=1)=> v < min ? min : v > max ? max : v;
|
|
|
455
457
|
* @param {Number} [max=1]
|
|
456
458
|
* @return {Number}
|
|
457
459
|
* @memberof Utilities */
|
|
458
|
-
|
|
460
|
+
function percent(v, min=0, max=1)
|
|
461
|
+
{ return max-min ? clamp((v-min) / (max-min)) : 0; }
|
|
459
462
|
|
|
460
463
|
/** Linearly interpolates the percent value between max and min
|
|
461
464
|
* @param {Number} percent
|
|
@@ -463,19 +466,19 @@ const percent = (v, min=0, max=1)=> max-min ? clamp((v-min) / (max-min)) : 0;
|
|
|
463
466
|
* @param {Number} [max=1]
|
|
464
467
|
* @return {Number}
|
|
465
468
|
* @memberof Utilities */
|
|
466
|
-
|
|
469
|
+
function lerp(p, min=0, max=1){ return min + clamp(p) * (max-min); }
|
|
467
470
|
|
|
468
471
|
/** Applies smoothstep function to the percentage value
|
|
469
472
|
* @param {Number} value
|
|
470
473
|
* @return {Number}
|
|
471
474
|
* @memberof Utilities */
|
|
472
|
-
|
|
475
|
+
function smoothStep(p) { return p * p * (3 - 2 * p); }
|
|
473
476
|
|
|
474
477
|
/** Returns the nearest power of two not less then the value
|
|
475
478
|
* @param {Number} value
|
|
476
479
|
* @return {Number}
|
|
477
480
|
* @memberof Utilities */
|
|
478
|
-
|
|
481
|
+
function nearestPowerOfTwo(v) { return 2**Math.ceil(Math.log2(v)); }
|
|
479
482
|
|
|
480
483
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
481
484
|
* @param {Vector2} pointA - Center of box A
|
|
@@ -484,7 +487,8 @@ const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
|
|
|
484
487
|
* @param {Vector2} [sizeB] - Size of box B
|
|
485
488
|
* @return {Boolean} - True if overlapping
|
|
486
489
|
* @memberof Utilities */
|
|
487
|
-
|
|
490
|
+
function isOverlapping(pA, sA, pB, sB)
|
|
491
|
+
{ return abs(pA.x - pB.x)*2 < sA.x + sB.x && abs(pA.y - pB.y)*2 < sA.y + sB.y; }
|
|
488
492
|
|
|
489
493
|
/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
|
|
490
494
|
* @param {Number} [frequency=1] - Frequency of the wave in Hz
|
|
@@ -492,13 +496,14 @@ const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x && abs
|
|
|
492
496
|
* @param {Number} [t=time] - Value to use for time of the wave
|
|
493
497
|
* @return {Number} - Value waving between 0 and amplitude
|
|
494
498
|
* @memberof Utilities */
|
|
495
|
-
|
|
499
|
+
function wave(frequency=1, amplitude=1, t=time)
|
|
500
|
+
{ return amplitude/2 * (1 - Math.cos(t*frequency*2*PI)); }
|
|
496
501
|
|
|
497
502
|
/** Formats seconds to mm:ss style for display purposes
|
|
498
503
|
* @param {Number} t - time in seconds
|
|
499
504
|
* @return {String}
|
|
500
505
|
* @memberof Utilities */
|
|
501
|
-
|
|
506
|
+
function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
|
|
502
507
|
|
|
503
508
|
///////////////////////////////////////////////////////////////////////////////
|
|
504
509
|
|
|
@@ -510,32 +515,33 @@ const formatTime = (t)=> (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0);
|
|
|
510
515
|
* @param {Number} [valueB=0]
|
|
511
516
|
* @return {Number}
|
|
512
517
|
* @memberof Random */
|
|
513
|
-
|
|
518
|
+
function rand(a=1, b=0) { return b + (a-b)*Math.random(); }
|
|
514
519
|
|
|
515
520
|
/** Returns a floored random value the two values passed in
|
|
516
521
|
* @param {Number} [valueA=1]
|
|
517
522
|
* @param {Number} [valueB=0]
|
|
518
523
|
* @return {Number}
|
|
519
524
|
* @memberof Random */
|
|
520
|
-
|
|
525
|
+
function randInt(a=1, b=0) { return rand(a,b)|0; }
|
|
521
526
|
|
|
522
527
|
/** Randomly returns either -1 or 1
|
|
523
528
|
* @return {Number}
|
|
524
529
|
* @memberof Random */
|
|
525
|
-
|
|
530
|
+
function randSign() { return randInt(2) * 2 - 1; }
|
|
526
531
|
|
|
527
532
|
/** Returns a random Vector2 within a circular shape
|
|
528
533
|
* @param {Number} [radius=1]
|
|
529
534
|
* @param {Number} [minRadius=0]
|
|
530
535
|
* @return {Vector2}
|
|
531
536
|
* @memberof Random */
|
|
532
|
-
|
|
537
|
+
function randInCircle(radius=1, minRadius=0)
|
|
538
|
+
{ return radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
|
|
533
539
|
|
|
534
540
|
/** Returns a random Vector2 with the passed in length
|
|
535
541
|
* @param {Number} [length=1]
|
|
536
542
|
* @return {Vector2}
|
|
537
543
|
* @memberof Random */
|
|
538
|
-
|
|
544
|
+
function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
|
|
539
545
|
|
|
540
546
|
/** Returns a random color between the two passed in colors, combine components if linear
|
|
541
547
|
* @param {Color} [colorA=Color()]
|
|
@@ -543,8 +549,8 @@ const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
|
|
|
543
549
|
* @param {Boolean} [linear]
|
|
544
550
|
* @return {Color}
|
|
545
551
|
* @memberof Random */
|
|
546
|
-
|
|
547
|
-
|
|
552
|
+
function randColor(cA = new Color, cB = new Color(0,0,0,1), linear)
|
|
553
|
+
{ return 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)); }
|
|
548
554
|
|
|
549
555
|
/** Seed used by the randSeeded function
|
|
550
556
|
* @type {Number}
|
|
@@ -555,16 +561,19 @@ let randSeed = 1;
|
|
|
555
561
|
/** Set seed used by the randSeeded function, should not be 0
|
|
556
562
|
* @param {Number} seed
|
|
557
563
|
* @memberof Random */
|
|
558
|
-
|
|
564
|
+
function setRandSeed(seed) { randSeed = seed; }
|
|
559
565
|
|
|
560
566
|
/** Returns a seeded random value between the two values passed in using randSeed
|
|
561
567
|
* @param {Number} [valueA=1]
|
|
562
568
|
* @param {Number} [valueB=0]
|
|
563
569
|
* @return {Number}
|
|
564
570
|
* @memberof Random */
|
|
565
|
-
|
|
571
|
+
function randSeeded(a=1, b=0)
|
|
566
572
|
{
|
|
567
|
-
|
|
573
|
+
// xorshift algorithm
|
|
574
|
+
randSeed ^= randSeed << 13;
|
|
575
|
+
randSeed ^= randSeed >>> 17;
|
|
576
|
+
randSeed ^= randSeed << 5;
|
|
568
577
|
return b + (a-b) * abs(randSeed % 1e9) / 1e9;
|
|
569
578
|
}
|
|
570
579
|
|
|
@@ -582,7 +591,8 @@ const randSeeded = (a=1, b=0)=>
|
|
|
582
591
|
* b = vec2(); // set b to (0, 0)
|
|
583
592
|
* @memberof Utilities
|
|
584
593
|
*/
|
|
585
|
-
|
|
594
|
+
function vec2(x=0, y)
|
|
595
|
+
{ return x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y); }
|
|
586
596
|
|
|
587
597
|
/**
|
|
588
598
|
* Check if object is a valid Vector2
|
|
@@ -590,11 +600,11 @@ const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y)
|
|
|
590
600
|
* @return {Boolean}
|
|
591
601
|
* @memberof Utilities
|
|
592
602
|
*/
|
|
593
|
-
|
|
603
|
+
function isVector2(v) { return !isNaN(v.x) && !isNaN(v.y); }
|
|
594
604
|
|
|
595
605
|
/**
|
|
596
606
|
* 2D Vector object with vector math library
|
|
597
|
-
*
|
|
607
|
+
* - Functions do not change this so they can be chained together
|
|
598
608
|
* @example
|
|
599
609
|
* let a = new Vector2(2, 3); // vector with coordinates (2, 3)
|
|
600
610
|
* let b = new Vector2; // vector with coordinates (0, 0)
|
|
@@ -741,7 +751,7 @@ class Vector2
|
|
|
741
751
|
* @return {Color}
|
|
742
752
|
* @memberof Utilities
|
|
743
753
|
*/
|
|
744
|
-
|
|
754
|
+
function rgb(r, g, b, a) { return new Color(r, g, b, a); }
|
|
745
755
|
|
|
746
756
|
/**
|
|
747
757
|
* Create a color object with HSLA values
|
|
@@ -752,7 +762,7 @@ const colorRGBA = (r, g, b, a)=> new Color(r, g, b, a);
|
|
|
752
762
|
* @return {Color}
|
|
753
763
|
* @memberof Utilities
|
|
754
764
|
*/
|
|
755
|
-
|
|
765
|
+
function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
|
|
756
766
|
|
|
757
767
|
/**
|
|
758
768
|
* Color object (red, green, blue, alpha) with some helpful functions
|
|
@@ -760,8 +770,8 @@ const colorHSLA = (h, s, l, a)=> new Color().setHSLA(h, s, l, a);
|
|
|
760
770
|
* let a = new Color; // white
|
|
761
771
|
* let b = new Color(1, 0, 0); // red
|
|
762
772
|
* let c = new Color(0, 0, 0, 0); // transparent black
|
|
763
|
-
* let d =
|
|
764
|
-
* let e =
|
|
773
|
+
* let d = RGB(0, 0, 1); // blue using rgb color
|
|
774
|
+
* let e = HSL(.3, 1, .5); // green using hsl color
|
|
765
775
|
*/
|
|
766
776
|
class Color
|
|
767
777
|
{
|
|
@@ -1011,11 +1021,11 @@ let canvasMaxSize = vec2(1920, 1200);
|
|
|
1011
1021
|
* @memberof Settings */
|
|
1012
1022
|
let canvasFixedSize = vec2();
|
|
1013
1023
|
|
|
1014
|
-
/** Disables
|
|
1024
|
+
/** Disables filtering for crisper pixel art if true
|
|
1015
1025
|
* @type {Boolean}
|
|
1016
1026
|
* @default
|
|
1017
1027
|
* @memberof Settings */
|
|
1018
|
-
let
|
|
1028
|
+
let canvasPixelated = 1;
|
|
1019
1029
|
|
|
1020
1030
|
/** Default font used for text rendering
|
|
1021
1031
|
* @type {String}
|
|
@@ -1132,8 +1142,8 @@ let gamepadDirectionEmulateStick = 1;
|
|
|
1132
1142
|
let inputWASDEmulateDirection = 1;
|
|
1133
1143
|
|
|
1134
1144
|
/** True if touch gamepad should appear on mobile devices
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
1145
|
+
* - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
1146
|
+
* - Must be set by end of gameInit to be activated
|
|
1137
1147
|
* @type {Boolean}
|
|
1138
1148
|
* @default 0
|
|
1139
1149
|
* @memberof Settings */
|
|
@@ -1222,32 +1232,32 @@ let medalDisplayIconSize = 50;
|
|
|
1222
1232
|
* @default 0
|
|
1223
1233
|
* @memberof Settings */
|
|
1224
1234
|
let medalsPreventUnlock;
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
*/
|
|
1235
|
+
/**
|
|
1236
|
+
* LittleJS Object System
|
|
1237
|
+
*/
|
|
1228
1238
|
|
|
1229
1239
|
'use strict';
|
|
1230
1240
|
|
|
1231
1241
|
/**
|
|
1232
1242
|
* LittleJS Object Base Object Class
|
|
1233
|
-
*
|
|
1234
|
-
*
|
|
1235
|
-
*
|
|
1236
|
-
*
|
|
1237
|
-
*
|
|
1238
|
-
*
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1247
|
-
*
|
|
1248
|
-
*
|
|
1249
|
-
*
|
|
1250
|
-
*
|
|
1243
|
+
* - Base object class used by the engine
|
|
1244
|
+
* - Automatically adds self to object list
|
|
1245
|
+
* - Will be updated and rendered each frame
|
|
1246
|
+
* - Renders as a sprite from a tilesheet by default
|
|
1247
|
+
* - Can have color and addtive color applied
|
|
1248
|
+
* - 2d Physics and collision system
|
|
1249
|
+
* - Sorted by renderOrder
|
|
1250
|
+
* - Objects can have children attached
|
|
1251
|
+
* - Parents are updated before children, and set child transform
|
|
1252
|
+
* - Call destroy() to get rid of objects
|
|
1253
|
+
*
|
|
1254
|
+
* The physics system used by objects is simple and fast with some caveats...
|
|
1255
|
+
* - Collision uses the axis aligned size, the object's rotation angle is only for rendering
|
|
1256
|
+
* - Objects are guaranteed to not intersect tile collision from physics
|
|
1257
|
+
* - If an object starts or is moved inside tile collision, it will not collide with that tile
|
|
1258
|
+
* - Collision for objects can be set to be solid to block other objects
|
|
1259
|
+
* - Objects may get pushed into overlapping other solid objects, if so they will push away
|
|
1260
|
+
* - Solid objects are more performance intensive and should be used sparingly
|
|
1251
1261
|
* @example
|
|
1252
1262
|
* // create an engine object, normally you would first extend the class with your own
|
|
1253
1263
|
* const pos = vec2(2,3);
|
|
@@ -1336,6 +1346,7 @@ class EngineObject
|
|
|
1336
1346
|
this.velocity.y += gravity * this.gravityScale;
|
|
1337
1347
|
this.pos.x += this.velocity.x *= this.damping;
|
|
1338
1348
|
this.pos.y += this.velocity.y *= this.damping;
|
|
1349
|
+
this.angle += this.angleVelocity *= this.angleDamping;
|
|
1339
1350
|
|
|
1340
1351
|
// physics sanity checks
|
|
1341
1352
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
@@ -1392,6 +1403,7 @@ class EngineObject
|
|
|
1392
1403
|
const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity; // prefer to push up if small delta
|
|
1393
1404
|
const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
|
|
1394
1405
|
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
|
|
1406
|
+
const elasticity = max(this.elasticity, o.elasticity);
|
|
1395
1407
|
|
|
1396
1408
|
if (smallStepUp | isBlockedY | !isBlockedX) // resolve y collision
|
|
1397
1409
|
{
|
|
@@ -1404,7 +1416,7 @@ class EngineObject
|
|
|
1404
1416
|
this.groundObject = o;
|
|
1405
1417
|
|
|
1406
1418
|
// bounce if other object is fixed or grounded
|
|
1407
|
-
this.velocity.y *= -
|
|
1419
|
+
this.velocity.y *= -elasticity;
|
|
1408
1420
|
}
|
|
1409
1421
|
else if (o.mass)
|
|
1410
1422
|
{
|
|
@@ -1418,7 +1430,6 @@ class EngineObject
|
|
|
1418
1430
|
+ this.velocity.y * 2 * this.mass / (this.mass + o.mass);
|
|
1419
1431
|
|
|
1420
1432
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1421
|
-
const elasticity = max(this.elasticity, o.elasticity);
|
|
1422
1433
|
this.velocity.y = lerp(elasticity, inelastic, elastic0);
|
|
1423
1434
|
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
1424
1435
|
}
|
|
@@ -1439,12 +1450,11 @@ class EngineObject
|
|
|
1439
1450
|
+ this.velocity.x * 2 * this.mass / (this.mass + o.mass);
|
|
1440
1451
|
|
|
1441
1452
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1442
|
-
const elasticity = max(this.elasticity, o.elasticity);
|
|
1443
1453
|
this.velocity.x = lerp(elasticity, inelastic, elastic0);
|
|
1444
1454
|
o.velocity.x = lerp(elasticity, inelastic, elastic1);
|
|
1445
1455
|
}
|
|
1446
1456
|
else // bounce if other object is fixed
|
|
1447
|
-
this.velocity.x *= -
|
|
1457
|
+
this.velocity.x *= -elasticity;
|
|
1448
1458
|
}
|
|
1449
1459
|
debugOverlay && debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
|
|
1450
1460
|
}
|
|
@@ -1600,23 +1610,23 @@ class EngineObject
|
|
|
1600
1610
|
}
|
|
1601
1611
|
/**
|
|
1602
1612
|
* LittleJS Drawing System
|
|
1603
|
-
*
|
|
1604
|
-
*
|
|
1605
|
-
*
|
|
1606
|
-
*
|
|
1607
|
-
*
|
|
1608
|
-
*
|
|
1609
|
-
*
|
|
1610
|
-
*
|
|
1611
|
-
*
|
|
1612
|
-
*
|
|
1613
|
-
*
|
|
1614
|
-
*
|
|
1615
|
-
*
|
|
1616
|
-
*
|
|
1617
|
-
*
|
|
1618
|
-
*
|
|
1619
|
-
*
|
|
1613
|
+
* - Hybrid with both Canvas2D and WebGL available
|
|
1614
|
+
* - Super fast tile sheet rendering with WebGL
|
|
1615
|
+
* - Can apply rotation, mirror, color and additive color
|
|
1616
|
+
* - Many useful utility functions
|
|
1617
|
+
*
|
|
1618
|
+
* LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
|
|
1619
|
+
* There are 3 canvas/contexts available to draw to...
|
|
1620
|
+
* mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
|
|
1621
|
+
* glCanvas - Used by the accelerated WebGL batch rendering system.
|
|
1622
|
+
* overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
|
|
1623
|
+
*
|
|
1624
|
+
* The WebGL rendering system is very fast with some caveats...
|
|
1625
|
+
* - The default setup supports only 1 tile sheet, to support more call glCreateTexture and glSetTexture
|
|
1626
|
+
* - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
|
|
1627
|
+
* - Group additive rendering together using renderOrder to mitigate this issue
|
|
1628
|
+
*
|
|
1629
|
+
* The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
|
|
1620
1630
|
* @namespace Draw
|
|
1621
1631
|
*/
|
|
1622
1632
|
|
|
@@ -1660,7 +1670,7 @@ let tileImageSize, tileImageFixBleed, drawCount;
|
|
|
1660
1670
|
* @param {Vector2} screenPos
|
|
1661
1671
|
* @return {Vector2}
|
|
1662
1672
|
* @memberof Draw */
|
|
1663
|
-
|
|
1673
|
+
function screenToWorld(screenPos)
|
|
1664
1674
|
{
|
|
1665
1675
|
ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
|
|
1666
1676
|
return screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
|
|
@@ -1671,7 +1681,7 @@ const screenToWorld = (screenPos)=>
|
|
|
1671
1681
|
* @param {Vector2} worldPos
|
|
1672
1682
|
* @return {Vector2}
|
|
1673
1683
|
* @memberof Draw */
|
|
1674
|
-
|
|
1684
|
+
function worldToScreen(worldPos)
|
|
1675
1685
|
{
|
|
1676
1686
|
ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
|
|
1677
1687
|
return worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
|
|
@@ -1826,6 +1836,23 @@ function setBlendMode(additive, useWebGL=glEnable)
|
|
|
1826
1836
|
mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
1827
1837
|
}
|
|
1828
1838
|
|
|
1839
|
+
/** Draw text on overlay canvas in world space
|
|
1840
|
+
* Automatically splits new lines into rows
|
|
1841
|
+
* @param {String} text
|
|
1842
|
+
* @param {Vector2} pos
|
|
1843
|
+
* @param {Number} [size=1]
|
|
1844
|
+
* @param {Color} [color=Color()]
|
|
1845
|
+
* @param {Number} [lineWidth=0]
|
|
1846
|
+
* @param {Color} [lineColor=Color(0,0,0)]
|
|
1847
|
+
* @param {String} [textAlign='center']
|
|
1848
|
+
* @param {String} [font=fontDefault]
|
|
1849
|
+
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
1850
|
+
* @memberof Draw */
|
|
1851
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
|
|
1852
|
+
{
|
|
1853
|
+
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1829
1856
|
/** Draw text on overlay canvas in screen space
|
|
1830
1857
|
* Automatically splits new lines into rows
|
|
1831
1858
|
* @param {String} text
|
|
@@ -1835,6 +1862,8 @@ function setBlendMode(additive, useWebGL=glEnable)
|
|
|
1835
1862
|
* @param {Number} [lineWidth=0]
|
|
1836
1863
|
* @param {Color} [lineColor=Color(0,0,0)]
|
|
1837
1864
|
* @param {String} [textAlign='center']
|
|
1865
|
+
* @param {String} [font=fontDefault]
|
|
1866
|
+
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
1838
1867
|
* @memberof Draw */
|
|
1839
1868
|
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
|
|
1840
1869
|
{
|
|
@@ -1855,30 +1884,15 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
|
|
|
1855
1884
|
});
|
|
1856
1885
|
}
|
|
1857
1886
|
|
|
1858
|
-
/** Draw text on overlay canvas in world space
|
|
1859
|
-
* Automatically splits new lines into rows
|
|
1860
|
-
* @param {String} text
|
|
1861
|
-
* @param {Vector2} pos
|
|
1862
|
-
* @param {Number} [size=1]
|
|
1863
|
-
* @param {Color} [color=Color()]
|
|
1864
|
-
* @param {Number} [lineWidth=0]
|
|
1865
|
-
* @param {Color} [lineColor=Color(0,0,0)]
|
|
1866
|
-
* @param {String} [textAlign='center']
|
|
1867
|
-
* @memberof Draw */
|
|
1868
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font)
|
|
1869
|
-
{
|
|
1870
|
-
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, mainContext);
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
1887
|
///////////////////////////////////////////////////////////////////////////////
|
|
1874
1888
|
|
|
1875
1889
|
let engineFontImage;
|
|
1876
1890
|
|
|
1877
1891
|
/**
|
|
1878
1892
|
* Font Image Object - Draw text on a 2D canvas by using characters in an image
|
|
1879
|
-
*
|
|
1880
|
-
*
|
|
1881
|
-
*
|
|
1893
|
+
* - 96 characters (from space to tilde) are stored in an image
|
|
1894
|
+
* - Uses a default 8x8 font if none is supplied
|
|
1895
|
+
* - You can also use fonts from the main tile sheet
|
|
1882
1896
|
* @example
|
|
1883
1897
|
* // use built in font
|
|
1884
1898
|
* const font = new ImageFont;
|
|
@@ -1918,7 +1932,7 @@ class FontImage
|
|
|
1918
1932
|
{
|
|
1919
1933
|
const context = this.context;
|
|
1920
1934
|
context.save();
|
|
1921
|
-
context.imageSmoothingEnabled = !
|
|
1935
|
+
context.imageSmoothingEnabled = !canvasPixelated;
|
|
1922
1936
|
|
|
1923
1937
|
const size = this.tileSize;
|
|
1924
1938
|
const drawSize = size.add(this.paddingSize).scale(scale);
|
|
@@ -1964,7 +1978,7 @@ class FontImage
|
|
|
1964
1978
|
/** Returns true if fullscreen mode is active
|
|
1965
1979
|
* @return {Boolean}
|
|
1966
1980
|
* @memberof Draw */
|
|
1967
|
-
|
|
1981
|
+
function isFullscreen() { return document.fullscreenElement; }
|
|
1968
1982
|
|
|
1969
1983
|
/** Toggle fullsceen mode
|
|
1970
1984
|
* @memberof Draw */
|
|
@@ -1981,10 +1995,10 @@ function toggleFullscreen()
|
|
|
1981
1995
|
|
|
1982
1996
|
/**
|
|
1983
1997
|
* LittleJS Input System
|
|
1984
|
-
*
|
|
1985
|
-
*
|
|
1986
|
-
*
|
|
1987
|
-
*
|
|
1998
|
+
* - Tracks key down, pressed, and released
|
|
1999
|
+
* - Also tracks mouse buttons, position, and wheel
|
|
2000
|
+
* - Supports multiple gamepads
|
|
2001
|
+
* - Virtual gamepad for touch devices with touchGamepadSize
|
|
1988
2002
|
* @namespace Input
|
|
1989
2003
|
*/
|
|
1990
2004
|
|
|
@@ -1995,25 +2009,28 @@ function toggleFullscreen()
|
|
|
1995
2009
|
* @param {Number} [device=0]
|
|
1996
2010
|
* @return {Boolean}
|
|
1997
2011
|
* @memberof Input */
|
|
1998
|
-
|
|
2012
|
+
function keyIsDown(key, device=0)
|
|
2013
|
+
{ return inputData[device] && inputData[device][key] & 1; }
|
|
1999
2014
|
|
|
2000
2015
|
/** Returns true if device key was pressed this frame
|
|
2001
2016
|
* @param {Number} key
|
|
2002
2017
|
* @param {Number} [device=0]
|
|
2003
2018
|
* @return {Boolean}
|
|
2004
2019
|
* @memberof Input */
|
|
2005
|
-
|
|
2020
|
+
function keyWasPressed(key, device=0)
|
|
2021
|
+
{ return inputData[device] && inputData[device][key] & 2 ? 1 : 0; }
|
|
2006
2022
|
|
|
2007
2023
|
/** Returns true if device key was released this frame
|
|
2008
2024
|
* @param {Number} key
|
|
2009
2025
|
* @param {Number} [device=0]
|
|
2010
2026
|
* @return {Boolean}
|
|
2011
2027
|
* @memberof Input */
|
|
2012
|
-
|
|
2028
|
+
function keyWasReleased(key, device=0)
|
|
2029
|
+
{ return inputData[device] && inputData[device][key] & 4 ? 1 : 0; }
|
|
2013
2030
|
|
|
2014
2031
|
/** Clears all input
|
|
2015
2032
|
* @memberof Input */
|
|
2016
|
-
|
|
2033
|
+
function clearInput() { inputData = [[]]; }
|
|
2017
2034
|
|
|
2018
2035
|
/** Returns true if mouse button is down
|
|
2019
2036
|
* @function
|
|
@@ -2066,28 +2083,32 @@ let preventDefaultInput = 0;
|
|
|
2066
2083
|
* @param {Number} [gamepad=0]
|
|
2067
2084
|
* @return {Boolean}
|
|
2068
2085
|
* @memberof Input */
|
|
2069
|
-
|
|
2086
|
+
function gamepadIsDown(button, gamepad=0)
|
|
2087
|
+
{ return keyIsDown(button, gamepad+1); }
|
|
2070
2088
|
|
|
2071
2089
|
/** Returns true if gamepad button was pressed
|
|
2072
2090
|
* @param {Number} button
|
|
2073
2091
|
* @param {Number} [gamepad=0]
|
|
2074
2092
|
* @return {Boolean}
|
|
2075
2093
|
* @memberof Input */
|
|
2076
|
-
|
|
2094
|
+
function gamepadWasPressed(button, gamepad=0)
|
|
2095
|
+
{ return keyWasPressed(button, gamepad+1); }
|
|
2077
2096
|
|
|
2078
2097
|
/** Returns true if gamepad button was released
|
|
2079
2098
|
* @param {Number} button
|
|
2080
2099
|
* @param {Number} [gamepad=0]
|
|
2081
2100
|
* @return {Boolean}
|
|
2082
2101
|
* @memberof Input */
|
|
2083
|
-
|
|
2102
|
+
function gamepadWasReleased(button, gamepad=0)
|
|
2103
|
+
{ return keyWasReleased(button, gamepad+1); }
|
|
2084
2104
|
|
|
2085
2105
|
/** Returns gamepad stick value
|
|
2086
2106
|
* @param {Number} stick
|
|
2087
2107
|
* @param {Number} [gamepad=0]
|
|
2088
2108
|
* @return {Vector2}
|
|
2089
2109
|
* @memberof Input */
|
|
2090
|
-
|
|
2110
|
+
function gamepadStick(stick, gamepad=0)
|
|
2111
|
+
{ return stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2(); }
|
|
2091
2112
|
|
|
2092
2113
|
///////////////////////////////////////////////////////////////////////////////
|
|
2093
2114
|
// Input update called by engine
|
|
@@ -2126,12 +2147,18 @@ onkeydown = (e)=>
|
|
|
2126
2147
|
e.repeat || (inputData[isUsingGamepad = 0][remapKey(e.which)] = 3);
|
|
2127
2148
|
preventDefaultInput && e.preventDefault();
|
|
2128
2149
|
}
|
|
2150
|
+
|
|
2129
2151
|
onkeyup = (e)=>
|
|
2130
2152
|
{
|
|
2131
2153
|
if (debug && e.target != document.body) return;
|
|
2132
2154
|
inputData[0][remapKey(e.which)] = 4;
|
|
2133
2155
|
}
|
|
2134
|
-
|
|
2156
|
+
|
|
2157
|
+
function remapKey(c)
|
|
2158
|
+
{
|
|
2159
|
+
return inputWASDEmulateDirection ?
|
|
2160
|
+
c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
2161
|
+
}
|
|
2135
2162
|
|
|
2136
2163
|
///////////////////////////////////////////////////////////////////////////////
|
|
2137
2164
|
// Mouse event handlers
|
|
@@ -2140,10 +2167,10 @@ onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e)
|
|
|
2140
2167
|
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2141
2168
|
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2142
2169
|
onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
2143
|
-
oncontextmenu = (e)=>
|
|
2170
|
+
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2144
2171
|
|
|
2145
2172
|
// convert a mouse or touch event position to screen space
|
|
2146
|
-
|
|
2173
|
+
function mouseToScreen(mousePos)
|
|
2147
2174
|
{
|
|
2148
2175
|
if (!mainCanvas)
|
|
2149
2176
|
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
@@ -2189,8 +2216,7 @@ function gamepadsUpdate()
|
|
|
2189
2216
|
if (gamepad)
|
|
2190
2217
|
{
|
|
2191
2218
|
// read clamp dead zone of analog sticks
|
|
2192
|
-
const deadZone = .3, deadZoneMax = .8
|
|
2193
|
-
const applyDeadZone = (v)=>
|
|
2219
|
+
const deadZone = .3, deadZoneMax = .8, applyDeadZone = (v)=>
|
|
2194
2220
|
v > deadZone ? percent( v, deadZone, deadZoneMax) :
|
|
2195
2221
|
v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
|
|
2196
2222
|
|
|
@@ -2223,11 +2249,12 @@ function gamepadsUpdate()
|
|
|
2223
2249
|
/** Pulse the vibration hardware if it exists
|
|
2224
2250
|
* @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
|
|
2225
2251
|
* @memberof Input */
|
|
2226
|
-
|
|
2252
|
+
function vibrate(pattern)
|
|
2253
|
+
{ vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
2227
2254
|
|
|
2228
2255
|
/** Cancel any ongoing vibration
|
|
2229
2256
|
* @memberof Input */
|
|
2230
|
-
|
|
2257
|
+
function vibrateStop() { vibrate(0); }
|
|
2231
2258
|
|
|
2232
2259
|
///////////////////////////////////////////////////////////////////////////////
|
|
2233
2260
|
// Touch input
|
|
@@ -2273,6 +2300,9 @@ if (isTouchDevice)
|
|
|
2273
2300
|
return true;
|
|
2274
2301
|
}
|
|
2275
2302
|
|
|
2303
|
+
// try to create touch game pad
|
|
2304
|
+
touchGamepadEnable && touchGamepadCreate();
|
|
2305
|
+
|
|
2276
2306
|
return ontouchstart(e);
|
|
2277
2307
|
}
|
|
2278
2308
|
}
|
|
@@ -2286,76 +2316,69 @@ let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
|
2286
2316
|
// create the touch gamepad, called automatically by the engine
|
|
2287
2317
|
function touchGamepadCreate()
|
|
2288
2318
|
{
|
|
2289
|
-
if (!touchGamepadEnable || !isTouchDevice)
|
|
2290
|
-
return;
|
|
2291
|
-
|
|
2292
2319
|
// touch input internal variables
|
|
2293
2320
|
touchGamepadButtons = [];
|
|
2294
2321
|
touchGamepadStick = vec2();
|
|
2295
2322
|
|
|
2296
|
-
|
|
2297
|
-
ontouchstart = (e)=>
|
|
2323
|
+
let touchHandler = ontouchstart;
|
|
2324
|
+
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2298
2325
|
{
|
|
2299
|
-
//
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2326
|
+
// clear touch gamepad input
|
|
2327
|
+
touchGamepadStick = vec2();
|
|
2328
|
+
touchGamepadButtons = [];
|
|
2329
|
+
|
|
2330
|
+
const touching = e.touches.length;
|
|
2331
|
+
if (touching)
|
|
2303
2332
|
{
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
touchGamepadButtons = [];
|
|
2307
|
-
|
|
2308
|
-
const touching = e.touches.length;
|
|
2309
|
-
if (touching)
|
|
2333
|
+
touchGamepadTimer.set();
|
|
2334
|
+
if (paused)
|
|
2310
2335
|
{
|
|
2311
|
-
//
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
if (paused)
|
|
2316
|
-
{
|
|
2317
|
-
// touch anywhere to press start when paused
|
|
2318
|
-
touchGamepadButtons[9] = 1;
|
|
2319
|
-
return;
|
|
2320
|
-
}
|
|
2336
|
+
// touch anywhere to press start when paused
|
|
2337
|
+
touchGamepadButtons[9] = 1;
|
|
2338
|
+
return;
|
|
2321
2339
|
}
|
|
2340
|
+
}
|
|
2322
2341
|
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2342
|
+
// get center of left and right sides
|
|
2343
|
+
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2344
|
+
const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
|
|
2345
|
+
const startCenter = mainCanvasSize.scale(.5);
|
|
2327
2346
|
|
|
2328
|
-
|
|
2329
|
-
|
|
2347
|
+
// check each touch point
|
|
2348
|
+
for (const touch of e.touches)
|
|
2349
|
+
{
|
|
2350
|
+
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
2351
|
+
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
2330
2352
|
{
|
|
2331
|
-
|
|
2332
|
-
if (
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
if (touchGamepadAnalog)
|
|
2336
|
-
touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
|
|
2337
|
-
else
|
|
2338
|
-
{
|
|
2339
|
-
// 8 way dpad
|
|
2340
|
-
const angle = touchPos.subtract(stickCenter).angle();
|
|
2341
|
-
touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
2353
|
+
// virtual analog stick
|
|
2354
|
+
if (touchGamepadAnalog)
|
|
2355
|
+
touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
|
|
2356
|
+
else
|
|
2345
2357
|
{
|
|
2346
|
-
//
|
|
2347
|
-
const
|
|
2348
|
-
|
|
2349
|
-
}
|
|
2350
|
-
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
2351
|
-
{
|
|
2352
|
-
// virtual start button in center
|
|
2353
|
-
touchGamepadButtons[9] = 1;
|
|
2358
|
+
// 8 way dpad
|
|
2359
|
+
const angle = touchPos.subtract(stickCenter).angle();
|
|
2360
|
+
touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
|
|
2354
2361
|
}
|
|
2355
2362
|
}
|
|
2363
|
+
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
2364
|
+
{
|
|
2365
|
+
// virtual face buttons
|
|
2366
|
+
const button = touchPos.subtract(buttonCenter).direction();
|
|
2367
|
+
touchGamepadButtons[button] = 1;
|
|
2368
|
+
}
|
|
2369
|
+
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
2370
|
+
{
|
|
2371
|
+
// virtual start button in center
|
|
2372
|
+
touchGamepadButtons[9] = 1;
|
|
2373
|
+
}
|
|
2356
2374
|
}
|
|
2357
2375
|
|
|
2358
|
-
|
|
2376
|
+
// call default touch handler and set to using gamepad
|
|
2377
|
+
touchHandler(e);
|
|
2378
|
+
isUsingGamepad = 1;
|
|
2379
|
+
|
|
2380
|
+
// must return true so the document will get focus
|
|
2381
|
+
return true;
|
|
2359
2382
|
}
|
|
2360
2383
|
}
|
|
2361
2384
|
|
|
@@ -2381,7 +2404,7 @@ function touchGamepadRender()
|
|
|
2381
2404
|
overlayContext.beginPath();
|
|
2382
2405
|
|
|
2383
2406
|
const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2384
|
-
if (touchGamepadAnalog)
|
|
2407
|
+
if (touchGamepadAnalog) // draw circle shaped gamepad
|
|
2385
2408
|
{
|
|
2386
2409
|
overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
|
|
2387
2410
|
overlayContext.fill();
|
|
@@ -2416,20 +2439,21 @@ function touchGamepadRender()
|
|
|
2416
2439
|
}
|
|
2417
2440
|
/**
|
|
2418
2441
|
* LittleJS Audio System
|
|
2419
|
-
*
|
|
2420
|
-
*
|
|
2421
|
-
*
|
|
2422
|
-
*
|
|
2423
|
-
*
|
|
2424
|
-
*
|
|
2442
|
+
* - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a> - Sound Effect Generator
|
|
2443
|
+
* - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a> - Music System
|
|
2444
|
+
* - Caches sounds and music for fast playback
|
|
2445
|
+
* - Can attenuate and apply stereo panning to sounds
|
|
2446
|
+
* - Ability to play mp3, ogg, and wave files
|
|
2447
|
+
* - Speech synthesis wrapper functions
|
|
2448
|
+
* @namespace Audio
|
|
2425
2449
|
*/
|
|
2426
2450
|
|
|
2427
2451
|
'use strict';
|
|
2428
2452
|
|
|
2429
2453
|
/**
|
|
2430
2454
|
* Sound Object - Stores a zzfx sound for later use and can be played positionally
|
|
2431
|
-
*
|
|
2432
|
-
* <
|
|
2455
|
+
*
|
|
2456
|
+
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
2433
2457
|
* @example
|
|
2434
2458
|
* // create a sound
|
|
2435
2459
|
* const sound_example = new Sound([.5,.5]);
|
|
@@ -2513,8 +2537,8 @@ class Sound
|
|
|
2513
2537
|
|
|
2514
2538
|
/**
|
|
2515
2539
|
* Music Object - Stores a zzfx music track for later use
|
|
2516
|
-
*
|
|
2517
|
-
* <
|
|
2540
|
+
*
|
|
2541
|
+
* <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
|
|
2518
2542
|
* @example
|
|
2519
2543
|
* // create some music
|
|
2520
2544
|
* const music_example = new Music(
|
|
@@ -2624,14 +2648,15 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
2624
2648
|
|
|
2625
2649
|
/** Stop all queued speech
|
|
2626
2650
|
* @memberof Audio */
|
|
2627
|
-
|
|
2651
|
+
function speakStop() {speechSynthesis && speechSynthesis.cancel();}
|
|
2628
2652
|
|
|
2629
2653
|
/** Get frequency of a note on a musical scale
|
|
2630
2654
|
* @param {Number} semitoneOffset - How many semitones away from the root note
|
|
2631
2655
|
* @param {Number} [rootNoteFrequency=220] - Frequency at semitone offset 0
|
|
2632
2656
|
* @return {Number} - The frequency of the note
|
|
2633
2657
|
* @memberof Audio */
|
|
2634
|
-
|
|
2658
|
+
function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
2659
|
+
{ return rootFrequency * 2**(semitoneOffset/12); }
|
|
2635
2660
|
|
|
2636
2661
|
///////////////////////////////////////////////////////////////////////////////
|
|
2637
2662
|
|
|
@@ -2686,15 +2711,15 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2686
2711
|
}
|
|
2687
2712
|
|
|
2688
2713
|
///////////////////////////////////////////////////////////////////////////////
|
|
2689
|
-
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.
|
|
2714
|
+
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.2.0 by Frank Force
|
|
2690
2715
|
|
|
2691
2716
|
/** Generate and play a ZzFX sound
|
|
2692
|
-
*
|
|
2693
|
-
* <
|
|
2717
|
+
*
|
|
2718
|
+
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
2694
2719
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
2695
|
-
* @return {
|
|
2720
|
+
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
2696
2721
|
* @memberof Audio */
|
|
2697
|
-
|
|
2722
|
+
function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
|
|
2698
2723
|
|
|
2699
2724
|
/** Sample rate used for all ZzFX sounds
|
|
2700
2725
|
* @default 44100
|
|
@@ -2702,7 +2727,29 @@ const zzfx = (...zzfxSound) => playSamples([zzfxG(...zzfxSound)]);
|
|
|
2702
2727
|
const zzfxR = 44100;
|
|
2703
2728
|
|
|
2704
2729
|
/** Generate samples for a ZzFX sound
|
|
2705
|
-
* @
|
|
2730
|
+
* @param {Number} [volume=1] - Volume scale (percent)
|
|
2731
|
+
* @param {Number} [randomness=.05] - How much to randomize frequency (percent Hz)
|
|
2732
|
+
* @param {Number} [frequency=220] - Frequency of sound (Hz)
|
|
2733
|
+
* @param {Number} [attack=0] - Attack time, how fast sound starts (seconds)
|
|
2734
|
+
* @param {Number} [sustain=0] - Sustain time, how long sound holds (seconds)
|
|
2735
|
+
* @param {Number} [release=.1] - Release time, how fast sound fades out (seconds)
|
|
2736
|
+
* @param {Number} [shape=0] - Shape of the sound wave
|
|
2737
|
+
* @param {Number} [shapeCurve=1] - Squarenes of wave (0=square, 1=normal, 2=pointy)
|
|
2738
|
+
* @param {Number} [slide=0] - How much to slide frequency (kHz/s)
|
|
2739
|
+
* @param {Number} [deltaSlide=0] - How much to change slide (kHz/s/s)
|
|
2740
|
+
* @param {Number} [pitchJump=0] - Frequency of pitch jump (Hz)
|
|
2741
|
+
* @param {Number} [pitchJumpTime=0] - Time of pitch jump (seconds)
|
|
2742
|
+
* @param {Number} [repeatTime=0] - Resets some parameters periodically (seconds)
|
|
2743
|
+
* @param {Number} [noise=0] - How much random noise to add (percent)
|
|
2744
|
+
* @param {Number} [modulation=0] - Frequency of modulation wave, negative flips phase (Hz)
|
|
2745
|
+
* @param {Number} [bitCrush=0] - Resamples at a lower frequency in (samples*100)
|
|
2746
|
+
* @param {Number} [delay=0] - Overlap sound with itself for reverb and flanger effects (seconds)
|
|
2747
|
+
* @param {Number} [sustainVolume=1] - Volume level for sustain (percent)
|
|
2748
|
+
* @param {Number} [decay=0] - Decay time, how long to reach sustain after attack (seconds)
|
|
2749
|
+
* @param {Number} [tremolo=0] - Trembling effect, rate controlled by repeat time (precent)
|
|
2750
|
+
* @return {Array} - Array of audio samples
|
|
2751
|
+
* @memberof Audio
|
|
2752
|
+
*/
|
|
2706
2753
|
function zzfxG
|
|
2707
2754
|
(
|
|
2708
2755
|
// parameters
|
|
@@ -2712,11 +2759,11 @@ function zzfxG
|
|
|
2712
2759
|
bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0
|
|
2713
2760
|
)
|
|
2714
2761
|
{
|
|
2715
|
-
//
|
|
2762
|
+
// locals
|
|
2716
2763
|
let PI2 = PI*2, startSlide = slide *= 500 * PI2 / zzfxR / zzfxR, b=[],
|
|
2717
2764
|
startFrequency = frequency *= (1 + randomness*rand(-1,1)) * PI2 / zzfxR,
|
|
2718
|
-
t=0, tm=0, i=0, j=1, r=0, c=0, s=0, f, length
|
|
2719
|
-
|
|
2765
|
+
t=0, tm=0, i=0, j=1, r=0, c=0, s=0, f, length
|
|
2766
|
+
|
|
2720
2767
|
// scale by sample rate
|
|
2721
2768
|
attack = attack * zzfxR + 9; // minimum attack to prevent pop
|
|
2722
2769
|
decay *= zzfxR;
|
|
@@ -2766,18 +2813,18 @@ function zzfxG
|
|
|
2766
2813
|
Math.cos(modulation*tm++); // modulation
|
|
2767
2814
|
t += f - f*noise*(1 - (Math.sin(i)+1)*1e9%2); // noise
|
|
2768
2815
|
|
|
2769
|
-
if (j && ++j > pitchJumpTime)
|
|
2816
|
+
if (j && ++j > pitchJumpTime) // pitch jump
|
|
2770
2817
|
{
|
|
2771
|
-
frequency += pitchJump;
|
|
2772
|
-
startFrequency += pitchJump;
|
|
2773
|
-
j = 0;
|
|
2818
|
+
frequency += pitchJump; // apply pitch jump
|
|
2819
|
+
startFrequency += pitchJump; // also apply to start
|
|
2820
|
+
j = 0; // reset pitch jump time
|
|
2774
2821
|
}
|
|
2775
2822
|
|
|
2776
2823
|
if (repeatTime && !(++r % repeatTime)) // repeat
|
|
2777
2824
|
{
|
|
2778
|
-
frequency = startFrequency;
|
|
2779
|
-
slide = startSlide;
|
|
2780
|
-
j
|
|
2825
|
+
frequency = startFrequency; // reset frequency
|
|
2826
|
+
slide = startSlide; // reset slide
|
|
2827
|
+
j ||= 1; // reset pitch jump time
|
|
2781
2828
|
}
|
|
2782
2829
|
}
|
|
2783
2830
|
|
|
@@ -2792,7 +2839,7 @@ function zzfxG
|
|
|
2792
2839
|
* @param {Array} patterns - Array of pattern data
|
|
2793
2840
|
* @param {Array} sequence - Array of pattern indexes
|
|
2794
2841
|
* @param {Number} [BPM=125] - Playback speed of the song in BPM
|
|
2795
|
-
* @
|
|
2842
|
+
* @return {Array} - Left and right channel sample data
|
|
2796
2843
|
* @memberof Audio */
|
|
2797
2844
|
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
2798
2845
|
{
|
|
@@ -2890,13 +2937,13 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
2890
2937
|
}
|
|
2891
2938
|
/**
|
|
2892
2939
|
* LittleJS Tile Layer System
|
|
2893
|
-
*
|
|
2894
|
-
*
|
|
2895
|
-
*
|
|
2896
|
-
*
|
|
2897
|
-
*
|
|
2898
|
-
*
|
|
2899
|
-
*
|
|
2940
|
+
* - Caches arrays of tiles to off screen canvas for fast rendering
|
|
2941
|
+
* - Unlimted numbers of layers, allocates canvases as needed
|
|
2942
|
+
* - Interfaces with EngineObject for collision
|
|
2943
|
+
* - Collision layer is separate from visible layers
|
|
2944
|
+
* - It is recommended to have a visible layer that matches the collision
|
|
2945
|
+
* - Tile layers can be drawn to using their context with canvas2d
|
|
2946
|
+
* - Drawn directly to the main canvas without using WebGL
|
|
2900
2947
|
* @namespace TileCollision
|
|
2901
2948
|
*/
|
|
2902
2949
|
|
|
@@ -2927,15 +2974,19 @@ function initTileCollision(size)
|
|
|
2927
2974
|
* @param {Vector2} pos
|
|
2928
2975
|
* @param {Number} [data=0]
|
|
2929
2976
|
* @memberof TileCollision */
|
|
2930
|
-
|
|
2977
|
+
function setTileCollisionData(pos, data=0)
|
|
2978
|
+
{
|
|
2931
2979
|
pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
|
|
2980
|
+
}
|
|
2932
2981
|
|
|
2933
2982
|
/** Get tile collision data
|
|
2934
2983
|
* @param {Vector2} pos
|
|
2935
2984
|
* @return {Number}
|
|
2936
2985
|
* @memberof TileCollision */
|
|
2937
|
-
|
|
2938
|
-
|
|
2986
|
+
function getTileCollisionData(pos)
|
|
2987
|
+
{
|
|
2988
|
+
return pos.arrayCheck(tileCollisionSize) ? tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] : 0;
|
|
2989
|
+
}
|
|
2939
2990
|
|
|
2940
2991
|
/** Check if collision with another object should occur
|
|
2941
2992
|
* @param {Vector2} pos
|
|
@@ -3029,10 +3080,10 @@ class TileLayerData
|
|
|
3029
3080
|
|
|
3030
3081
|
/**
|
|
3031
3082
|
* Tile layer object - cached rendering system for tile layers
|
|
3032
|
-
*
|
|
3033
|
-
*
|
|
3034
|
-
*
|
|
3035
|
-
*
|
|
3083
|
+
* - Each Tile layer is rendered to an off screen canvas
|
|
3084
|
+
* - To allow dynamic modifications, layers are rendered using canvas 2d
|
|
3085
|
+
* - Some devices like mobile phones are limited to 4k texture resolution
|
|
3086
|
+
* - So with 16x16 tiles this limits layers to 256x256 on mobile devices
|
|
3036
3087
|
* @extends EngineObject
|
|
3037
3088
|
* @example
|
|
3038
3089
|
* // create tile collision and visible tile layer
|
|
@@ -3232,12 +3283,9 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
|
|
|
3232
3283
|
drawRect(pos, size, color, angle)
|
|
3233
3284
|
{ this.drawTile(pos, size, -1, 0, color, angle); }
|
|
3234
3285
|
}
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
- Updates particle physics
|
|
3239
|
-
- Fast particle rendering
|
|
3240
|
-
*/
|
|
3286
|
+
/**
|
|
3287
|
+
* LittleJS Particle System
|
|
3288
|
+
*/
|
|
3241
3289
|
|
|
3242
3290
|
'use strict';
|
|
3243
3291
|
|
|
@@ -3543,9 +3591,9 @@ class Particle extends EngineObject
|
|
|
3543
3591
|
}
|
|
3544
3592
|
/**
|
|
3545
3593
|
* LittleJS Medal System
|
|
3546
|
-
*
|
|
3547
|
-
*
|
|
3548
|
-
*
|
|
3594
|
+
* - Tracks and displays medals
|
|
3595
|
+
* - Saves medals to local storage
|
|
3596
|
+
* - Newgrounds integration
|
|
3549
3597
|
* @namespace Medals
|
|
3550
3598
|
*/
|
|
3551
3599
|
|
|
@@ -3562,8 +3610,8 @@ let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
|
|
|
3562
3610
|
///////////////////////////////////////////////////////////////////////////////
|
|
3563
3611
|
|
|
3564
3612
|
/** Initialize medals with a save name used for storage
|
|
3565
|
-
*
|
|
3566
|
-
*
|
|
3613
|
+
* - Call this after creating all medals
|
|
3614
|
+
* - Checks if medals are unlocked
|
|
3567
3615
|
* @param {String} saveName
|
|
3568
3616
|
* @memberof Medals */
|
|
3569
3617
|
function medalsInit(saveName)
|
|
@@ -3833,20 +3881,43 @@ class Newgrounds
|
|
|
3833
3881
|
CryptoJS()
|
|
3834
3882
|
{
|
|
3835
3883
|
///////////////////////////////////////////////////////////////////////////////
|
|
3836
|
-
// Crypto-JS - https://github.com/brix/crypto-js
|
|
3837
|
-
//
|
|
3838
|
-
|
|
3884
|
+
// Crypto-JS - https://github.com/brix/crypto-js - MIT License
|
|
3885
|
+
//
|
|
3886
|
+
// [The MIT License (MIT)](http://opensource.org/licenses/MIT)
|
|
3887
|
+
//
|
|
3888
|
+
// Copyright (c) 2009-2013 Jeff Mott
|
|
3889
|
+
// Copyright (c) 2013-2016 Evan Vosberg
|
|
3890
|
+
//
|
|
3891
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
3892
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
3893
|
+
// in the Software without restriction, including without limitation the rights
|
|
3894
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
3895
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
3896
|
+
// furnished to do so, subject to the following conditions:
|
|
3897
|
+
//
|
|
3898
|
+
// The above copyright notice and this permission notice shall be included in
|
|
3899
|
+
// all copies or substantial portions of the Software.
|
|
3900
|
+
//
|
|
3901
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
3902
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
3903
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
3904
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
3905
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
3906
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
3907
|
+
// THE SOFTWARE.
|
|
3839
3908
|
return 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));
|
|
3909
|
+
// end of Crypto-JS
|
|
3910
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3840
3911
|
}
|
|
3841
3912
|
}
|
|
3842
3913
|
/**
|
|
3843
3914
|
* LittleJS WebGL Interface
|
|
3844
|
-
*
|
|
3845
|
-
*
|
|
3846
|
-
*
|
|
3847
|
-
*
|
|
3848
|
-
*
|
|
3849
|
-
*
|
|
3915
|
+
* - All webgl used by the engine is wrapped up here
|
|
3916
|
+
* - For normal stuff you won't need to see or call anything in this file
|
|
3917
|
+
* - For advanced stuff there are helper functions to create shaders, textures, etc
|
|
3918
|
+
* - Can be disabled with glEnable to revert to 2D canvas rendering
|
|
3919
|
+
* - Batches sprite rendering on GPU for incredibly fast performance
|
|
3920
|
+
* - Sprite transform math is done in the shader where possible
|
|
3850
3921
|
* @namespace WebGL
|
|
3851
3922
|
*/
|
|
3852
3923
|
|
|
@@ -3921,7 +3992,7 @@ function glSetBlendMode(additive)
|
|
|
3921
3992
|
}
|
|
3922
3993
|
|
|
3923
3994
|
/** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
|
|
3924
|
-
*
|
|
3995
|
+
* - This may also flush the gl buffer resulting in more draw calls and worse performance
|
|
3925
3996
|
* @param {WebGLTexture} [texture=glTileTexture]
|
|
3926
3997
|
* @memberof WebGL */
|
|
3927
3998
|
function glSetTexture(texture=glTileTexture)
|
|
@@ -3982,7 +4053,7 @@ function glCreateTexture(image)
|
|
|
3982
4053
|
image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
3983
4054
|
|
|
3984
4055
|
// use point filtering for pixelated rendering
|
|
3985
|
-
const filter =
|
|
4056
|
+
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
3986
4057
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
3987
4058
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
3988
4059
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
|
|
@@ -4233,27 +4304,23 @@ gl_INDICIES_PER_VERT = 6,
|
|
|
4233
4304
|
gl_MAX_BATCH = 1<<16,
|
|
4234
4305
|
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
|
|
4235
4306
|
gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE;
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
*/
|
|
4254
|
-
|
|
4255
|
-
/**
|
|
4256
|
-
* LittleJS Engine Globals
|
|
4307
|
+
/**
|
|
4308
|
+
* LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
4309
|
+
* MIT License - Copyright 2021 Frank Force
|
|
4310
|
+
*
|
|
4311
|
+
* Engine Features
|
|
4312
|
+
* - Object oriented system with base class engine object
|
|
4313
|
+
* - Base class object handles update, physics, collision, rendering, etc
|
|
4314
|
+
* - Engine helper classes and functions like Vector2, Color, and Timer
|
|
4315
|
+
* - Super fast rendering system for tile sheets
|
|
4316
|
+
* - Sound effects audio with zzfx and music with zzfxm
|
|
4317
|
+
* - Input processing system with gamepad and touchscreen support
|
|
4318
|
+
* - Tile layer rendering and collision system
|
|
4319
|
+
* - Particle effect system
|
|
4320
|
+
* - Medal system tracks and displays achievements
|
|
4321
|
+
* - Debug tools and debug rendering system
|
|
4322
|
+
* - Post processing effects
|
|
4323
|
+
* - Call engineInit() to start it up!
|
|
4257
4324
|
* @namespace Engine
|
|
4258
4325
|
*/
|
|
4259
4326
|
|
|
@@ -4269,7 +4336,7 @@ const engineName = 'LittleJS';
|
|
|
4269
4336
|
* @type {String}
|
|
4270
4337
|
* @default
|
|
4271
4338
|
* @memberof Engine */
|
|
4272
|
-
const engineVersion = '1.6.
|
|
4339
|
+
const engineVersion = '1.6.6';
|
|
4273
4340
|
|
|
4274
4341
|
/** Frames per second to update objects
|
|
4275
4342
|
* @type {Number}
|
|
@@ -4339,10 +4406,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4339
4406
|
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
4340
4407
|
|
|
4341
4408
|
// setup html
|
|
4342
|
-
const styleBody = 'margin:0;overflow:hidden;
|
|
4343
|
-
';
|
|
4344
|
-
'
|
|
4345
|
-
'
|
|
4409
|
+
const styleBody = 'margin:0;overflow:hidden;' + // fill the window
|
|
4410
|
+
'background:#000;' + // set background color
|
|
4411
|
+
'touch-action:none;' + // prevent mobile pinch to resize
|
|
4412
|
+
'user-select:none;' + // prevent mobile hold to select
|
|
4413
|
+
'-webkit-user-select:none'; // compatibility for ios
|
|
4346
4414
|
document.body.style = styleBody;
|
|
4347
4415
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
4348
4416
|
mainContext = mainCanvas.getContext('2d');
|
|
@@ -4355,12 +4423,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4355
4423
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
4356
4424
|
overlayContext = overlayCanvas.getContext('2d');
|
|
4357
4425
|
|
|
4358
|
-
// set canvas style
|
|
4359
|
-
const styleCanvas = 'position:absolute;
|
|
4426
|
+
// set canvas style
|
|
4427
|
+
const styleCanvas = 'position:absolute;' +
|
|
4428
|
+
'top:50%;left:50%;transform:translate(-50%,-50%);' + // center the canvas
|
|
4429
|
+
(canvasPixelated?'image-rendering:pixelated':''); // set pixelated rendering
|
|
4360
4430
|
(glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
|
|
4361
4431
|
|
|
4362
4432
|
gameInit();
|
|
4363
|
-
touchGamepadCreate();
|
|
4364
4433
|
engineUpdate();
|
|
4365
4434
|
};
|
|
4366
4435
|
|
|
@@ -4490,7 +4559,7 @@ function enginePreRender()
|
|
|
4490
4559
|
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
4491
4560
|
|
|
4492
4561
|
// disable smoothing for pixel art
|
|
4493
|
-
mainContext.imageSmoothingEnabled = !
|
|
4562
|
+
mainContext.imageSmoothingEnabled = !canvasPixelated;
|
|
4494
4563
|
|
|
4495
4564
|
// setup gl rendering if enabled
|
|
4496
4565
|
glEnable && glPreRender();
|
|
@@ -4504,7 +4573,7 @@ function engineObjectsUpdate()
|
|
|
4504
4573
|
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
4505
4574
|
|
|
4506
4575
|
// recursive object update
|
|
4507
|
-
|
|
4576
|
+
function updateObject(o)
|
|
4508
4577
|
{
|
|
4509
4578
|
if (!o.destroyed)
|
|
4510
4579
|
{
|