littlejsengine 1.6.4 → 1.6.92
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 +61 -41
- package/build/littlejs.d.ts +160 -165
- package/build/littlejs.esm.js +451 -367
- package/build/littlejs.esm.min.js +1 -1
- package/build/littlejs.js +408 -314
- package/build/littlejs.min.js +1 -1
- package/build/littlejs.release.js +2573 -2470
- package/examples/breakout/game.js +5 -1
- package/examples/breakout/index.html +5 -5
- package/examples/breakoutTutorial/README.md +6 -6
- package/examples/breakoutTutorial/game.js +3 -0
- package/examples/breakoutTutorial/index.html +3 -3
- package/examples/electron/build.js +105 -0
- package/examples/electron/game.js +4 -2
- package/examples/electron/index.html +13 -2
- package/examples/electron/package.json +5 -3
- package/examples/empty/game.js +3 -1
- package/examples/empty/index.html +1 -1
- package/examples/favicon.png +0 -0
- package/examples/js13k/build.bat +2 -0
- package/examples/js13k/build.js +110 -0
- package/examples/js13k/game.js +109 -0
- package/examples/js13k/index.html +18 -0
- package/examples/js13k/tiles.png +0 -0
- package/examples/module/game.js +9 -3
- package/examples/module/index.html +2 -2
- package/examples/particles/index.html +7 -13
- package/examples/platformer/game.js +5 -2
- package/examples/platformer/gameEffects.js +1 -2
- package/examples/platformer/gamePlayer.js +2 -2
- package/examples/platformer/index.html +8 -8
- package/examples/puzzle/game.js +6 -4
- package/examples/puzzle/index.html +4 -4
- package/examples/starter/build.bat +2 -78
- package/examples/starter/build.js +109 -0
- package/examples/starter/game.js +4 -1
- package/examples/starter/index.html +15 -18
- package/examples/stress/index.html +2 -4
- package/examples/typescript/build.bat +2 -14
- package/examples/typescript/build.js +31 -0
- package/examples/typescript/game.js +94 -89
- package/examples/typescript/game.ts +9 -3
- package/examples/typescript/index.html +2 -2
- package/package.json +3 -3
- package/src/engine.js +30 -31
- package/src/engineAudio.js +44 -20
- package/src/engineBuild.bat +2 -132
- package/src/engineBuild.js +143 -0
- package/src/engineDebug.js +21 -32
- package/src/engineDraw.js +36 -28
- package/src/engineExport.js +41 -51
- package/src/engineInput.js +33 -20
- package/src/engineMedals.js +31 -8
- package/src/engineObject.js +34 -32
- package/src/engineParticles.js +9 -12
- package/src/engineRelease.js +18 -20
- package/src/engineSettings.js +4 -4
- package/src/engineTileLayer.js +49 -32
- package/src/engineUtilities.js +96 -74
- package/src/engineWebGL.js +8 -8
- package/examples/electron/build.bat +0 -52
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
|
|
|
@@ -39,12 +34,6 @@ const debugPointSize = .5;
|
|
|
39
34
|
* @memberof Debug */
|
|
40
35
|
let showWatermark = 1;
|
|
41
36
|
|
|
42
|
-
/** True if god mode is enabled, handle this however you want
|
|
43
|
-
* @type {Boolean}
|
|
44
|
-
* @default
|
|
45
|
-
* @memberof Debug */
|
|
46
|
-
let godMode = 0;
|
|
47
|
-
|
|
48
37
|
/** Key code used to toggle debug mode, Esc by default
|
|
49
38
|
* @type {Boolean}
|
|
50
39
|
* @default
|
|
@@ -62,7 +51,7 @@ debugParticles = 0, debugGamepads = 0, debugMedals = 0, debugTakeScreenshot, dow
|
|
|
62
51
|
* @param {Boolean} assertion
|
|
63
52
|
* @param {Object} output
|
|
64
53
|
* @memberof Debug */
|
|
65
|
-
|
|
54
|
+
function ASSERT(...assert) { enableAsserts && console.assert(...assert); }
|
|
66
55
|
|
|
67
56
|
/** Draw a debug rectangle in world space
|
|
68
57
|
* @param {Vector2} pos
|
|
@@ -72,7 +61,7 @@ const ASSERT = enableAsserts ? (...assert)=> console.assert(...assert) : ()=>{};
|
|
|
72
61
|
* @param {Number} [angle=0]
|
|
73
62
|
* @param {Boolean} [fill=false]
|
|
74
63
|
* @memberof Debug */
|
|
75
|
-
|
|
64
|
+
function debugRect(pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)
|
|
76
65
|
{
|
|
77
66
|
ASSERT(typeof color == 'string'); // pass in regular html strings as colors
|
|
78
67
|
debugPrimitives.push({pos, size:vec2(size), color, time:new Timer(time), angle, fill});
|
|
@@ -85,7 +74,7 @@ const debugRect = (pos, size=vec2(), color='#fff', time=0, angle=0, fill=false)=
|
|
|
85
74
|
* @param {Number} [time=0]
|
|
86
75
|
* @param {Boolean} [fill=false]
|
|
87
76
|
* @memberof Debug */
|
|
88
|
-
|
|
77
|
+
function debugCircle(pos, radius=0, color='#fff', time=0, fill=false)
|
|
89
78
|
{
|
|
90
79
|
ASSERT(typeof color == 'string'); // pass in regular html strings as colors
|
|
91
80
|
debugPrimitives.push({pos, size:radius, color, time:new Timer(time), angle:0, fill});
|
|
@@ -97,7 +86,7 @@ const debugCircle = (pos, radius=0, color='#fff', time=0, fill=false)=>
|
|
|
97
86
|
* @param {Number} [time=0]
|
|
98
87
|
* @param {Number} [angle=0]
|
|
99
88
|
* @memberof Debug */
|
|
100
|
-
|
|
89
|
+
function debugPoint(pos, color, time, angle) {debugRect(pos, 0, color, time, angle);}
|
|
101
90
|
|
|
102
91
|
/** Draw a debug line in world space
|
|
103
92
|
* @param {Vector2} posA
|
|
@@ -106,7 +95,7 @@ const debugPoint = (pos, color, time, angle)=> debugRect(pos, 0, color, time, an
|
|
|
106
95
|
* @param {Number} [thickness=.1]
|
|
107
96
|
* @param {Number} [time=0]
|
|
108
97
|
* @memberof Debug */
|
|
109
|
-
|
|
98
|
+
function debugLine(posA, posB, color, thickness=.1, time)
|
|
110
99
|
{
|
|
111
100
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
112
101
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
@@ -120,7 +109,7 @@ const debugLine = (posA, posB, color, thickness=.1, time)=>
|
|
|
120
109
|
* @param {Vector2} sizeB
|
|
121
110
|
* @param {String} [color='#fff']
|
|
122
111
|
* @memberof Debug */
|
|
123
|
-
|
|
112
|
+
function debugAABB(pA, sA, pB, sB, color)
|
|
124
113
|
{
|
|
125
114
|
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));
|
|
126
115
|
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));
|
|
@@ -136,7 +125,7 @@ const debugAABB = (pA, sA, pB, sB, color)=>
|
|
|
136
125
|
* @param {Number} [angle=0]
|
|
137
126
|
* @param {String} [font='monospace']
|
|
138
127
|
* @memberof Debug */
|
|
139
|
-
|
|
128
|
+
function debugText(text, pos, size=1, color='#fff', time=0, angle=0, font='monospace')
|
|
140
129
|
{
|
|
141
130
|
ASSERT(typeof color == 'string'); // pass in regular html strings as colors
|
|
142
131
|
debugPrimitives.push({text, pos, size, color, time:new Timer(time), angle, font});
|
|
@@ -144,13 +133,13 @@ const debugText = (text, pos, size=1, color='#fff', time=0, angle=0, font='monos
|
|
|
144
133
|
|
|
145
134
|
/** Clear all debug primitives in the list
|
|
146
135
|
* @memberof Debug */
|
|
147
|
-
|
|
136
|
+
function debugClear() { debugPrimitives = []; }
|
|
148
137
|
|
|
149
138
|
/** Save a canvas to disk
|
|
150
139
|
* @param {HTMLCanvasElement} canvas
|
|
151
140
|
* @param {String} [filename]
|
|
152
141
|
* @memberof Debug */
|
|
153
|
-
|
|
142
|
+
function debugSaveCanvas(canvas, filename = engineName + '.png')
|
|
154
143
|
{
|
|
155
144
|
downloadLink.download = 'screenshot.png';
|
|
156
145
|
downloadLink.href = canvas.toDataURL('image/png').replace('image/png','image/octet-stream');
|
|
@@ -160,14 +149,14 @@ const debugSaveCanvas = (canvas, filename = engineName + '.png') =>
|
|
|
160
149
|
///////////////////////////////////////////////////////////////////////////////
|
|
161
150
|
// Engine debug function (called automatically)
|
|
162
151
|
|
|
163
|
-
|
|
152
|
+
function debugInit()
|
|
164
153
|
{
|
|
165
154
|
// create link for saving screenshots
|
|
166
155
|
document.body.appendChild(downloadLink = document.createElement('a'));
|
|
167
156
|
downloadLink.style.display = 'none';
|
|
168
157
|
}
|
|
169
158
|
|
|
170
|
-
|
|
159
|
+
function debugUpdate()
|
|
171
160
|
{
|
|
172
161
|
if (!debug)
|
|
173
162
|
return;
|
|
@@ -185,7 +174,7 @@ const debugUpdate = ()=>
|
|
|
185
174
|
if (keyWasPressed(51)) // 3
|
|
186
175
|
debugGamepads = !debugGamepads;
|
|
187
176
|
if (keyWasPressed(52)) // 4
|
|
188
|
-
|
|
177
|
+
debugRaycast = !debugRaycast;
|
|
189
178
|
if (keyWasPressed(53)) // 5
|
|
190
179
|
debugTakeScreenshot = 1;
|
|
191
180
|
//if (keyWasPressed(54)) // 6
|
|
@@ -195,7 +184,7 @@ const debugUpdate = ()=>
|
|
|
195
184
|
}
|
|
196
185
|
}
|
|
197
186
|
|
|
198
|
-
|
|
187
|
+
function debugRender()
|
|
199
188
|
{
|
|
200
189
|
glCopyToContext(mainContext);
|
|
201
190
|
|
|
@@ -364,8 +353,8 @@ const debugRender = ()=>
|
|
|
364
353
|
overlayContext.fillText('2: Debug Particles', x, y += h);
|
|
365
354
|
overlayContext.fillStyle = debugGamepads ? '#f00' : '#fff';
|
|
366
355
|
overlayContext.fillText('3: Debug Gamepads', x, y += h);
|
|
367
|
-
overlayContext.fillStyle =
|
|
368
|
-
overlayContext.fillText('4:
|
|
356
|
+
overlayContext.fillStyle = debugRaycast ? '#f00' : '#fff';
|
|
357
|
+
overlayContext.fillText('4: Debug Raycasts', x, y += h);
|
|
369
358
|
overlayContext.fillStyle = '#fff';
|
|
370
359
|
overlayContext.fillText('5: Save Screenshot', x, y += h);
|
|
371
360
|
|
|
@@ -390,19 +379,19 @@ const debugRender = ()=>
|
|
|
390
379
|
{
|
|
391
380
|
overlayContext.fillText(debugPhysics ? 'Debug Physics' : '', x, y += h);
|
|
392
381
|
overlayContext.fillText(debugParticles ? 'Debug Particles' : '', x, y += h);
|
|
393
|
-
overlayContext.fillText(
|
|
382
|
+
overlayContext.fillText(debugRaycast ? 'Debug Raycasts' : '', x, y += h);
|
|
394
383
|
overlayContext.fillText(debugGamepads ? 'Debug Gamepads' : '', x, y += h);
|
|
395
384
|
}
|
|
396
385
|
|
|
397
386
|
overlayContext.restore();
|
|
398
387
|
}
|
|
399
|
-
}
|
|
388
|
+
}
|
|
400
389
|
/**
|
|
401
390
|
* LittleJS Utility Classes and Functions
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
391
|
+
* - General purpose math library
|
|
392
|
+
* - Vector2 - fast, simple, easy 2D vector class
|
|
393
|
+
* - Color - holds a rgba color with some math functions
|
|
394
|
+
* - Timer - tracks time automatically
|
|
406
395
|
* @namespace Utilities
|
|
407
396
|
*/
|
|
408
397
|
|
|
@@ -418,34 +407,34 @@ const PI = Math.PI;
|
|
|
418
407
|
* @param {Number} value
|
|
419
408
|
* @return {Number}
|
|
420
409
|
* @memberof Utilities */
|
|
421
|
-
|
|
410
|
+
function abs(value) { return Math.abs(value); }
|
|
422
411
|
|
|
423
412
|
/** Returns lowest of two values passed in
|
|
424
413
|
* @param {Number} valueA
|
|
425
414
|
* @param {Number} valueB
|
|
426
415
|
* @return {Number}
|
|
427
416
|
* @memberof Utilities */
|
|
428
|
-
|
|
417
|
+
function min(valueA, valueB) { return Math.min(valueA, valueB); }
|
|
429
418
|
|
|
430
419
|
/** Returns highest of two values passed in
|
|
431
420
|
* @param {Number} valueA
|
|
432
421
|
* @param {Number} valueB
|
|
433
422
|
* @return {Number}
|
|
434
423
|
* @memberof Utilities */
|
|
435
|
-
|
|
424
|
+
function max(valueA, valueB) { return Math.max(valueA, valueB); }
|
|
436
425
|
|
|
437
426
|
/** Returns the sign of value passed in (also returns 1 if 0)
|
|
438
427
|
* @param {Number} value
|
|
439
428
|
* @return {Number}
|
|
440
429
|
* @memberof Utilities */
|
|
441
|
-
|
|
430
|
+
function sign(value) { return Math.sign(value); }
|
|
442
431
|
|
|
443
432
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
444
433
|
* @param {Number} dividend
|
|
445
434
|
* @param {Number} [divisor=1]
|
|
446
435
|
* @return {Number}
|
|
447
436
|
* @memberof Utilities */
|
|
448
|
-
|
|
437
|
+
function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
|
|
449
438
|
|
|
450
439
|
/** Clamps the value beween max and min
|
|
451
440
|
* @param {Number} value
|
|
@@ -453,7 +442,8 @@ const mod = (a, b=1)=> ((a % b) + b) % b;
|
|
|
453
442
|
* @param {Number} [max=1]
|
|
454
443
|
* @return {Number}
|
|
455
444
|
* @memberof Utilities */
|
|
456
|
-
|
|
445
|
+
function clamp(value, min=0, max=1)
|
|
446
|
+
{ return value < min ? min : value > max ? max : value; }
|
|
457
447
|
|
|
458
448
|
/** Returns what percentage the value is between max and min
|
|
459
449
|
* @param {Number} value
|
|
@@ -461,7 +451,8 @@ const clamp = (v, min=0, max=1)=> v < min ? min : v > max ? max : v;
|
|
|
461
451
|
* @param {Number} [max=1]
|
|
462
452
|
* @return {Number}
|
|
463
453
|
* @memberof Utilities */
|
|
464
|
-
|
|
454
|
+
function percent(value, min=0, max=1)
|
|
455
|
+
{ return max-min ? clamp((value-min) / (max-min)) : 0; }
|
|
465
456
|
|
|
466
457
|
/** Linearly interpolates the percent value between max and min
|
|
467
458
|
* @param {Number} percent
|
|
@@ -469,28 +460,32 @@ const percent = (v, min=0, max=1)=> max-min ? clamp((v-min) / (max-min)) : 0;
|
|
|
469
460
|
* @param {Number} [max=1]
|
|
470
461
|
* @return {Number}
|
|
471
462
|
* @memberof Utilities */
|
|
472
|
-
|
|
463
|
+
function lerp(percent, min=0, max=1){ return min + clamp(percent) * (max-min); }
|
|
473
464
|
|
|
474
465
|
/** Applies smoothstep function to the percentage value
|
|
475
|
-
* @param {Number}
|
|
466
|
+
* @param {Number} percent
|
|
476
467
|
* @return {Number}
|
|
477
468
|
* @memberof Utilities */
|
|
478
|
-
|
|
469
|
+
function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
479
470
|
|
|
480
471
|
/** Returns the nearest power of two not less then the value
|
|
481
472
|
* @param {Number} value
|
|
482
473
|
* @return {Number}
|
|
483
474
|
* @memberof Utilities */
|
|
484
|
-
|
|
475
|
+
function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
|
|
485
476
|
|
|
486
477
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
487
478
|
* @param {Vector2} pointA - Center of box A
|
|
488
479
|
* @param {Vector2} sizeA - Size of box A
|
|
489
480
|
* @param {Vector2} pointB - Center of box B
|
|
490
|
-
* @param {Vector2}
|
|
481
|
+
* @param {Vector2} sizeB - Size of box B
|
|
491
482
|
* @return {Boolean} - True if overlapping
|
|
492
483
|
* @memberof Utilities */
|
|
493
|
-
|
|
484
|
+
function isOverlapping(pointA, sizeA, pointB, sizeB)
|
|
485
|
+
{
|
|
486
|
+
return abs(pointA.x - pointB.x)*2 < sizeA.x + sizeB.x
|
|
487
|
+
&& abs(pointA.y - pointB.y)*2 < sizeA.y + sizeB.y;
|
|
488
|
+
}
|
|
494
489
|
|
|
495
490
|
/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
|
|
496
491
|
* @param {Number} [frequency=1] - Frequency of the wave in Hz
|
|
@@ -498,13 +493,14 @@ const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x && abs
|
|
|
498
493
|
* @param {Number} [t=time] - Value to use for time of the wave
|
|
499
494
|
* @return {Number} - Value waving between 0 and amplitude
|
|
500
495
|
* @memberof Utilities */
|
|
501
|
-
|
|
496
|
+
function wave(frequency=1, amplitude=1, t=time)
|
|
497
|
+
{ return amplitude/2 * (1 - Math.cos(t*frequency*2*PI)); }
|
|
502
498
|
|
|
503
499
|
/** Formats seconds to mm:ss style for display purposes
|
|
504
500
|
* @param {Number} t - time in seconds
|
|
505
501
|
* @return {String}
|
|
506
502
|
* @memberof Utilities */
|
|
507
|
-
|
|
503
|
+
function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
|
|
508
504
|
|
|
509
505
|
///////////////////////////////////////////////////////////////////////////////
|
|
510
506
|
|
|
@@ -516,32 +512,33 @@ const formatTime = (t)=> (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0);
|
|
|
516
512
|
* @param {Number} [valueB=0]
|
|
517
513
|
* @return {Number}
|
|
518
514
|
* @memberof Random */
|
|
519
|
-
|
|
515
|
+
function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valueB); }
|
|
520
516
|
|
|
521
517
|
/** Returns a floored random value the two values passed in
|
|
522
518
|
* @param {Number} [valueA=1]
|
|
523
519
|
* @param {Number} [valueB=0]
|
|
524
520
|
* @return {Number}
|
|
525
521
|
* @memberof Random */
|
|
526
|
-
|
|
522
|
+
function randInt(valueA=1, valueB=0) { return Math.floor(rand(valueA,valueB)); }
|
|
527
523
|
|
|
528
524
|
/** Randomly returns either -1 or 1
|
|
529
525
|
* @return {Number}
|
|
530
526
|
* @memberof Random */
|
|
531
|
-
|
|
527
|
+
function randSign() { return randInt(2) * 2 - 1; }
|
|
532
528
|
|
|
533
529
|
/** Returns a random Vector2 within a circular shape
|
|
534
530
|
* @param {Number} [radius=1]
|
|
535
531
|
* @param {Number} [minRadius=0]
|
|
536
532
|
* @return {Vector2}
|
|
537
533
|
* @memberof Random */
|
|
538
|
-
|
|
534
|
+
function randInCircle(radius=1, minRadius=0)
|
|
535
|
+
{ return radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2; }
|
|
539
536
|
|
|
540
537
|
/** Returns a random Vector2 with the passed in length
|
|
541
538
|
* @param {Number} [length=1]
|
|
542
539
|
* @return {Vector2}
|
|
543
540
|
* @memberof Random */
|
|
544
|
-
|
|
541
|
+
function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
|
|
545
542
|
|
|
546
543
|
/** Returns a random color between the two passed in colors, combine components if linear
|
|
547
544
|
* @param {Color} [colorA=Color()]
|
|
@@ -549,8 +546,11 @@ const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
|
|
|
549
546
|
* @param {Boolean} [linear]
|
|
550
547
|
* @return {Color}
|
|
551
548
|
* @memberof Random */
|
|
552
|
-
|
|
553
|
-
|
|
549
|
+
function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear)
|
|
550
|
+
{
|
|
551
|
+
return linear ? colorA.lerp(colorB, rand()) :
|
|
552
|
+
new Color(rand(colorA.r,colorB.r), rand(colorA.g,colorB.g), rand(colorA.b,colorB.b), rand(colorA.a,colorB.a));
|
|
553
|
+
}
|
|
554
554
|
|
|
555
555
|
/** Seed used by the randSeeded function
|
|
556
556
|
* @type {Number}
|
|
@@ -561,17 +561,20 @@ let randSeed = 1;
|
|
|
561
561
|
/** Set seed used by the randSeeded function, should not be 0
|
|
562
562
|
* @param {Number} seed
|
|
563
563
|
* @memberof Random */
|
|
564
|
-
|
|
564
|
+
function setRandSeed(seed) { randSeed = seed; }
|
|
565
565
|
|
|
566
566
|
/** Returns a seeded random value between the two values passed in using randSeed
|
|
567
567
|
* @param {Number} [valueA=1]
|
|
568
568
|
* @param {Number} [valueB=0]
|
|
569
569
|
* @return {Number}
|
|
570
570
|
* @memberof Random */
|
|
571
|
-
|
|
571
|
+
function randSeeded(valueA=1, valueB=0)
|
|
572
572
|
{
|
|
573
|
-
|
|
574
|
-
|
|
573
|
+
// xorshift algorithm
|
|
574
|
+
randSeed ^= randSeed << 13;
|
|
575
|
+
randSeed ^= randSeed >>> 17;
|
|
576
|
+
randSeed ^= randSeed << 5;
|
|
577
|
+
return valueB + (valueA-valueB) * abs(randSeed % 1e9) / 1e9;
|
|
575
578
|
}
|
|
576
579
|
|
|
577
580
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -588,19 +591,20 @@ const randSeeded = (a=1, b=0)=>
|
|
|
588
591
|
* b = vec2(); // set b to (0, 0)
|
|
589
592
|
* @memberof Utilities
|
|
590
593
|
*/
|
|
591
|
-
|
|
594
|
+
function vec2(x=0, y)
|
|
595
|
+
{ return x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y); }
|
|
592
596
|
|
|
593
597
|
/**
|
|
594
598
|
* Check if object is a valid Vector2
|
|
595
|
-
* @param {Vector2}
|
|
599
|
+
* @param {Vector2} v
|
|
596
600
|
* @return {Boolean}
|
|
597
601
|
* @memberof Utilities
|
|
598
602
|
*/
|
|
599
|
-
|
|
603
|
+
function isVector2(v) { return !isNaN(v.x) && !isNaN(v.y); }
|
|
600
604
|
|
|
601
605
|
/**
|
|
602
606
|
* 2D Vector object with vector math library
|
|
603
|
-
*
|
|
607
|
+
* - Functions do not change this so they can be chained together
|
|
604
608
|
* @example
|
|
605
609
|
* let a = new Vector2(2, 3); // vector with coordinates (2, 3)
|
|
606
610
|
* let b = new Vector2; // vector with coordinates (0, 0)
|
|
@@ -625,27 +629,27 @@ class Vector2
|
|
|
625
629
|
copy() { return new Vector2(this.x, this.y); }
|
|
626
630
|
|
|
627
631
|
/** Returns a copy of this vector plus the vector passed in
|
|
628
|
-
* @param {Vector2} vector
|
|
632
|
+
* @param {Vector2} v - other vector
|
|
629
633
|
* @return {Vector2} */
|
|
630
634
|
add(v) { ASSERT(isVector2(v)); return new Vector2(this.x + v.x, this.y + v.y); }
|
|
631
635
|
|
|
632
636
|
/** Returns a copy of this vector minus the vector passed in
|
|
633
|
-
* @param {Vector2} vector
|
|
637
|
+
* @param {Vector2} v - other vector
|
|
634
638
|
* @return {Vector2} */
|
|
635
639
|
subtract(v) { ASSERT(isVector2(v)); return new Vector2(this.x - v.x, this.y - v.y); }
|
|
636
640
|
|
|
637
641
|
/** Returns a copy of this vector times the vector passed in
|
|
638
|
-
* @param {Vector2} vector
|
|
642
|
+
* @param {Vector2} v - other vector
|
|
639
643
|
* @return {Vector2} */
|
|
640
644
|
multiply(v) { ASSERT(isVector2(v)); return new Vector2(this.x * v.x, this.y * v.y); }
|
|
641
645
|
|
|
642
646
|
/** Returns a copy of this vector divided by the vector passed in
|
|
643
|
-
* @param {Vector2} vector
|
|
647
|
+
* @param {Vector2} v - other vector
|
|
644
648
|
* @return {Vector2} */
|
|
645
649
|
divide(v) { ASSERT(isVector2(v)); return new Vector2(this.x / v.x, this.y / v.y); }
|
|
646
650
|
|
|
647
651
|
/** Returns a copy of this vector scaled by the vector passed in
|
|
648
|
-
* @param {Number} scale
|
|
652
|
+
* @param {Number} s - scale
|
|
649
653
|
* @return {Vector2} */
|
|
650
654
|
scale(s) { ASSERT(!isVector2(s)); return new Vector2(this.x * s, this.y * s); }
|
|
651
655
|
|
|
@@ -658,12 +662,12 @@ class Vector2
|
|
|
658
662
|
lengthSquared() { return this.x**2 + this.y**2; }
|
|
659
663
|
|
|
660
664
|
/** Returns the distance from this vector to vector passed in
|
|
661
|
-
* @param {Vector2} vector
|
|
665
|
+
* @param {Vector2} v - other vector
|
|
662
666
|
* @return {Number} */
|
|
663
667
|
distance(v) { return this.distanceSquared(v)**.5; }
|
|
664
668
|
|
|
665
669
|
/** Returns the distance squared from this vector to vector passed in
|
|
666
|
-
* @param {Vector2} vector
|
|
670
|
+
* @param {Vector2} v - other vector
|
|
667
671
|
* @return {Number} */
|
|
668
672
|
distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
|
|
669
673
|
|
|
@@ -678,12 +682,12 @@ class Vector2
|
|
|
678
682
|
clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
|
|
679
683
|
|
|
680
684
|
/** Returns the dot product of this and the vector passed in
|
|
681
|
-
* @param {Vector2} vector
|
|
685
|
+
* @param {Vector2} v - other vector
|
|
682
686
|
* @return {Number} */
|
|
683
687
|
dot(v) { ASSERT(isVector2(v)); return this.x*v.x + this.y*v.y; }
|
|
684
688
|
|
|
685
689
|
/** Returns the cross product of this and the vector passed in
|
|
686
|
-
* @param {Vector2} vector
|
|
690
|
+
* @param {Vector2} v - other vector
|
|
687
691
|
* @return {Number} */
|
|
688
692
|
cross(v) { ASSERT(isVector2(v)); return this.x*v.y - this.y*v.x; }
|
|
689
693
|
|
|
@@ -695,12 +699,17 @@ class Vector2
|
|
|
695
699
|
* @param {Number} [angle=0]
|
|
696
700
|
* @param {Number} [length=1]
|
|
697
701
|
* @return {Vector2} */
|
|
698
|
-
setAngle(
|
|
702
|
+
setAngle(angle=0, length=1)
|
|
703
|
+
{ this.x = length*Math.sin(angle); this.y = length*Math.cos(angle); return this; }
|
|
699
704
|
|
|
700
705
|
/** Returns copy of this vector rotated by the angle passed in
|
|
701
706
|
* @param {Number} angle
|
|
702
707
|
* @return {Vector2} */
|
|
703
|
-
rotate(
|
|
708
|
+
rotate(angle)
|
|
709
|
+
{
|
|
710
|
+
const c = Math.cos(angle), s = Math.sin(angle);
|
|
711
|
+
return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
|
|
712
|
+
}
|
|
704
713
|
|
|
705
714
|
/** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
|
|
706
715
|
* @return {Number} */
|
|
@@ -719,10 +728,11 @@ class Vector2
|
|
|
719
728
|
area() { return abs(this.x * this.y); }
|
|
720
729
|
|
|
721
730
|
/** Returns a new vector that is p percent between this and the vector passed in
|
|
722
|
-
* @param {Vector2} vector
|
|
731
|
+
* @param {Vector2} v - other vector
|
|
723
732
|
* @param {Number} percent
|
|
724
733
|
* @return {Vector2} */
|
|
725
|
-
lerp(v,
|
|
734
|
+
lerp(v, percent)
|
|
735
|
+
{ ASSERT(isVector2(v)); return this.add(v.subtract(this).scale(clamp(percent))); }
|
|
726
736
|
|
|
727
737
|
/** Returns true if this vector is within the bounds of an array size passed in
|
|
728
738
|
* @param {Vector2} arraySize
|
|
@@ -740,25 +750,25 @@ class Vector2
|
|
|
740
750
|
|
|
741
751
|
/**
|
|
742
752
|
* Create a color object with RGBA values
|
|
743
|
-
* @param {Number} [r=1]
|
|
744
|
-
* @param {Number} [g=1]
|
|
745
|
-
* @param {Number} [b=1]
|
|
746
|
-
* @param {Number} [a=1]
|
|
753
|
+
* @param {Number} [r=1] - red
|
|
754
|
+
* @param {Number} [g=1] - green
|
|
755
|
+
* @param {Number} [b=1] - blue
|
|
756
|
+
* @param {Number} [a=1] - alpha
|
|
747
757
|
* @return {Color}
|
|
748
758
|
* @memberof Utilities
|
|
749
759
|
*/
|
|
750
|
-
|
|
760
|
+
function rgb(r, g, b, a) { return new Color(r, g, b, a); }
|
|
751
761
|
|
|
752
762
|
/**
|
|
753
763
|
* Create a color object with HSLA values
|
|
754
|
-
* @param {Number} [h=0]
|
|
755
|
-
* @param {Number} [s=0]
|
|
756
|
-
* @param {Number} [l=1]
|
|
757
|
-
* @param {Number} [a=1]
|
|
764
|
+
* @param {Number} [h=0] - hue
|
|
765
|
+
* @param {Number} [s=0] - saturation
|
|
766
|
+
* @param {Number} [l=1] - lightness
|
|
767
|
+
* @param {Number} [a=1] - alpha
|
|
758
768
|
* @return {Color}
|
|
759
769
|
* @memberof Utilities
|
|
760
770
|
*/
|
|
761
|
-
|
|
771
|
+
function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
|
|
762
772
|
|
|
763
773
|
/**
|
|
764
774
|
* Color object (red, green, blue, alpha) with some helpful functions
|
|
@@ -771,11 +781,11 @@ const hsl = (h, s, l, a)=> new Color().setHSLA(h, s, l, a);
|
|
|
771
781
|
*/
|
|
772
782
|
class Color
|
|
773
783
|
{
|
|
774
|
-
/** Create a color with the components passed in, white by default
|
|
775
|
-
* @param {Number} [
|
|
776
|
-
* @param {Number} [
|
|
777
|
-
* @param {Number} [
|
|
778
|
-
* @param {Number} [
|
|
784
|
+
/** Create a color with the rgba components passed in, white by default
|
|
785
|
+
* @param {Number} [r=1] - red
|
|
786
|
+
* @param {Number} [g=1] - green
|
|
787
|
+
* @param {Number} [b=1] - blue
|
|
788
|
+
* @param {Number} [a=1] - alpha*/
|
|
779
789
|
constructor(r=1, g=1, b=1, a=1)
|
|
780
790
|
{
|
|
781
791
|
/** @property {Number} - Red */
|
|
@@ -793,22 +803,22 @@ class Color
|
|
|
793
803
|
copy() { return new Color(this.r, this.g, this.b, this.a); }
|
|
794
804
|
|
|
795
805
|
/** Returns a copy of this color plus the color passed in
|
|
796
|
-
* @param {Color} color
|
|
806
|
+
* @param {Color} c - other color
|
|
797
807
|
* @return {Color} */
|
|
798
808
|
add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
|
|
799
809
|
|
|
800
810
|
/** Returns a copy of this color minus the color passed in
|
|
801
|
-
* @param {Color} color
|
|
811
|
+
* @param {Color} c - other color
|
|
802
812
|
* @return {Color} */
|
|
803
813
|
subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
|
|
804
814
|
|
|
805
815
|
/** Returns a copy of this color times the color passed in
|
|
806
|
-
* @param {Color} color
|
|
816
|
+
* @param {Color} c - other color
|
|
807
817
|
* @return {Color} */
|
|
808
818
|
multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
|
|
809
819
|
|
|
810
820
|
/** Returns a copy of this color divided by the color passed in
|
|
811
|
-
* @param {Color} color
|
|
821
|
+
* @param {Color} c - other color
|
|
812
822
|
* @return {Color} */
|
|
813
823
|
divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
|
|
814
824
|
|
|
@@ -816,23 +826,24 @@ class Color
|
|
|
816
826
|
* @param {Number} scale
|
|
817
827
|
* @param {Number} [alphaScale=scale]
|
|
818
828
|
* @return {Color} */
|
|
819
|
-
scale(
|
|
829
|
+
scale(scale, alphaScale=scale)
|
|
830
|
+
{ return new Color(this.r*scale, this.g*scale, this.b*scale, this.a*alphaScale); }
|
|
820
831
|
|
|
821
832
|
/** Returns a copy of this color clamped to the valid range between 0 and 1
|
|
822
833
|
* @return {Color} */
|
|
823
834
|
clamp() { return new Color(clamp(this.r), clamp(this.g), clamp(this.b), clamp(this.a)); }
|
|
824
835
|
|
|
825
836
|
/** Returns a new color that is p percent between this and the color passed in
|
|
826
|
-
* @param {Color} color
|
|
837
|
+
* @param {Color} c - other color
|
|
827
838
|
* @param {Number} percent
|
|
828
839
|
* @return {Color} */
|
|
829
|
-
lerp(c,
|
|
840
|
+
lerp(c, percent) { return this.add(c.subtract(this).scale(clamp(percent))); }
|
|
830
841
|
|
|
831
842
|
/** Sets this color given a hue, saturation, lightness, and alpha
|
|
832
|
-
* @param {Number} [
|
|
833
|
-
* @param {Number} [
|
|
834
|
-
* @param {Number} [
|
|
835
|
-
* @param {Number} [
|
|
843
|
+
* @param {Number} [h=0] - hue
|
|
844
|
+
* @param {Number} [s=0] - saturation
|
|
845
|
+
* @param {Number} [l=1] - lightness
|
|
846
|
+
* @param {Number} [a=1] - alpha
|
|
836
847
|
* @return {Color} */
|
|
837
848
|
setHSLA(h=0, s=0, l=1, a=1)
|
|
838
849
|
{
|
|
@@ -973,12 +984,12 @@ class Timer
|
|
|
973
984
|
|
|
974
985
|
/** Returns this timer expressed as a string
|
|
975
986
|
* @return {String} */
|
|
976
|
-
toString() { if (debug) { return this.
|
|
987
|
+
toString() { if (debug) { return this.isSet() ? Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; }}
|
|
977
988
|
|
|
978
989
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
979
990
|
* @return {Number} */
|
|
980
991
|
valueOf() { return this.get(); }
|
|
981
|
-
}
|
|
992
|
+
}
|
|
982
993
|
/**
|
|
983
994
|
* LittleJS Engine Settings
|
|
984
995
|
* @namespace Settings
|
|
@@ -1017,11 +1028,11 @@ let canvasMaxSize = vec2(1920, 1200);
|
|
|
1017
1028
|
* @memberof Settings */
|
|
1018
1029
|
let canvasFixedSize = vec2();
|
|
1019
1030
|
|
|
1020
|
-
/** Disables
|
|
1031
|
+
/** Disables filtering for crisper pixel art if true
|
|
1021
1032
|
* @type {Boolean}
|
|
1022
1033
|
* @default
|
|
1023
1034
|
* @memberof Settings */
|
|
1024
|
-
let
|
|
1035
|
+
let canvasPixelated = 1;
|
|
1025
1036
|
|
|
1026
1037
|
/** Default font used for text rendering
|
|
1027
1038
|
* @type {String}
|
|
@@ -1138,8 +1149,8 @@ let gamepadDirectionEmulateStick = 1;
|
|
|
1138
1149
|
let inputWASDEmulateDirection = 1;
|
|
1139
1150
|
|
|
1140
1151
|
/** True if touch gamepad should appear on mobile devices
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1152
|
+
* - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
1153
|
+
* - Must be set by end of gameInit to be activated
|
|
1143
1154
|
* @type {Boolean}
|
|
1144
1155
|
* @default 0
|
|
1145
1156
|
* @memberof Settings */
|
|
@@ -1227,33 +1238,33 @@ let medalDisplayIconSize = 50;
|
|
|
1227
1238
|
* @type {Boolean}
|
|
1228
1239
|
* @default 0
|
|
1229
1240
|
* @memberof Settings */
|
|
1230
|
-
let medalsPreventUnlock;
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
*/
|
|
1241
|
+
let medalsPreventUnlock;
|
|
1242
|
+
/**
|
|
1243
|
+
* LittleJS Object System
|
|
1244
|
+
*/
|
|
1234
1245
|
|
|
1235
1246
|
'use strict';
|
|
1236
1247
|
|
|
1237
1248
|
/**
|
|
1238
1249
|
* LittleJS Object Base Object Class
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1247
|
-
*
|
|
1248
|
-
*
|
|
1249
|
-
*
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1252
|
-
*
|
|
1253
|
-
*
|
|
1254
|
-
*
|
|
1255
|
-
*
|
|
1256
|
-
*
|
|
1250
|
+
* - Base object class used by the engine
|
|
1251
|
+
* - Automatically adds self to object list
|
|
1252
|
+
* - Will be updated and rendered each frame
|
|
1253
|
+
* - Renders as a sprite from a tilesheet by default
|
|
1254
|
+
* - Can have color and addtive color applied
|
|
1255
|
+
* - 2d Physics and collision system
|
|
1256
|
+
* - Sorted by renderOrder
|
|
1257
|
+
* - Objects can have children attached
|
|
1258
|
+
* - Parents are updated before children, and set child transform
|
|
1259
|
+
* - Call destroy() to get rid of objects
|
|
1260
|
+
*
|
|
1261
|
+
* The physics system used by objects is simple and fast with some caveats...
|
|
1262
|
+
* - Collision uses the axis aligned size, the object's rotation angle is only for rendering
|
|
1263
|
+
* - Objects are guaranteed to not intersect tile collision from physics
|
|
1264
|
+
* - If an object starts or is moved inside tile collision, it will not collide with that tile
|
|
1265
|
+
* - Collision for objects can be set to be solid to block other objects
|
|
1266
|
+
* - Objects may get pushed into overlapping other solid objects, if so they will push away
|
|
1267
|
+
* - Solid objects are more performance intensive and should be used sparingly
|
|
1257
1268
|
* @example
|
|
1258
1269
|
* // create an engine object, normally you would first extend the class with your own
|
|
1259
1270
|
* const pos = vec2(2,3);
|
|
@@ -1308,7 +1319,7 @@ class EngineObject
|
|
|
1308
1319
|
/** @property {Number} [renderOrder=0] - Objects are sorted by render order */
|
|
1309
1320
|
this.renderOrder = renderOrder;
|
|
1310
1321
|
/** @property {Vector2} [velocity=Vector2()] - Velocity of the object */
|
|
1311
|
-
this.velocity =
|
|
1322
|
+
this.velocity = vec2();
|
|
1312
1323
|
/** @property {Number} [angleVelocity=0] - Angular velocity of the object */
|
|
1313
1324
|
this.angleVelocity = 0;
|
|
1314
1325
|
|
|
@@ -1368,15 +1379,17 @@ class EngineObject
|
|
|
1368
1379
|
for (const o of engineObjectsCollide)
|
|
1369
1380
|
{
|
|
1370
1381
|
// non solid objects don't collide with eachother
|
|
1371
|
-
if (!this.isSolid
|
|
1382
|
+
if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o == this)
|
|
1372
1383
|
continue;
|
|
1373
1384
|
|
|
1374
1385
|
// check collision
|
|
1375
1386
|
if (!isOverlapping(this.pos, this.size, o.pos, o.size))
|
|
1376
1387
|
continue;
|
|
1377
1388
|
|
|
1378
|
-
//
|
|
1379
|
-
|
|
1389
|
+
// notify objects of collision and check if should be resolved
|
|
1390
|
+
const collide1 = this.collideWithObject(o);
|
|
1391
|
+
const collide2 = o.collideWithObject(this);
|
|
1392
|
+
if (!collide1 || !collide2)
|
|
1380
1393
|
continue;
|
|
1381
1394
|
|
|
1382
1395
|
if (isOverlapping(oldPos, this.size, o.pos, o.size))
|
|
@@ -1401,7 +1414,7 @@ class EngineObject
|
|
|
1401
1414
|
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
|
|
1402
1415
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1403
1416
|
|
|
1404
|
-
if (smallStepUp
|
|
1417
|
+
if (smallStepUp || isBlockedY || !isBlockedX) // resolve y collision
|
|
1405
1418
|
{
|
|
1406
1419
|
// push outside object collision
|
|
1407
1420
|
this.pos.y = o.pos.y + (sizeBoth.y/2 + epsilon) * sign(oldPos.y - o.pos.y);
|
|
@@ -1430,7 +1443,7 @@ class EngineObject
|
|
|
1430
1443
|
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
1431
1444
|
}
|
|
1432
1445
|
}
|
|
1433
|
-
if (!smallStepUp
|
|
1446
|
+
if (!smallStepUp && isBlockedX) // resolve x collision
|
|
1434
1447
|
{
|
|
1435
1448
|
// push outside collision
|
|
1436
1449
|
this.pos.x = o.pos.x + (sizeBoth.x/2 + epsilon) * sign(oldPos.x - o.pos.x);
|
|
@@ -1465,9 +1478,9 @@ class EngineObject
|
|
|
1465
1478
|
if (!tileCollisionTest(oldPos, this.size, this))
|
|
1466
1479
|
{
|
|
1467
1480
|
// test which side we bounced off (or both if a corner)
|
|
1468
|
-
const isBlockedY = tileCollisionTest(
|
|
1469
|
-
const isBlockedX = tileCollisionTest(
|
|
1470
|
-
if (isBlockedY
|
|
1481
|
+
const isBlockedY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
|
|
1482
|
+
const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
1483
|
+
if (isBlockedY || !isBlockedX)
|
|
1471
1484
|
{
|
|
1472
1485
|
// set if landed on ground
|
|
1473
1486
|
this.groundObject = wasMovingDown;
|
|
@@ -1530,7 +1543,7 @@ class EngineObject
|
|
|
1530
1543
|
* @param {EngineObject} object - the object to test against
|
|
1531
1544
|
* @return {Boolean} - true if the collision should be resolved
|
|
1532
1545
|
*/
|
|
1533
|
-
collideWithObject(
|
|
1546
|
+
collideWithObject(object) { return 1; }
|
|
1534
1547
|
|
|
1535
1548
|
/** How long since the object was created
|
|
1536
1549
|
* @return {Number} */
|
|
@@ -1538,7 +1551,7 @@ class EngineObject
|
|
|
1538
1551
|
|
|
1539
1552
|
/** Apply acceleration to this object (adjust velocity, not affected by mass)
|
|
1540
1553
|
* @param {Vector2} acceleration */
|
|
1541
|
-
applyAcceleration(
|
|
1554
|
+
applyAcceleration(acceleration) { if (this.mass) this.velocity = this.velocity.add(acceleration); }
|
|
1542
1555
|
|
|
1543
1556
|
/** Apply force to this object (adjust velocity, affected by mass)
|
|
1544
1557
|
* @param {Vector2} force */
|
|
@@ -1603,26 +1616,26 @@ class EngineObject
|
|
|
1603
1616
|
return text;
|
|
1604
1617
|
}
|
|
1605
1618
|
}
|
|
1606
|
-
}
|
|
1619
|
+
}
|
|
1607
1620
|
/**
|
|
1608
1621
|
* LittleJS Drawing System
|
|
1609
|
-
*
|
|
1610
|
-
*
|
|
1611
|
-
*
|
|
1612
|
-
*
|
|
1613
|
-
*
|
|
1614
|
-
*
|
|
1615
|
-
*
|
|
1616
|
-
*
|
|
1617
|
-
*
|
|
1618
|
-
*
|
|
1619
|
-
*
|
|
1620
|
-
*
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1623
|
-
*
|
|
1624
|
-
*
|
|
1625
|
-
*
|
|
1622
|
+
* - Hybrid with both Canvas2D and WebGL available
|
|
1623
|
+
* - Super fast tile sheet rendering with WebGL
|
|
1624
|
+
* - Can apply rotation, mirror, color and additive color
|
|
1625
|
+
* - Many useful utility functions
|
|
1626
|
+
*
|
|
1627
|
+
* LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
|
|
1628
|
+
* There are 3 canvas/contexts available to draw to...
|
|
1629
|
+
* mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
|
|
1630
|
+
* glCanvas - Used by the accelerated WebGL batch rendering system.
|
|
1631
|
+
* overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
|
|
1632
|
+
*
|
|
1633
|
+
* The WebGL rendering system is very fast with some caveats...
|
|
1634
|
+
* - The default setup supports only 1 tile sheet, to support more call glCreateTexture and glSetTexture
|
|
1635
|
+
* - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
|
|
1636
|
+
* - Group additive rendering together using renderOrder to mitigate this issue
|
|
1637
|
+
*
|
|
1638
|
+
* The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
|
|
1626
1639
|
* @namespace Draw
|
|
1627
1640
|
*/
|
|
1628
1641
|
|
|
@@ -1666,10 +1679,14 @@ let tileImageSize, tileImageFixBleed, drawCount;
|
|
|
1666
1679
|
* @param {Vector2} screenPos
|
|
1667
1680
|
* @return {Vector2}
|
|
1668
1681
|
* @memberof Draw */
|
|
1669
|
-
|
|
1682
|
+
function screenToWorld(screenPos)
|
|
1670
1683
|
{
|
|
1671
1684
|
ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
|
|
1672
|
-
return screenPos
|
|
1685
|
+
return screenPos
|
|
1686
|
+
.add(vec2(.5))
|
|
1687
|
+
.subtract(mainCanvasSize.scale(.5))
|
|
1688
|
+
.multiply(vec2(1/cameraScale,-1/cameraScale))
|
|
1689
|
+
.add(cameraPos);
|
|
1673
1690
|
}
|
|
1674
1691
|
|
|
1675
1692
|
/** Convert from world to screen space coordinates
|
|
@@ -1677,10 +1694,14 @@ const screenToWorld = (screenPos)=>
|
|
|
1677
1694
|
* @param {Vector2} worldPos
|
|
1678
1695
|
* @return {Vector2}
|
|
1679
1696
|
* @memberof Draw */
|
|
1680
|
-
|
|
1697
|
+
function worldToScreen(worldPos)
|
|
1681
1698
|
{
|
|
1682
1699
|
ASSERT(mainCanvasSize.x && mainCanvasSize.y, 'mainCanvasSize is invalid');
|
|
1683
|
-
return worldPos
|
|
1700
|
+
return worldPos
|
|
1701
|
+
.subtract(cameraPos)
|
|
1702
|
+
.multiply(vec2(cameraScale,-cameraScale))
|
|
1703
|
+
.add(mainCanvasSize.scale(.5))
|
|
1704
|
+
.subtract(vec2(.5));
|
|
1684
1705
|
}
|
|
1685
1706
|
|
|
1686
1707
|
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
@@ -1694,8 +1715,8 @@ const worldToScreen = (worldPos)=>
|
|
|
1694
1715
|
* @param {Color} [additiveColor=Color(0,0,0,0)] - Additive color to be applied
|
|
1695
1716
|
* @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
1696
1717
|
* @memberof Draw */
|
|
1697
|
-
function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color,
|
|
1698
|
-
additiveColor=new Color(0,0,0,0), useWebGL=glEnable)
|
|
1718
|
+
function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color,
|
|
1719
|
+
angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable)
|
|
1699
1720
|
{
|
|
1700
1721
|
showWatermark && ++drawCount;
|
|
1701
1722
|
if (glEnable && useWebGL)
|
|
@@ -1886,9 +1907,9 @@ let engineFontImage;
|
|
|
1886
1907
|
|
|
1887
1908
|
/**
|
|
1888
1909
|
* Font Image Object - Draw text on a 2D canvas by using characters in an image
|
|
1889
|
-
*
|
|
1890
|
-
*
|
|
1891
|
-
*
|
|
1910
|
+
* - 96 characters (from space to tilde) are stored in an image
|
|
1911
|
+
* - Uses a default 8x8 font if none is supplied
|
|
1912
|
+
* - You can also use fonts from the main tile sheet
|
|
1892
1913
|
* @example
|
|
1893
1914
|
* // use built in font
|
|
1894
1915
|
* const font = new ImageFont;
|
|
@@ -1928,7 +1949,7 @@ class FontImage
|
|
|
1928
1949
|
{
|
|
1929
1950
|
const context = this.context;
|
|
1930
1951
|
context.save();
|
|
1931
|
-
context.imageSmoothingEnabled = !
|
|
1952
|
+
context.imageSmoothingEnabled = !canvasPixelated;
|
|
1932
1953
|
|
|
1933
1954
|
const size = this.tileSize;
|
|
1934
1955
|
const drawSize = size.add(this.paddingSize).scale(scale);
|
|
@@ -1974,7 +1995,7 @@ class FontImage
|
|
|
1974
1995
|
/** Returns true if fullscreen mode is active
|
|
1975
1996
|
* @return {Boolean}
|
|
1976
1997
|
* @memberof Draw */
|
|
1977
|
-
|
|
1998
|
+
function isFullscreen() { return document.fullscreenElement; }
|
|
1978
1999
|
|
|
1979
2000
|
/** Toggle fullsceen mode
|
|
1980
2001
|
* @memberof Draw */
|
|
@@ -1988,13 +2009,13 @@ function toggleFullscreen()
|
|
|
1988
2009
|
else if (document.body.requestFullscreen)
|
|
1989
2010
|
document.body.requestFullscreen();
|
|
1990
2011
|
}
|
|
1991
|
-
|
|
2012
|
+
|
|
1992
2013
|
/**
|
|
1993
2014
|
* LittleJS Input System
|
|
1994
|
-
*
|
|
1995
|
-
*
|
|
1996
|
-
*
|
|
1997
|
-
*
|
|
2015
|
+
* - Tracks key down, pressed, and released
|
|
2016
|
+
* - Also tracks mouse buttons, position, and wheel
|
|
2017
|
+
* - Supports multiple gamepads
|
|
2018
|
+
* - Virtual gamepad for touch devices with touchGamepadSize
|
|
1998
2019
|
* @namespace Input
|
|
1999
2020
|
*/
|
|
2000
2021
|
|
|
@@ -2005,25 +2026,28 @@ function toggleFullscreen()
|
|
|
2005
2026
|
* @param {Number} [device=0]
|
|
2006
2027
|
* @return {Boolean}
|
|
2007
2028
|
* @memberof Input */
|
|
2008
|
-
|
|
2029
|
+
function keyIsDown(key, device=0)
|
|
2030
|
+
{ return inputData[device] && inputData[device][key] & 1; }
|
|
2009
2031
|
|
|
2010
2032
|
/** Returns true if device key was pressed this frame
|
|
2011
2033
|
* @param {Number} key
|
|
2012
2034
|
* @param {Number} [device=0]
|
|
2013
2035
|
* @return {Boolean}
|
|
2014
2036
|
* @memberof Input */
|
|
2015
|
-
|
|
2037
|
+
function keyWasPressed(key, device=0)
|
|
2038
|
+
{ return inputData[device] && inputData[device][key] & 2 ? 1 : 0; }
|
|
2016
2039
|
|
|
2017
2040
|
/** Returns true if device key was released this frame
|
|
2018
2041
|
* @param {Number} key
|
|
2019
2042
|
* @param {Number} [device=0]
|
|
2020
2043
|
* @return {Boolean}
|
|
2021
2044
|
* @memberof Input */
|
|
2022
|
-
|
|
2045
|
+
function keyWasReleased(key, device=0)
|
|
2046
|
+
{ return inputData[device] && inputData[device][key] & 4 ? 1 : 0; }
|
|
2023
2047
|
|
|
2024
2048
|
/** Clears all input
|
|
2025
2049
|
* @memberof Input */
|
|
2026
|
-
|
|
2050
|
+
function clearInput() { inputData = [[]]; }
|
|
2027
2051
|
|
|
2028
2052
|
/** Returns true if mouse button is down
|
|
2029
2053
|
* @function
|
|
@@ -2076,28 +2100,32 @@ let preventDefaultInput = 0;
|
|
|
2076
2100
|
* @param {Number} [gamepad=0]
|
|
2077
2101
|
* @return {Boolean}
|
|
2078
2102
|
* @memberof Input */
|
|
2079
|
-
|
|
2103
|
+
function gamepadIsDown(button, gamepad=0)
|
|
2104
|
+
{ return keyIsDown(button, gamepad+1); }
|
|
2080
2105
|
|
|
2081
2106
|
/** Returns true if gamepad button was pressed
|
|
2082
2107
|
* @param {Number} button
|
|
2083
2108
|
* @param {Number} [gamepad=0]
|
|
2084
2109
|
* @return {Boolean}
|
|
2085
2110
|
* @memberof Input */
|
|
2086
|
-
|
|
2111
|
+
function gamepadWasPressed(button, gamepad=0)
|
|
2112
|
+
{ return keyWasPressed(button, gamepad+1); }
|
|
2087
2113
|
|
|
2088
2114
|
/** Returns true if gamepad button was released
|
|
2089
2115
|
* @param {Number} button
|
|
2090
2116
|
* @param {Number} [gamepad=0]
|
|
2091
2117
|
* @return {Boolean}
|
|
2092
2118
|
* @memberof Input */
|
|
2093
|
-
|
|
2119
|
+
function gamepadWasReleased(button, gamepad=0)
|
|
2120
|
+
{ return keyWasReleased(button, gamepad+1); }
|
|
2094
2121
|
|
|
2095
2122
|
/** Returns gamepad stick value
|
|
2096
2123
|
* @param {Number} stick
|
|
2097
2124
|
* @param {Number} [gamepad=0]
|
|
2098
2125
|
* @return {Vector2}
|
|
2099
2126
|
* @memberof Input */
|
|
2100
|
-
|
|
2127
|
+
function gamepadStick(stick, gamepad=0)
|
|
2128
|
+
{ return stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2(); }
|
|
2101
2129
|
|
|
2102
2130
|
///////////////////////////////////////////////////////////////////////////////
|
|
2103
2131
|
// Input update called by engine
|
|
@@ -2136,12 +2164,18 @@ onkeydown = (e)=>
|
|
|
2136
2164
|
e.repeat || (inputData[isUsingGamepad = 0][remapKey(e.which)] = 3);
|
|
2137
2165
|
preventDefaultInput && e.preventDefault();
|
|
2138
2166
|
}
|
|
2167
|
+
|
|
2139
2168
|
onkeyup = (e)=>
|
|
2140
2169
|
{
|
|
2141
2170
|
if (debug && e.target != document.body) return;
|
|
2142
2171
|
inputData[0][remapKey(e.which)] = 4;
|
|
2143
2172
|
}
|
|
2144
|
-
|
|
2173
|
+
|
|
2174
|
+
function remapKey(c)
|
|
2175
|
+
{
|
|
2176
|
+
return inputWASDEmulateDirection ?
|
|
2177
|
+
c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
2178
|
+
}
|
|
2145
2179
|
|
|
2146
2180
|
///////////////////////////////////////////////////////////////////////////////
|
|
2147
2181
|
// Mouse event handlers
|
|
@@ -2150,10 +2184,10 @@ onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e)
|
|
|
2150
2184
|
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2151
2185
|
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2152
2186
|
onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
2153
|
-
oncontextmenu = (e)=>
|
|
2187
|
+
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2154
2188
|
|
|
2155
2189
|
// convert a mouse or touch event position to screen space
|
|
2156
|
-
|
|
2190
|
+
function mouseToScreen(mousePos)
|
|
2157
2191
|
{
|
|
2158
2192
|
if (!mainCanvas)
|
|
2159
2193
|
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
@@ -2199,8 +2233,7 @@ function gamepadsUpdate()
|
|
|
2199
2233
|
if (gamepad)
|
|
2200
2234
|
{
|
|
2201
2235
|
// read clamp dead zone of analog sticks
|
|
2202
|
-
const deadZone = .3, deadZoneMax = .8
|
|
2203
|
-
const applyDeadZone = (v)=>
|
|
2236
|
+
const deadZone = .3, deadZoneMax = .8, applyDeadZone = (v)=>
|
|
2204
2237
|
v > deadZone ? percent( v, deadZone, deadZoneMax) :
|
|
2205
2238
|
v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
|
|
2206
2239
|
|
|
@@ -2233,11 +2266,12 @@ function gamepadsUpdate()
|
|
|
2233
2266
|
/** Pulse the vibration hardware if it exists
|
|
2234
2267
|
* @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
|
|
2235
2268
|
* @memberof Input */
|
|
2236
|
-
|
|
2269
|
+
function vibrate(pattern)
|
|
2270
|
+
{ vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
2237
2271
|
|
|
2238
2272
|
/** Cancel any ongoing vibration
|
|
2239
2273
|
* @memberof Input */
|
|
2240
|
-
|
|
2274
|
+
function vibrateStop() { vibrate(0); }
|
|
2241
2275
|
|
|
2242
2276
|
///////////////////////////////////////////////////////////////////////////////
|
|
2243
2277
|
// Touch input
|
|
@@ -2409,7 +2443,7 @@ function touchGamepadRender()
|
|
|
2409
2443
|
const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2410
2444
|
for (let i=4; i--;)
|
|
2411
2445
|
{
|
|
2412
|
-
const pos = rightCenter.add((
|
|
2446
|
+
const pos = rightCenter.add(vec2().setAngle(i*PI/2, touchGamepadSize/2));
|
|
2413
2447
|
overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
2414
2448
|
overlayContext.beginPath();
|
|
2415
2449
|
overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
@@ -2419,23 +2453,24 @@ function touchGamepadRender()
|
|
|
2419
2453
|
|
|
2420
2454
|
// set canvas back to normal
|
|
2421
2455
|
overlayContext.restore();
|
|
2422
|
-
}
|
|
2456
|
+
}
|
|
2423
2457
|
/**
|
|
2424
2458
|
* LittleJS Audio System
|
|
2425
|
-
*
|
|
2426
|
-
*
|
|
2427
|
-
*
|
|
2428
|
-
*
|
|
2429
|
-
*
|
|
2430
|
-
*
|
|
2459
|
+
* - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a> - Sound Effect Generator
|
|
2460
|
+
* - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a> - Music System
|
|
2461
|
+
* - Caches sounds and music for fast playback
|
|
2462
|
+
* - Can attenuate and apply stereo panning to sounds
|
|
2463
|
+
* - Ability to play mp3, ogg, and wave files
|
|
2464
|
+
* - Speech synthesis wrapper functions
|
|
2465
|
+
* @namespace Audio
|
|
2431
2466
|
*/
|
|
2432
2467
|
|
|
2433
2468
|
'use strict';
|
|
2434
2469
|
|
|
2435
2470
|
/**
|
|
2436
2471
|
* Sound Object - Stores a zzfx sound for later use and can be played positionally
|
|
2437
|
-
*
|
|
2438
|
-
* <
|
|
2472
|
+
*
|
|
2473
|
+
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
2439
2474
|
* @example
|
|
2440
2475
|
* // create a sound
|
|
2441
2476
|
* const sound_example = new Sound([.5,.5]);
|
|
@@ -2519,8 +2554,8 @@ class Sound
|
|
|
2519
2554
|
|
|
2520
2555
|
/**
|
|
2521
2556
|
* Music Object - Stores a zzfx music track for later use
|
|
2522
|
-
*
|
|
2523
|
-
* <
|
|
2557
|
+
*
|
|
2558
|
+
* <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
|
|
2524
2559
|
* @example
|
|
2525
2560
|
* // create some music
|
|
2526
2561
|
* const music_example = new Music(
|
|
@@ -2630,14 +2665,15 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
2630
2665
|
|
|
2631
2666
|
/** Stop all queued speech
|
|
2632
2667
|
* @memberof Audio */
|
|
2633
|
-
|
|
2668
|
+
function speakStop() {speechSynthesis && speechSynthesis.cancel();}
|
|
2634
2669
|
|
|
2635
2670
|
/** Get frequency of a note on a musical scale
|
|
2636
2671
|
* @param {Number} semitoneOffset - How many semitones away from the root note
|
|
2637
2672
|
* @param {Number} [rootNoteFrequency=220] - Frequency at semitone offset 0
|
|
2638
2673
|
* @return {Number} - The frequency of the note
|
|
2639
2674
|
* @memberof Audio */
|
|
2640
|
-
|
|
2675
|
+
function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
2676
|
+
{ return rootFrequency * 2**(semitoneOffset/12); }
|
|
2641
2677
|
|
|
2642
2678
|
///////////////////////////////////////////////////////////////////////////////
|
|
2643
2679
|
|
|
@@ -2695,12 +2731,12 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2695
2731
|
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.2.0 by Frank Force
|
|
2696
2732
|
|
|
2697
2733
|
/** Generate and play a ZzFX sound
|
|
2698
|
-
*
|
|
2699
|
-
* <
|
|
2734
|
+
*
|
|
2735
|
+
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
2700
2736
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
2701
|
-
* @return {
|
|
2737
|
+
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
2702
2738
|
* @memberof Audio */
|
|
2703
|
-
|
|
2739
|
+
function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
|
|
2704
2740
|
|
|
2705
2741
|
/** Sample rate used for all ZzFX sounds
|
|
2706
2742
|
* @default 44100
|
|
@@ -2708,7 +2744,29 @@ const zzfx = (...zzfxSound) => playSamples([zzfxG(...zzfxSound)]);
|
|
|
2708
2744
|
const zzfxR = 44100;
|
|
2709
2745
|
|
|
2710
2746
|
/** Generate samples for a ZzFX sound
|
|
2711
|
-
* @
|
|
2747
|
+
* @param {Number} [volume=1] - Volume scale (percent)
|
|
2748
|
+
* @param {Number} [randomness=.05] - How much to randomize frequency (percent Hz)
|
|
2749
|
+
* @param {Number} [frequency=220] - Frequency of sound (Hz)
|
|
2750
|
+
* @param {Number} [attack=0] - Attack time, how fast sound starts (seconds)
|
|
2751
|
+
* @param {Number} [sustain=0] - Sustain time, how long sound holds (seconds)
|
|
2752
|
+
* @param {Number} [release=.1] - Release time, how fast sound fades out (seconds)
|
|
2753
|
+
* @param {Number} [shape=0] - Shape of the sound wave
|
|
2754
|
+
* @param {Number} [shapeCurve=1] - Squarenes of wave (0=square, 1=normal, 2=pointy)
|
|
2755
|
+
* @param {Number} [slide=0] - How much to slide frequency (kHz/s)
|
|
2756
|
+
* @param {Number} [deltaSlide=0] - How much to change slide (kHz/s/s)
|
|
2757
|
+
* @param {Number} [pitchJump=0] - Frequency of pitch jump (Hz)
|
|
2758
|
+
* @param {Number} [pitchJumpTime=0] - Time of pitch jump (seconds)
|
|
2759
|
+
* @param {Number} [repeatTime=0] - Resets some parameters periodically (seconds)
|
|
2760
|
+
* @param {Number} [noise=0] - How much random noise to add (percent)
|
|
2761
|
+
* @param {Number} [modulation=0] - Frequency of modulation wave, negative flips phase (Hz)
|
|
2762
|
+
* @param {Number} [bitCrush=0] - Resamples at a lower frequency in (samples*100)
|
|
2763
|
+
* @param {Number} [delay=0] - Overlap sound with itself for reverb and flanger effects (seconds)
|
|
2764
|
+
* @param {Number} [sustainVolume=1] - Volume level for sustain (percent)
|
|
2765
|
+
* @param {Number} [decay=0] - Decay time, how long to reach sustain after attack (seconds)
|
|
2766
|
+
* @param {Number} [tremolo=0] - Trembling effect, rate controlled by repeat time (precent)
|
|
2767
|
+
* @return {Array} - Array of audio samples
|
|
2768
|
+
* @memberof Audio
|
|
2769
|
+
*/
|
|
2712
2770
|
function zzfxG
|
|
2713
2771
|
(
|
|
2714
2772
|
// parameters
|
|
@@ -2798,7 +2856,7 @@ function zzfxG
|
|
|
2798
2856
|
* @param {Array} patterns - Array of pattern data
|
|
2799
2857
|
* @param {Array} sequence - Array of pattern indexes
|
|
2800
2858
|
* @param {Number} [BPM=125] - Playback speed of the song in BPM
|
|
2801
|
-
* @
|
|
2859
|
+
* @return {Array} - Left and right channel sample data
|
|
2802
2860
|
* @memberof Audio */
|
|
2803
2861
|
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
2804
2862
|
{
|
|
@@ -2836,7 +2894,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
2836
2894
|
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
2837
2895
|
|
|
2838
2896
|
// check if there are more channels
|
|
2839
|
-
hasMore
|
|
2897
|
+
hasMore ||= !!patterns[patternIndex][channelIndex];
|
|
2840
2898
|
|
|
2841
2899
|
// get next offset, use the length of first channel
|
|
2842
2900
|
nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - !notFirstBeat) * beatLength;
|
|
@@ -2849,7 +2907,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
2849
2907
|
|
|
2850
2908
|
// stop if end, different instrument or new note
|
|
2851
2909
|
stop = i == patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
|
|
2852
|
-
instrument != (patternChannel[0] || 0)
|
|
2910
|
+
instrument != (patternChannel[0] || 0) || note;
|
|
2853
2911
|
|
|
2854
2912
|
// fill buffer with samples for previous beat, most cpu intensive part
|
|
2855
2913
|
for (j = 0; j < beatLength && notFirstBeat;
|
|
@@ -2893,16 +2951,16 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
2893
2951
|
}
|
|
2894
2952
|
|
|
2895
2953
|
return [leftChannelBuffer, rightChannelBuffer];
|
|
2896
|
-
}
|
|
2954
|
+
}
|
|
2897
2955
|
/**
|
|
2898
2956
|
* LittleJS Tile Layer System
|
|
2899
|
-
*
|
|
2900
|
-
*
|
|
2901
|
-
*
|
|
2902
|
-
*
|
|
2903
|
-
*
|
|
2904
|
-
*
|
|
2905
|
-
*
|
|
2957
|
+
* - Caches arrays of tiles to off screen canvas for fast rendering
|
|
2958
|
+
* - Unlimted numbers of layers, allocates canvases as needed
|
|
2959
|
+
* - Interfaces with EngineObject for collision
|
|
2960
|
+
* - Collision layer is separate from visible layers
|
|
2961
|
+
* - It is recommended to have a visible layer that matches the collision
|
|
2962
|
+
* - Tile layers can be drawn to using their context with canvas2d
|
|
2963
|
+
* - Drawn directly to the main canvas without using WebGL
|
|
2906
2964
|
* @namespace TileCollision
|
|
2907
2965
|
*/
|
|
2908
2966
|
|
|
@@ -2933,15 +2991,19 @@ function initTileCollision(size)
|
|
|
2933
2991
|
* @param {Vector2} pos
|
|
2934
2992
|
* @param {Number} [data=0]
|
|
2935
2993
|
* @memberof TileCollision */
|
|
2936
|
-
|
|
2994
|
+
function setTileCollisionData(pos, data=0)
|
|
2995
|
+
{
|
|
2937
2996
|
pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
|
|
2997
|
+
}
|
|
2938
2998
|
|
|
2939
2999
|
/** Get tile collision data
|
|
2940
3000
|
* @param {Vector2} pos
|
|
2941
3001
|
* @return {Number}
|
|
2942
3002
|
* @memberof TileCollision */
|
|
2943
|
-
|
|
2944
|
-
|
|
3003
|
+
function getTileCollisionData(pos)
|
|
3004
|
+
{
|
|
3005
|
+
return pos.arrayCheck(tileCollisionSize) ? tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] : 0;
|
|
3006
|
+
}
|
|
2945
3007
|
|
|
2946
3008
|
/** Check if collision with another object should occur
|
|
2947
3009
|
* @param {Vector2} pos
|
|
@@ -2959,12 +3021,12 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
2959
3021
|
for (let x = minX; x < maxX; ++x)
|
|
2960
3022
|
{
|
|
2961
3023
|
const tileData = tileCollision[y*tileCollisionSize.x+x];
|
|
2962
|
-
if (tileData && (!object || object.collideWithTile(tileData,
|
|
3024
|
+
if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
|
|
2963
3025
|
return 1;
|
|
2964
3026
|
}
|
|
2965
3027
|
}
|
|
2966
3028
|
|
|
2967
|
-
/** Return the center of tile if any that is hit (
|
|
3029
|
+
/** Return the center of tile if any that is hit (does not return the exact intersection)
|
|
2968
3030
|
* @param {Vector2} posStart
|
|
2969
3031
|
* @param {Vector2} posEnd
|
|
2970
3032
|
* @param {EngineObject} [object]
|
|
@@ -2973,28 +3035,41 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
2973
3035
|
function tileCollisionRaycast(posStart, posEnd, object)
|
|
2974
3036
|
{
|
|
2975
3037
|
// test if a ray collides with tiles from start to end
|
|
2976
|
-
// todo: a way to get the exact hit point, it must still
|
|
2977
|
-
const
|
|
2978
|
-
const
|
|
2979
|
-
const
|
|
2980
|
-
|
|
2981
|
-
|
|
3038
|
+
// todo: a way to get the exact hit point, it must still be inside the hit tile
|
|
3039
|
+
const delta = posEnd.subtract(posStart);
|
|
3040
|
+
const totalLength = delta.length();
|
|
3041
|
+
const normalizedDelta = delta.normalize();
|
|
3042
|
+
const unit = vec2(abs(1/normalizedDelta.x), abs(1/normalizedDelta.y));
|
|
3043
|
+
const flooredPosStart = posStart.floor();
|
|
3044
|
+
|
|
3045
|
+
// setup iteration variables
|
|
3046
|
+
let pos = flooredPosStart;
|
|
3047
|
+
let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
|
|
3048
|
+
let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
|
|
3049
|
+
|
|
3050
|
+
while (1)
|
|
2982
3051
|
{
|
|
2983
|
-
|
|
2984
|
-
|
|
3052
|
+
// check for tile collision
|
|
3053
|
+
const tileData = getTileCollisionData(pos);
|
|
3054
|
+
if (tileData && (!object || object.collideWithTile(tileData, pos)))
|
|
2985
3055
|
{
|
|
2986
|
-
debugRaycast && debugLine(posStart, posEnd, '#f00'
|
|
2987
|
-
debugRaycast && debugPoint(
|
|
2988
|
-
return
|
|
3056
|
+
debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
|
|
3057
|
+
debugRaycast && debugPoint(pos.add(vec2(.5)), '#ff0');
|
|
3058
|
+
return pos.add(vec2(.5));
|
|
2989
3059
|
}
|
|
2990
3060
|
|
|
2991
|
-
//
|
|
2992
|
-
if (
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
3061
|
+
// check if past the end
|
|
3062
|
+
if (xi > totalLength && yi > totalLength)
|
|
3063
|
+
break;
|
|
3064
|
+
|
|
3065
|
+
// get coordinates of the next tile to check
|
|
3066
|
+
if (xi > yi)
|
|
3067
|
+
pos.y += sign(delta.y), yi += unit.y;
|
|
3068
|
+
else
|
|
3069
|
+
pos.x += sign(delta.x), xi += unit.x;
|
|
2996
3070
|
}
|
|
2997
|
-
|
|
3071
|
+
|
|
3072
|
+
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|
|
2998
3073
|
}
|
|
2999
3074
|
|
|
3000
3075
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3035,10 +3110,10 @@ class TileLayerData
|
|
|
3035
3110
|
|
|
3036
3111
|
/**
|
|
3037
3112
|
* Tile layer object - cached rendering system for tile layers
|
|
3038
|
-
*
|
|
3039
|
-
*
|
|
3040
|
-
*
|
|
3041
|
-
*
|
|
3113
|
+
* - Each Tile layer is rendered to an off screen canvas
|
|
3114
|
+
* - To allow dynamic modifications, layers are rendered using canvas 2d
|
|
3115
|
+
* - Some devices like mobile phones are limited to 4k texture resolution
|
|
3116
|
+
* - So with 16x16 tiles this limits layers to 256x256 on mobile devices
|
|
3042
3117
|
* @extends EngineObject
|
|
3043
3118
|
* @example
|
|
3044
3119
|
* // create tile collision and visible tile layer
|
|
@@ -3237,13 +3312,10 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
|
|
|
3237
3312
|
* @param {Number} [angle=0] */
|
|
3238
3313
|
drawRect(pos, size, color, angle)
|
|
3239
3314
|
{ this.drawTile(pos, size, -1, 0, color, angle); }
|
|
3240
|
-
}
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
- Updates particle physics
|
|
3245
|
-
- Fast particle rendering
|
|
3246
|
-
*/
|
|
3315
|
+
}
|
|
3316
|
+
/**
|
|
3317
|
+
* LittleJS Particle System
|
|
3318
|
+
*/
|
|
3247
3319
|
|
|
3248
3320
|
'use strict';
|
|
3249
3321
|
|
|
@@ -3328,7 +3400,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3328
3400
|
localSpace
|
|
3329
3401
|
)
|
|
3330
3402
|
{
|
|
3331
|
-
super(pos,
|
|
3403
|
+
super(pos, vec2(), tileIndex, tileSize, angle, undefined, renderOrder);
|
|
3332
3404
|
|
|
3333
3405
|
// emitter settings
|
|
3334
3406
|
/** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
@@ -3395,7 +3467,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3395
3467
|
this.parent && super.update();
|
|
3396
3468
|
|
|
3397
3469
|
// update emitter
|
|
3398
|
-
if (!this.emitTime
|
|
3470
|
+
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
3399
3471
|
{
|
|
3400
3472
|
// emit particles
|
|
3401
3473
|
if (this.emitRate * particleEmitRateScale)
|
|
@@ -3417,7 +3489,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3417
3489
|
{
|
|
3418
3490
|
// spawn a particle
|
|
3419
3491
|
let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
|
|
3420
|
-
(
|
|
3492
|
+
vec2(rand(-.5,.5), rand(-.5,.5))
|
|
3421
3493
|
.multiply(this.emitSize).rotate(this.angle) // box emitter
|
|
3422
3494
|
: randInCircle(this.emitSize/2); // circle emitter
|
|
3423
3495
|
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
@@ -3447,7 +3519,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3447
3519
|
// build particle settings
|
|
3448
3520
|
particle.colorStart = colorStart;
|
|
3449
3521
|
particle.colorEndDelta = colorEnd.subtract(colorStart);
|
|
3450
|
-
particle.velocity = (
|
|
3522
|
+
particle.velocity = vec2().setAngle(velocityAngle, speed);
|
|
3451
3523
|
particle.angleVelocity = angleSpeed;
|
|
3452
3524
|
particle.lifeTime = particleTime;
|
|
3453
3525
|
particle.sizeStart = sizeStart;
|
|
@@ -3492,7 +3564,7 @@ class Particle extends EngineObject
|
|
|
3492
3564
|
* @param {Number} [angle=0] - Angle to rotate the particle
|
|
3493
3565
|
*/
|
|
3494
3566
|
constructor(pos, tileIndex, tileSize, angle)
|
|
3495
|
-
{ super(pos,
|
|
3567
|
+
{ super(pos, vec2(), tileIndex, tileSize, angle); }
|
|
3496
3568
|
|
|
3497
3569
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
3498
3570
|
render()
|
|
@@ -3500,7 +3572,7 @@ class Particle extends EngineObject
|
|
|
3500
3572
|
// modulate size and color
|
|
3501
3573
|
const p = min((time - this.spawnTime) / this.lifeTime, 1);
|
|
3502
3574
|
const radius = this.sizeStart + p * this.sizeEndDelta;
|
|
3503
|
-
const size =
|
|
3575
|
+
const size = vec2(radius);
|
|
3504
3576
|
const fadeRate = this.fadeRate/2;
|
|
3505
3577
|
const color = new Color(
|
|
3506
3578
|
this.colorStart.r + p * this.colorEndDelta.r,
|
|
@@ -3546,12 +3618,12 @@ class Particle extends EngineObject
|
|
|
3546
3618
|
this.destroyed = 1;
|
|
3547
3619
|
}
|
|
3548
3620
|
}
|
|
3549
|
-
}
|
|
3621
|
+
}
|
|
3550
3622
|
/**
|
|
3551
3623
|
* LittleJS Medal System
|
|
3552
|
-
*
|
|
3553
|
-
*
|
|
3554
|
-
*
|
|
3624
|
+
* - Tracks and displays medals
|
|
3625
|
+
* - Saves medals to local storage
|
|
3626
|
+
* - Newgrounds integration
|
|
3555
3627
|
* @namespace Medals
|
|
3556
3628
|
*/
|
|
3557
3629
|
|
|
@@ -3568,8 +3640,8 @@ let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
|
|
|
3568
3640
|
///////////////////////////////////////////////////////////////////////////////
|
|
3569
3641
|
|
|
3570
3642
|
/** Initialize medals with a save name used for storage
|
|
3571
|
-
*
|
|
3572
|
-
*
|
|
3643
|
+
* - Call this after creating all medals
|
|
3644
|
+
* - Checks if medals are unlocked
|
|
3573
3645
|
* @param {String} saveName
|
|
3574
3646
|
* @memberof Medals */
|
|
3575
3647
|
function medalsInit(saveName)
|
|
@@ -3839,20 +3911,43 @@ class Newgrounds
|
|
|
3839
3911
|
CryptoJS()
|
|
3840
3912
|
{
|
|
3841
3913
|
///////////////////////////////////////////////////////////////////////////////
|
|
3842
|
-
// Crypto-JS - https://github.com/brix/crypto-js
|
|
3843
|
-
//
|
|
3844
|
-
|
|
3914
|
+
// Crypto-JS - https://github.com/brix/crypto-js - MIT License
|
|
3915
|
+
//
|
|
3916
|
+
// [The MIT License (MIT)](http://opensource.org/licenses/MIT)
|
|
3917
|
+
//
|
|
3918
|
+
// Copyright (c) 2009-2013 Jeff Mott
|
|
3919
|
+
// Copyright (c) 2013-2016 Evan Vosberg
|
|
3920
|
+
//
|
|
3921
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
3922
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
3923
|
+
// in the Software without restriction, including without limitation the rights
|
|
3924
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
3925
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
3926
|
+
// furnished to do so, subject to the following conditions:
|
|
3927
|
+
//
|
|
3928
|
+
// The above copyright notice and this permission notice shall be included in
|
|
3929
|
+
// all copies or substantial portions of the Software.
|
|
3930
|
+
//
|
|
3931
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
3932
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
3933
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
3934
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
3935
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
3936
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
3937
|
+
// THE SOFTWARE.
|
|
3845
3938
|
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));
|
|
3939
|
+
// end of Crypto-JS
|
|
3940
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3846
3941
|
}
|
|
3847
|
-
}
|
|
3942
|
+
}
|
|
3848
3943
|
/**
|
|
3849
3944
|
* LittleJS WebGL Interface
|
|
3850
|
-
*
|
|
3851
|
-
*
|
|
3852
|
-
*
|
|
3853
|
-
*
|
|
3854
|
-
*
|
|
3855
|
-
*
|
|
3945
|
+
* - All webgl used by the engine is wrapped up here
|
|
3946
|
+
* - For normal stuff you won't need to see or call anything in this file
|
|
3947
|
+
* - For advanced stuff there are helper functions to create shaders, textures, etc
|
|
3948
|
+
* - Can be disabled with glEnable to revert to 2D canvas rendering
|
|
3949
|
+
* - Batches sprite rendering on GPU for incredibly fast performance
|
|
3950
|
+
* - Sprite transform math is done in the shader where possible
|
|
3856
3951
|
* @namespace WebGL
|
|
3857
3952
|
*/
|
|
3858
3953
|
|
|
@@ -3927,7 +4022,7 @@ function glSetBlendMode(additive)
|
|
|
3927
4022
|
}
|
|
3928
4023
|
|
|
3929
4024
|
/** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
|
|
3930
|
-
*
|
|
4025
|
+
* - This may also flush the gl buffer resulting in more draw calls and worse performance
|
|
3931
4026
|
* @param {WebGLTexture} [texture=glTileTexture]
|
|
3932
4027
|
* @memberof WebGL */
|
|
3933
4028
|
function glSetTexture(texture=glTileTexture)
|
|
@@ -3988,7 +4083,7 @@ function glCreateTexture(image)
|
|
|
3988
4083
|
image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
3989
4084
|
|
|
3990
4085
|
// use point filtering for pixelated rendering
|
|
3991
|
-
const filter =
|
|
4086
|
+
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
3992
4087
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
3993
4088
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
3994
4089
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
|
|
@@ -4238,28 +4333,24 @@ gl_VERTICES_PER_QUAD = 6,
|
|
|
4238
4333
|
gl_INDICIES_PER_VERT = 6,
|
|
4239
4334
|
gl_MAX_BATCH = 1<<16,
|
|
4240
4335
|
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
|
|
4241
|
-
gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE;
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
*/
|
|
4260
|
-
|
|
4261
|
-
/**
|
|
4262
|
-
* LittleJS Engine Globals
|
|
4336
|
+
gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE;
|
|
4337
|
+
/**
|
|
4338
|
+
* LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
4339
|
+
* MIT License - Copyright 2021 Frank Force
|
|
4340
|
+
*
|
|
4341
|
+
* Engine Features
|
|
4342
|
+
* - Object oriented system with base class engine object
|
|
4343
|
+
* - Base class object handles update, physics, collision, rendering, etc
|
|
4344
|
+
* - Engine helper classes and functions like Vector2, Color, and Timer
|
|
4345
|
+
* - Super fast rendering system for tile sheets
|
|
4346
|
+
* - Sound effects audio with zzfx and music with zzfxm
|
|
4347
|
+
* - Input processing system with gamepad and touchscreen support
|
|
4348
|
+
* - Tile layer rendering and collision system
|
|
4349
|
+
* - Particle effect system
|
|
4350
|
+
* - Medal system tracks and displays achievements
|
|
4351
|
+
* - Debug tools and debug rendering system
|
|
4352
|
+
* - Post processing effects
|
|
4353
|
+
* - Call engineInit() to start it up!
|
|
4263
4354
|
* @namespace Engine
|
|
4264
4355
|
*/
|
|
4265
4356
|
|
|
@@ -4275,7 +4366,7 @@ const engineName = 'LittleJS';
|
|
|
4275
4366
|
* @type {String}
|
|
4276
4367
|
* @default
|
|
4277
4368
|
* @memberof Engine */
|
|
4278
|
-
const engineVersion = '1.6.
|
|
4369
|
+
const engineVersion = '1.6.92';
|
|
4279
4370
|
|
|
4280
4371
|
/** Frames per second to update objects
|
|
4281
4372
|
* @type {Number}
|
|
@@ -4345,10 +4436,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4345
4436
|
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
4346
4437
|
|
|
4347
4438
|
// setup html
|
|
4348
|
-
const styleBody = 'margin:0;overflow:hidden;
|
|
4349
|
-
';
|
|
4350
|
-
'
|
|
4351
|
-
'
|
|
4439
|
+
const styleBody = 'margin:0;overflow:hidden;' + // fill the window
|
|
4440
|
+
'background:#000;' + // set background color
|
|
4441
|
+
'touch-action:none;' + // prevent mobile pinch to resize
|
|
4442
|
+
'user-select:none;' + // prevent mobile hold to select
|
|
4443
|
+
'-webkit-user-select:none'; // compatibility for ios
|
|
4352
4444
|
document.body.style = styleBody;
|
|
4353
4445
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
4354
4446
|
mainContext = mainCanvas.getContext('2d');
|
|
@@ -4361,8 +4453,10 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4361
4453
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
4362
4454
|
overlayContext = overlayCanvas.getContext('2d');
|
|
4363
4455
|
|
|
4364
|
-
// set canvas style
|
|
4365
|
-
const styleCanvas = 'position:absolute;
|
|
4456
|
+
// set canvas style
|
|
4457
|
+
const styleCanvas = 'position:absolute;' +
|
|
4458
|
+
'top:50%;left:50%;transform:translate(-50%,-50%);' + // center the canvas
|
|
4459
|
+
(canvasPixelated?'image-rendering:pixelated':''); // set pixelated rendering
|
|
4366
4460
|
(glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
|
|
4367
4461
|
|
|
4368
4462
|
gameInit();
|
|
@@ -4370,7 +4464,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4370
4464
|
};
|
|
4371
4465
|
|
|
4372
4466
|
// frame time tracking
|
|
4373
|
-
let frameTimeLastMS = 0, frameTimeBufferMS, averageFPS;
|
|
4467
|
+
let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS;
|
|
4374
4468
|
|
|
4375
4469
|
// main update loop
|
|
4376
4470
|
function engineUpdate(frameTimeMS=0)
|
|
@@ -4495,7 +4589,7 @@ function enginePreRender()
|
|
|
4495
4589
|
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
4496
4590
|
|
|
4497
4591
|
// disable smoothing for pixel art
|
|
4498
|
-
mainContext.imageSmoothingEnabled = !
|
|
4592
|
+
mainContext.imageSmoothingEnabled = !canvasPixelated;
|
|
4499
4593
|
|
|
4500
4594
|
// setup gl rendering if enabled
|
|
4501
4595
|
glEnable && glPreRender();
|
|
@@ -4509,7 +4603,7 @@ function engineObjectsUpdate()
|
|
|
4509
4603
|
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
4510
4604
|
|
|
4511
4605
|
// recursive object update
|
|
4512
|
-
|
|
4606
|
+
function updateObject(o)
|
|
4513
4607
|
{
|
|
4514
4608
|
if (!o.destroyed)
|
|
4515
4609
|
{
|
|
@@ -4561,4 +4655,4 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
|
|
|
4561
4655
|
for (const o of objects)
|
|
4562
4656
|
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
|
4563
4657
|
}
|
|
4564
|
-
}
|
|
4658
|
+
}
|