littlejsengine 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/build.bat +2 -1
- package/engine/engine.all.js +1320 -930
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.release.js +965 -601
- package/engine/engine.js +71 -60
- package/engine/engineAudio.js +80 -22
- package/engine/engineBuild.bat +2 -2
- package/engine/engineDebug.js +65 -36
- package/engine/engineDraw.js +71 -56
- package/engine/engineInput.js +178 -28
- package/engine/engineMedals.js +66 -58
- package/engine/engineObject.js +86 -71
- package/engine/engineParticles.js +101 -63
- package/engine/engineRelease.js +4 -1
- package/engine/engineSettings.js +62 -53
- package/engine/engineTileLayer.js +99 -83
- package/engine/engineUtilities.js +95 -59
- package/engine/engineWebGL.js +53 -48
- package/examples/breakout/game.js +1 -1
- package/examples/breakout/gameObjects.js +2 -2
- package/examples/breakout/index.html +7 -5
- package/examples/platformer/game.js +7 -4
- package/examples/platformer/gameEffects.js +8 -11
- package/examples/platformer/gameLevel.js +15 -8
- package/examples/platformer/gameObjects.js +2 -2
- package/examples/platformer/gamePlayer.js +6 -6
- package/examples/platformer/index.html +10 -8
- package/examples/puzzle/game.js +3 -3
- package/examples/puzzle/index.html +6 -4
- package/examples/stress/index.html +7 -5
- package/game.js +10 -10
- package/index.html +17 -15
- package/package.json +4 -7
- package/tiles.png +0 -0
- package/LittleJS.zip +0 -0
- package/docs/Audio.html +0 -2264
- package/docs/Color.html +0 -2506
- package/docs/Debug.html +0 -2929
- package/docs/Draw.html +0 -3854
- package/docs/EngineObject.html +0 -2506
- package/docs/Input.html +0 -2478
- package/docs/Medal.html +0 -992
- package/docs/Medals.html +0 -624
- package/docs/Music.html +0 -569
- package/docs/Newgrounds.html +0 -1384
- package/docs/Particle.html +0 -567
- package/docs/ParticleEmitter.html +0 -1537
- package/docs/Random.html +0 -1770
- package/docs/Settings.html +0 -2885
- package/docs/Sound.html +0 -998
- package/docs/TileLayer.html +0 -3211
- package/docs/TileLayerData.html +0 -569
- package/docs/Timer.html +0 -1168
- package/docs/Utilities.html +0 -3266
- package/docs/Vector2.html +0 -3956
- package/docs/WebGL.html +0 -1842
- package/docs/engine.js.html +0 -428
- package/docs/engineAudio.js.html +0 -567
- package/docs/engineDebug.js.html +0 -721
- package/docs/engineDraw.js.html +0 -409
- package/docs/engineInput.js.html +0 -397
- package/docs/engineMedals.js.html +0 -445
- package/docs/engineObject.js.html +0 -492
- package/docs/engineParticles.js.html +0 -399
- package/docs/engineSettings.js.html +0 -358
- package/docs/engineTileLayer.js.html +0 -482
- package/docs/engineUtilities.js.html +0 -600
- package/docs/engineWebGL.js.html +0 -507
- package/docs/fonts/MavenPro-Regular.ttf +0 -0
- package/docs/fonts/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Muli-Black.ttf +0 -0
- package/docs/fonts/OFL-hind.txt +0 -93
- package/docs/fonts/OFL-montserrat.txt +0 -93
- package/docs/global.html +0 -1635
- package/docs/index.html +0 -243
- package/docs/scripts/fix-code-block.js +0 -53
- package/docs/scripts/fix-navbar.js +0 -25
- package/docs/scripts/linenumber.js +0 -25
- package/docs/scripts/misc.js +0 -217
- package/docs/scripts/resize.js +0 -85
- package/docs/scripts/search.js +0 -83
- package/docs/scripts/third-party/Apache-License-2.0.txt +0 -202
- package/docs/scripts/third-party/fuse.js +0 -9
- package/docs/scripts/third-party/lang-css.js +0 -2
- package/docs/scripts/third-party/prettify.js +0 -28
- package/docs/static/favicon.png +0 -0
- package/docs/styles/clean-jsdoc-theme-base.css +0 -393
- package/docs/styles/clean-jsdoc-theme-dark.css +0 -324
- package/docs/styles/clean-jsdoc-theme-light.css +0 -319
- package/docs/styles/reset.css +0 -346
- package/docs/styles/third-party/ionicons.min.css +0 -11
- package/docs/styles/third-party/prettify-jsdoc.css +0 -111
- package/docs/styles/third-party/prettify-tomorrow.css +0 -5
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
|
-
LittleJS - Release
|
|
2
|
+
LittleJS - Release Build
|
|
3
|
+
MIT License - Copyright 2021 Frank Force
|
|
4
|
+
|
|
3
5
|
- This file is used for release builds in place of engineDebug.js
|
|
4
6
|
- Debug functionality will be disabled to lower size and increase performance
|
|
5
7
|
*/
|
|
@@ -26,15 +28,16 @@ const debugCircle = ()=> {}
|
|
|
26
28
|
const debugPoint = ()=> {}
|
|
27
29
|
const debugLine = ()=> {}
|
|
28
30
|
const debugAABB = ()=> {}
|
|
31
|
+
const debugText = ()=> {}
|
|
29
32
|
const debugClear = ()=> {}
|
|
30
33
|
const debugSaveCanvas = ()=> {}
|
|
31
34
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
35
|
+
* LittleJS Utility Classes and Functions
|
|
36
|
+
* <br> - General purpose math library
|
|
37
|
+
* <br> - Vector2 - fast, simple, easy 2D vector class
|
|
38
|
+
* <br> - Color - holds a rgba color with some math functions
|
|
39
|
+
* <br> - Timer - tracks time automatically
|
|
40
|
+
* @namespace Utilities
|
|
38
41
|
*/
|
|
39
42
|
|
|
40
43
|
'use strict';
|
|
@@ -44,23 +47,12 @@ const debugSaveCanvas = ()=> {}
|
|
|
44
47
|
* @memberof Utilities */
|
|
45
48
|
const PI = Math.PI;
|
|
46
49
|
|
|
47
|
-
/** True if running a Chromium based browser
|
|
48
|
-
* @const
|
|
49
|
-
* @memberof Utilities */
|
|
50
|
-
const isChrome = window['chrome'];
|
|
51
|
-
|
|
52
50
|
/** Returns absoulte value of value passed in
|
|
53
51
|
* @param {Number} value
|
|
54
52
|
* @return {Number}
|
|
55
53
|
* @memberof Utilities */
|
|
56
54
|
const abs = (a)=> a < 0 ? -a : a;
|
|
57
55
|
|
|
58
|
-
/** Returns the sign of value passed in
|
|
59
|
-
* @param {Number} value
|
|
60
|
-
* @return {Number}
|
|
61
|
-
* @memberof Utilities */
|
|
62
|
-
const sign = (a)=> a < 0 ? -1 : 1;
|
|
63
|
-
|
|
64
56
|
/** Returns lowest of two values passed in
|
|
65
57
|
* @param {Number} valueA
|
|
66
58
|
* @param {Number} valueB
|
|
@@ -75,54 +67,54 @@ const min = (a, b)=> a < b ? a : b;
|
|
|
75
67
|
* @memberof Utilities */
|
|
76
68
|
const max = (a, b)=> a > b ? a : b;
|
|
77
69
|
|
|
70
|
+
/** Returns the sign of value passed in (also returns 1 if 0)
|
|
71
|
+
* @param {Number} value
|
|
72
|
+
* @return {Number}
|
|
73
|
+
* @memberof Utilities */
|
|
74
|
+
const sign = (a)=> a < 0 ? -1 : 1;
|
|
75
|
+
|
|
78
76
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
79
77
|
* @param {Number} dividend
|
|
80
|
-
* @param {Number} divisor
|
|
78
|
+
* @param {Number} [divisor=1]
|
|
81
79
|
* @return {Number}
|
|
82
80
|
* @memberof Utilities */
|
|
83
|
-
const mod = (a, b)=> ((a % b) + b) % b;
|
|
81
|
+
const mod = (a, b=1)=> ((a % b) + b) % b;
|
|
84
82
|
|
|
85
83
|
/** Clamps the value beween max and min
|
|
86
84
|
* @param {Number} value
|
|
87
|
-
* @param {Number} [max=1]
|
|
88
85
|
* @param {Number} [min=0]
|
|
86
|
+
* @param {Number} [max=1]
|
|
89
87
|
* @return {Number}
|
|
90
88
|
* @memberof Utilities */
|
|
91
|
-
const clamp = (v,
|
|
89
|
+
const clamp = (v, min=0, max=1)=> v < min ? min : v > max ? max : v;
|
|
92
90
|
|
|
93
91
|
/** Returns what percentage the value is between max and min
|
|
94
92
|
* @param {Number} value
|
|
95
|
-
* @param {Number} [max=1]
|
|
96
93
|
* @param {Number} [min=0]
|
|
94
|
+
* @param {Number} [max=1]
|
|
97
95
|
* @return {Number}
|
|
98
96
|
* @memberof Utilities */
|
|
99
|
-
const percent = (v,
|
|
97
|
+
const percent = (v, min=0, max=1)=> max-min ? clamp((v-min) / (max-min)) : 0;
|
|
100
98
|
|
|
101
99
|
/** Linearly interpolates the percent value between max and min
|
|
102
100
|
* @param {Number} percent
|
|
103
|
-
* @param {Number} [max=1]
|
|
104
101
|
* @param {Number} [min=0]
|
|
102
|
+
* @param {Number} [max=1]
|
|
105
103
|
* @return {Number}
|
|
106
104
|
* @memberof Utilities */
|
|
107
|
-
const lerp = (p,
|
|
105
|
+
const lerp = (p, min=0, max=1)=> min + clamp(p) * (max-min);
|
|
108
106
|
|
|
109
|
-
/**
|
|
110
|
-
* @param {Number} t - time in seconds
|
|
111
|
-
* @return {String}
|
|
112
|
-
* @memberof Utilities */
|
|
113
|
-
const formatTime = (t)=> (t/60|0)+':'+(t%60<10?'0':'')+(t%60|0);
|
|
114
|
-
|
|
115
|
-
/** Returns the nearest power of two not less then the value
|
|
107
|
+
/** Applies smoothstep function to the percentage value
|
|
116
108
|
* @param {Number} value
|
|
117
109
|
* @return {Number}
|
|
118
110
|
* @memberof Utilities */
|
|
119
|
-
const
|
|
111
|
+
const smoothStep = (p)=> p * p * (3 - 2 * p);
|
|
120
112
|
|
|
121
|
-
/**
|
|
113
|
+
/** Returns the nearest power of two not less then the value
|
|
122
114
|
* @param {Number} value
|
|
123
115
|
* @return {Number}
|
|
124
116
|
* @memberof Utilities */
|
|
125
|
-
const
|
|
117
|
+
const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
|
|
126
118
|
|
|
127
119
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
128
120
|
* @param {Vector2} pointA - Center of box A
|
|
@@ -141,6 +133,12 @@ const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x & abs(
|
|
|
141
133
|
* @memberof Utilities */
|
|
142
134
|
const wave = (frequency=1, amplitude=1, t=time)=> amplitude/2 * (1 - Math.cos(t*frequency*2*PI));
|
|
143
135
|
|
|
136
|
+
/** Formats seconds to mm:ss style for display purposes
|
|
137
|
+
* @param {Number} t - time in seconds
|
|
138
|
+
* @return {String}
|
|
139
|
+
* @memberof Utilities */
|
|
140
|
+
const formatTime = (t)=> (t/60|0)+':'+(t%60<10?'0':'')+(t%60|0);
|
|
141
|
+
|
|
144
142
|
///////////////////////////////////////////////////////////////////////////////
|
|
145
143
|
|
|
146
144
|
/** Random global functions
|
|
@@ -163,7 +161,7 @@ const randInt = (a=1, b=0)=> rand(a,b)|0;
|
|
|
163
161
|
/** Randomly returns either -1 or 1
|
|
164
162
|
* @return {Number}
|
|
165
163
|
* @memberof Random */
|
|
166
|
-
const randSign = ()=> (rand(2)|0)*2-1;
|
|
164
|
+
const randSign = ()=> (rand(2)|0) * 2 - 1;
|
|
167
165
|
|
|
168
166
|
/** Returns a random Vector2 within a circular shape
|
|
169
167
|
* @param {Number} [radius=1]
|
|
@@ -199,25 +197,46 @@ let randSeed = 1;
|
|
|
199
197
|
const randSeeded = (a=1, b=0)=>
|
|
200
198
|
{
|
|
201
199
|
randSeed ^= randSeed << 13; randSeed ^= randSeed >>> 17; randSeed ^= randSeed << 5; // xorshift
|
|
202
|
-
return b + (a-b)*abs(randSeed % 1e9)/1e9;
|
|
200
|
+
return b + (a-b) * abs(randSeed % 1e9) / 1e9;
|
|
203
201
|
}
|
|
204
202
|
|
|
205
203
|
///////////////////////////////////////////////////////////////////////////////
|
|
206
204
|
|
|
207
|
-
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
205
|
+
/**
|
|
206
|
+
* Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
|
|
207
|
+
* @param {Number} [x=0]
|
|
208
|
+
* @param {Number} [y=0]
|
|
209
|
+
* @return {Vector2}
|
|
210
|
+
* @example
|
|
211
|
+
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
212
|
+
* let b = vec2(a); // copy a into b
|
|
213
|
+
* a = vec2(5); // set a to (5, 5)
|
|
214
|
+
* b = vec2(); // set b to (0, 0)
|
|
215
|
+
* @memberof Utilities
|
|
216
|
+
*/
|
|
212
217
|
const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
|
|
213
218
|
|
|
214
|
-
/**
|
|
219
|
+
/**
|
|
220
|
+
* 2D Vector object with vector math library
|
|
221
|
+
* <br> - Functions do not change this so they can be chained together
|
|
222
|
+
* @example
|
|
223
|
+
* let a = new Vector2(2, 3); // vector with coordinates (2, 3)
|
|
224
|
+
* let b = new Vector2; // vector with coordinates (0, 0)
|
|
225
|
+
* let c = vec2(4, 2); // use the vec2 function to make a Vector2
|
|
226
|
+
* let d = a.add(b).scale(5); // operators can be chained
|
|
227
|
+
*/
|
|
215
228
|
class Vector2
|
|
216
229
|
{
|
|
217
230
|
/** Create a 2D vector with the x and y passed in, can also be created with vec2()
|
|
218
|
-
* @param {Number} [x=0] -
|
|
219
|
-
* @param {Number} [y=0] -
|
|
220
|
-
constructor(x=0, y=0)
|
|
231
|
+
* @param {Number} [x=0] - X axis location
|
|
232
|
+
* @param {Number} [y=0] - Y axis location */
|
|
233
|
+
constructor(x=0, y=0)
|
|
234
|
+
{
|
|
235
|
+
/** @property {Number} - X axis location */
|
|
236
|
+
this.x = x;
|
|
237
|
+
/** @property {Number} - Y axis location */
|
|
238
|
+
this.y = y;
|
|
239
|
+
}
|
|
221
240
|
|
|
222
241
|
/** Returns a new vector that is a copy of this
|
|
223
242
|
* @return {Vector2} */
|
|
@@ -308,10 +327,6 @@ class Vector2
|
|
|
308
327
|
* @return {Vector2} */
|
|
309
328
|
invert() { return new Vector2(this.y, -this.x); }
|
|
310
329
|
|
|
311
|
-
/** Returns a copy of this vector with the axies flipped
|
|
312
|
-
* @return {Vector2} */
|
|
313
|
-
flip() { return new Vector2(this.y, this.x); }
|
|
314
|
-
|
|
315
330
|
/** Returns a copy of this vector with each axis floored
|
|
316
331
|
* @return {Vector2} */
|
|
317
332
|
floor() { return new Vector2(Math.floor(this.x), Math.floor(this.y)); }
|
|
@@ -334,15 +349,31 @@ class Vector2
|
|
|
334
349
|
|
|
335
350
|
///////////////////////////////////////////////////////////////////////////////
|
|
336
351
|
|
|
337
|
-
/**
|
|
352
|
+
/**
|
|
353
|
+
* Color object (red, green, blue, alpha) with some helpful functions
|
|
354
|
+
* @example
|
|
355
|
+
* let a = new Color; // white
|
|
356
|
+
* let b = new Color(1, 0, 0); // red
|
|
357
|
+
* let c = new Color(0, 0, 0, 0); // transparent black
|
|
358
|
+
*/
|
|
338
359
|
class Color
|
|
339
360
|
{
|
|
340
361
|
/** Create a color with the components passed in, white by default
|
|
341
|
-
* @param {Number} [
|
|
342
|
-
* @param {Number} [
|
|
343
|
-
* @param {Number} [
|
|
344
|
-
* @param {Number} [
|
|
345
|
-
constructor(r=1, g=1, b=1, a=1)
|
|
362
|
+
* @param {Number} [red=1]
|
|
363
|
+
* @param {Number} [green=1]
|
|
364
|
+
* @param {Number} [blue=1]
|
|
365
|
+
* @param {Number} [alpha=1] */
|
|
366
|
+
constructor(r=1, g=1, b=1, a=1)
|
|
367
|
+
{
|
|
368
|
+
/** @property {Number} - Red */
|
|
369
|
+
this.r = r;
|
|
370
|
+
/** @property {Number} - Green */
|
|
371
|
+
this.g = g;
|
|
372
|
+
/** @property {Number} - Blue */
|
|
373
|
+
this.b = b;
|
|
374
|
+
/** @property {Number} - Alpha */
|
|
375
|
+
this.a = a;
|
|
376
|
+
}
|
|
346
377
|
|
|
347
378
|
/** Returns a new color that is a copy of this
|
|
348
379
|
* @return {Color} */
|
|
@@ -439,7 +470,15 @@ class Color
|
|
|
439
470
|
|
|
440
471
|
///////////////////////////////////////////////////////////////////////////////
|
|
441
472
|
|
|
442
|
-
/**
|
|
473
|
+
/**
|
|
474
|
+
* Timer object tracks how long has passed since it was set
|
|
475
|
+
* @example
|
|
476
|
+
* let a = new Timer; // creates a timer that is not set
|
|
477
|
+
* a.set(3); // sets the timer to 3 seconds
|
|
478
|
+
*
|
|
479
|
+
* let b = new Timer(1); // creates a timer with 1 second left
|
|
480
|
+
* b.unset(); // unsets the timer
|
|
481
|
+
*/
|
|
443
482
|
class Timer
|
|
444
483
|
{
|
|
445
484
|
/** Create a timer object set time passed in
|
|
@@ -465,17 +504,17 @@ class Timer
|
|
|
465
504
|
* @return {Boolean} */
|
|
466
505
|
elapsed() { return time > this.time; }
|
|
467
506
|
|
|
468
|
-
/** Get how long since elapsed, returns 0 if not set
|
|
507
|
+
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
469
508
|
* @return {Number} */
|
|
470
509
|
get() { return this.isSet()? time - this.time : 0; }
|
|
471
510
|
|
|
472
511
|
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
473
512
|
* @return {Number} */
|
|
474
|
-
getPercent() { return this.isSet()? percent(this.time - time,
|
|
513
|
+
getPercent() { return this.isSet()? percent(this.time - time, this.setTime, 0) : 0; }
|
|
475
514
|
}
|
|
476
515
|
/**
|
|
477
|
-
*
|
|
478
|
-
*
|
|
516
|
+
* LittleJS Engine Settings
|
|
517
|
+
* @namespace Settings
|
|
479
518
|
*/
|
|
480
519
|
|
|
481
520
|
'use strict';
|
|
@@ -483,35 +522,27 @@ class Timer
|
|
|
483
522
|
///////////////////////////////////////////////////////////////////////////////
|
|
484
523
|
// Display settings
|
|
485
524
|
|
|
486
|
-
/** The max
|
|
487
|
-
* @
|
|
488
|
-
* @memberof Settings */
|
|
489
|
-
const maxWidth = 1920;
|
|
490
|
-
|
|
491
|
-
/** The max height of the canvas, centered if window is larger
|
|
492
|
-
* @default
|
|
493
|
-
* @memberof Settings */
|
|
494
|
-
const maxHeight = 1200; // up to 1080p and 16:10
|
|
495
|
-
|
|
496
|
-
/** Fixed witdh, if enabled cavnvas size never changes
|
|
525
|
+
/** The max size of the canvas, centered if window is larger
|
|
526
|
+
* @type {Vector2}
|
|
497
527
|
* @default
|
|
498
528
|
* @memberof Settings */
|
|
499
|
-
let
|
|
529
|
+
let canvasMaxSize = vec2(1920, 1200);
|
|
500
530
|
|
|
501
|
-
/** Fixed
|
|
531
|
+
/** Fixed size of the canvas, if enabled cavnvas size never changes
|
|
532
|
+
* @type {Vector2}
|
|
502
533
|
* @default
|
|
503
534
|
* @memberof Settings */
|
|
504
|
-
let
|
|
535
|
+
let canvasFixedSize = vec2();
|
|
505
536
|
|
|
506
|
-
/**
|
|
537
|
+
/** Disables anti aliasing for pixel art if true
|
|
507
538
|
* @default
|
|
508
539
|
* @memberof Settings */
|
|
509
|
-
let
|
|
540
|
+
let cavasPixelated = 1;
|
|
510
541
|
|
|
511
542
|
/** Default font used for text rendering
|
|
512
543
|
* @default
|
|
513
544
|
* @memberof Settings */
|
|
514
|
-
let
|
|
545
|
+
let fontDefault = 'arial';
|
|
515
546
|
|
|
516
547
|
///////////////////////////////////////////////////////////////////////////////
|
|
517
548
|
// Tile sheet settings
|
|
@@ -520,17 +551,12 @@ let defaultFont = 'arial';
|
|
|
520
551
|
* @type {Vector2}
|
|
521
552
|
* @default
|
|
522
553
|
* @memberof Settings */
|
|
523
|
-
|
|
554
|
+
let tileSizeDefault = vec2(16);
|
|
524
555
|
|
|
525
556
|
/** Prevent tile bleeding from neighbors in pixels
|
|
526
557
|
* @default
|
|
527
558
|
* @memberof Settings */
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
/** Use crisp pixels for pixel art if true
|
|
531
|
-
* @default
|
|
532
|
-
* @memberof Settings */
|
|
533
|
-
let pixelated = 1;
|
|
559
|
+
let tileFixBleedScale = .3;
|
|
534
560
|
|
|
535
561
|
///////////////////////////////////////////////////////////////////////////////
|
|
536
562
|
// Object settings
|
|
@@ -539,43 +565,48 @@ let pixelated = 1;
|
|
|
539
565
|
* @type {Vector2}
|
|
540
566
|
* @default
|
|
541
567
|
* @memberof Settings */
|
|
542
|
-
|
|
568
|
+
let objectDefaultSize = vec2(1);
|
|
543
569
|
|
|
544
570
|
/** Default object mass for collison calcuations (how heavy objects are)
|
|
545
571
|
* @default
|
|
546
572
|
* @memberof Settings */
|
|
547
|
-
|
|
573
|
+
let objectDefaultMass = 1;
|
|
548
574
|
|
|
549
575
|
/** How much to slow velocity by each frame (0-1)
|
|
550
576
|
* @default
|
|
551
577
|
* @memberof Settings */
|
|
552
|
-
|
|
578
|
+
let objectDefaultDamping = .99;
|
|
553
579
|
|
|
554
580
|
/** How much to slow angular velocity each frame (0-1)
|
|
555
581
|
* @default
|
|
556
582
|
* @memberof Settings */
|
|
557
|
-
|
|
583
|
+
let objectDefaultAngleDamping = .99;
|
|
558
584
|
|
|
559
585
|
/** How much to bounce when a collision occurs (0-1)
|
|
560
586
|
* @default
|
|
561
587
|
* @memberof Settings */
|
|
562
|
-
|
|
588
|
+
let objectDefaultElasticity = 0;
|
|
563
589
|
|
|
564
590
|
/** How much to slow when touching (0-1)
|
|
565
591
|
* @default
|
|
566
592
|
* @memberof Settings */
|
|
567
|
-
|
|
593
|
+
let objectDefaultFriction = .8;
|
|
568
594
|
|
|
569
595
|
/** Clamp max speed to avoid fast objects missing collisions
|
|
570
596
|
* @default
|
|
571
597
|
* @memberof Settings */
|
|
572
|
-
|
|
598
|
+
let objectMaxSpeed = 1;
|
|
573
599
|
|
|
574
600
|
/** How much gravity to apply to objects along the Y axis, negative is down
|
|
575
601
|
* @default
|
|
576
602
|
* @memberof Settings */
|
|
577
603
|
let gravity = 0;
|
|
578
604
|
|
|
605
|
+
/** Scales emit rate of particles, useful for low graphics mode (0 disables particle emitters)
|
|
606
|
+
* @default
|
|
607
|
+
* @memberof Settings */
|
|
608
|
+
let particleEmitRateScale = 1;
|
|
609
|
+
|
|
579
610
|
///////////////////////////////////////////////////////////////////////////////
|
|
580
611
|
// Camera settings
|
|
581
612
|
|
|
@@ -588,7 +619,7 @@ let cameraPos = vec2();
|
|
|
588
619
|
/** Scale of camera in world space
|
|
589
620
|
* @default
|
|
590
621
|
* @memberof Settings */
|
|
591
|
-
let cameraScale = max(
|
|
622
|
+
let cameraScale = max(tileSizeDefault.x, tileSizeDefault.y);
|
|
592
623
|
|
|
593
624
|
///////////////////////////////////////////////////////////////////////////////
|
|
594
625
|
// WebGL settings
|
|
@@ -596,7 +627,7 @@ let cameraScale = max(defaultTileSize.x, defaultTileSize.y);
|
|
|
596
627
|
/** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
|
|
597
628
|
* @default
|
|
598
629
|
* @memberof Settings */
|
|
599
|
-
|
|
630
|
+
let glEnable = 1;
|
|
600
631
|
|
|
601
632
|
/** Fixes slow rendering in some browsers by not compositing the WebGL canvas
|
|
602
633
|
* @default
|
|
@@ -609,45 +640,62 @@ let glOverlay = 1;
|
|
|
609
640
|
/** Should gamepads be allowed
|
|
610
641
|
* @default
|
|
611
642
|
* @memberof Settings */
|
|
612
|
-
|
|
643
|
+
let gamepadsEnable = 1;
|
|
644
|
+
|
|
645
|
+
/** If true, the dpad input is also routed to the left analog stick (for better accessability)
|
|
646
|
+
* @default
|
|
647
|
+
* @memberof Settings */
|
|
648
|
+
let gamepadDirectionEmulateStick = 1;
|
|
649
|
+
|
|
650
|
+
/** If true the WASD keys are also routed to the direction keys (for better accessability)
|
|
651
|
+
* @default
|
|
652
|
+
* @memberof Settings */
|
|
653
|
+
let inputWASDEmulateDirection = 1;
|
|
654
|
+
|
|
655
|
+
/** True if touch gamepad should appear on mobile devices
|
|
656
|
+
* <br> - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
657
|
+
* <br> - Must be set by end of gameInit to be activated
|
|
658
|
+
* @default
|
|
659
|
+
* @memberof Settings */
|
|
660
|
+
let touchGamepadEnable = 0;
|
|
613
661
|
|
|
614
|
-
/**
|
|
662
|
+
/** Size of virutal gamepad for touch devices in pixels
|
|
615
663
|
* @default
|
|
616
664
|
* @memberof Settings */
|
|
617
|
-
|
|
665
|
+
let touchGamepadSize = 80;
|
|
618
666
|
|
|
619
|
-
/**
|
|
667
|
+
/** Transparency of touch gamepad overlay
|
|
620
668
|
* @default
|
|
621
669
|
* @memberof Settings */
|
|
622
|
-
|
|
670
|
+
let touchGamepadAlpha = .3;
|
|
623
671
|
|
|
624
|
-
/**
|
|
672
|
+
/** Allow vibration hardware if it exists
|
|
625
673
|
* @default
|
|
626
674
|
* @memberof Settings */
|
|
627
|
-
|
|
675
|
+
let vibrateEnable = 1;
|
|
628
676
|
|
|
629
677
|
///////////////////////////////////////////////////////////////////////////////
|
|
630
678
|
// Audio settings
|
|
631
679
|
|
|
632
|
-
/**
|
|
680
|
+
/** Volume scale to apply to all sound, music and speech
|
|
633
681
|
* @default
|
|
634
682
|
* @memberof Settings */
|
|
635
|
-
|
|
683
|
+
let soundVolume = .5;
|
|
636
684
|
|
|
637
|
-
/**
|
|
685
|
+
/** All audio code can be disabled and removed from build
|
|
638
686
|
* @default
|
|
639
687
|
* @memberof Settings */
|
|
640
|
-
let
|
|
688
|
+
let soundEnable = 1;
|
|
641
689
|
|
|
642
690
|
/** Default range where sound no longer plays
|
|
643
691
|
* @default
|
|
644
692
|
* @memberof Settings */
|
|
645
|
-
|
|
693
|
+
let soundDefaultRange = 30;
|
|
646
694
|
|
|
647
695
|
/** Default range percent to start tapering off sound (0-1)
|
|
648
696
|
* @default
|
|
649
697
|
* @memberof Settings */
|
|
650
|
-
|
|
698
|
+
let soundDefaultTaper = .7;
|
|
651
699
|
|
|
652
700
|
///////////////////////////////////////////////////////////////////////////////
|
|
653
701
|
// Medals settings
|
|
@@ -655,27 +703,27 @@ const defaultSoundTaper = .7;
|
|
|
655
703
|
/** How long to show medals for in seconds
|
|
656
704
|
* @default
|
|
657
705
|
* @memberof Settings */
|
|
658
|
-
|
|
706
|
+
let medalDisplayTime = 5;
|
|
659
707
|
|
|
660
708
|
/** How quickly to slide on/off medals in seconds
|
|
661
709
|
* @default
|
|
662
710
|
* @memberof Settings */
|
|
663
|
-
|
|
711
|
+
let medalDisplaySlideTime = .5;
|
|
664
712
|
|
|
665
713
|
/** Width of medal display
|
|
666
714
|
* @default
|
|
667
715
|
* @memberof Settings */
|
|
668
|
-
|
|
716
|
+
let medalDisplayWidth = 640;
|
|
669
717
|
|
|
670
718
|
/** Height of medal display
|
|
671
719
|
* @default
|
|
672
720
|
* @memberof Settings */
|
|
673
|
-
|
|
721
|
+
let medalDisplayHeight = 80;
|
|
674
722
|
|
|
675
723
|
/** Size of icon in medal display
|
|
676
724
|
* @default
|
|
677
725
|
* @memberof Settings */
|
|
678
|
-
|
|
726
|
+
let medalDisplayIconSize = 50;
|
|
679
727
|
/*
|
|
680
728
|
LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
681
729
|
MIT License - Copyright 2021 Frank Force
|
|
@@ -700,21 +748,21 @@ const medalDisplayIconSize = 80;
|
|
|
700
748
|
const engineName = 'LittleJS';
|
|
701
749
|
|
|
702
750
|
/** Version of engine */
|
|
703
|
-
const engineVersion = '1.1.
|
|
751
|
+
const engineVersion = '1.1.3';
|
|
704
752
|
|
|
705
753
|
/** Frames per second to update objects
|
|
706
754
|
* @default */
|
|
707
|
-
const
|
|
755
|
+
const frameRate = 60;
|
|
708
756
|
|
|
709
757
|
/** How many seconds each frame lasts, engine uses a fixed time step
|
|
710
758
|
* @default 1/60 */
|
|
711
|
-
const timeDelta = 1/
|
|
759
|
+
const timeDelta = 1/frameRate;
|
|
712
760
|
|
|
713
761
|
/** Array containing all engine objects */
|
|
714
762
|
let engineObjects = [];
|
|
715
763
|
|
|
716
|
-
/** Array containing only objects that are set to collide with other objects (for optimization) */
|
|
717
|
-
let
|
|
764
|
+
/** Array containing only objects that are set to collide with other objects this frame (for optimization) */
|
|
765
|
+
let engineObjectsCollide = [];
|
|
718
766
|
|
|
719
767
|
/** Current update frame, used to calculate time */
|
|
720
768
|
let frame = 0;
|
|
@@ -729,36 +777,40 @@ let timeReal = 0;
|
|
|
729
777
|
let paused = 0;
|
|
730
778
|
|
|
731
779
|
// Engine internal variables not exposed to documentation
|
|
732
|
-
let frameTimeLastMS = 0, frameTimeBufferMS = 0,
|
|
733
|
-
|
|
780
|
+
let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
|
|
781
|
+
|
|
782
|
+
// Engine stat tracking, if showWatermark is true
|
|
783
|
+
let averageFPS, drawCount;
|
|
784
|
+
|
|
785
|
+
// css text used for elements created by engine
|
|
786
|
+
const styleBody = 'margin:0;overflow:hidden;background:#000' +
|
|
787
|
+
';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none';
|
|
788
|
+
const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
734
789
|
|
|
735
790
|
///////////////////////////////////////////////////////////////////////////////
|
|
736
791
|
|
|
737
792
|
/** Start up LittleJS engine with your callback functions
|
|
738
|
-
* @param {Function} gameInit
|
|
739
|
-
* @param {Function} gameUpdate
|
|
740
|
-
* @param {Function} gameUpdatePost
|
|
741
|
-
* @param {Function} gameRender
|
|
742
|
-
* @param {Function} gameRenderPost
|
|
743
|
-
* @param {String} tileImageSource
|
|
793
|
+
* @param {Function} gameInit - Called once after the engine starts up, setup the game
|
|
794
|
+
* @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
|
|
795
|
+
* @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
|
|
796
|
+
* @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
|
|
797
|
+
* @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
|
|
798
|
+
* @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading
|
|
744
799
|
*/
|
|
745
800
|
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
|
|
746
801
|
{
|
|
747
|
-
// init engine when tiles load
|
|
748
|
-
tileImage.onload = ()=>
|
|
802
|
+
// init engine when tiles load or fail to load
|
|
803
|
+
tileImage.onerror = tileImage.onload = ()=>
|
|
749
804
|
{
|
|
750
805
|
// save tile image info
|
|
751
|
-
|
|
806
|
+
tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
752
807
|
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
753
|
-
shrinkTilesX = tileBleedShrinkFix/tileImageSize.x;
|
|
754
|
-
shrinkTilesY = tileBleedShrinkFix/tileImageSize.y;
|
|
755
808
|
|
|
756
809
|
// setup html
|
|
810
|
+
document.body.style = styleBody;
|
|
757
811
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
758
|
-
document.body.style = 'margin:0;overflow:hidden;background:#000';
|
|
759
|
-
mainCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)' +
|
|
760
|
-
(pixelated ? ';image-rendering:crisp-edges;image-rendering:pixelated' : ''); // pixelated rendering
|
|
761
812
|
mainContext = mainCanvas.getContext('2d');
|
|
813
|
+
mainCanvas.style = styleCanvas;
|
|
762
814
|
|
|
763
815
|
// init stuff and start engine
|
|
764
816
|
debugInit();
|
|
@@ -766,10 +818,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
766
818
|
|
|
767
819
|
// create overlay canvas for hud to appear above gl canvas
|
|
768
820
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
769
|
-
overlayCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
770
821
|
overlayContext = overlayCanvas.getContext('2d');
|
|
771
|
-
|
|
822
|
+
overlayCanvas.style = styleCanvas;
|
|
823
|
+
|
|
772
824
|
gameInit();
|
|
825
|
+
touchGamepadCreate();
|
|
773
826
|
engineUpdate();
|
|
774
827
|
};
|
|
775
828
|
|
|
@@ -782,7 +835,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
782
835
|
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
783
836
|
frameTimeLastMS = frameTimeMS;
|
|
784
837
|
if (debug || showWatermark)
|
|
785
|
-
|
|
838
|
+
averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
|
|
786
839
|
if (debug)
|
|
787
840
|
frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
|
|
788
841
|
timeReal += frameTimeDeltaMS / 1e3;
|
|
@@ -808,7 +861,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
808
861
|
}
|
|
809
862
|
|
|
810
863
|
// update multiple frames if necessary in case of slow framerate
|
|
811
|
-
for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 /
|
|
864
|
+
for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
|
|
812
865
|
{
|
|
813
866
|
// update game and objects
|
|
814
867
|
inputUpdate();
|
|
@@ -825,45 +878,39 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
825
878
|
frameTimeBufferMS += deltaSmooth;
|
|
826
879
|
}
|
|
827
880
|
|
|
828
|
-
if (
|
|
881
|
+
if (canvasFixedSize.x)
|
|
829
882
|
{
|
|
830
883
|
// clear set fixed size
|
|
831
|
-
mainCanvas.width =
|
|
832
|
-
mainCanvas.height =
|
|
884
|
+
mainCanvas.width = canvasFixedSize.x;
|
|
885
|
+
mainCanvas.height = canvasFixedSize.y;
|
|
833
886
|
|
|
834
|
-
if
|
|
887
|
+
// fit to window by adding space on top or bottom if necessary
|
|
888
|
+
const aspect = innerWidth / innerHeight;
|
|
889
|
+
const fixedAspect = mainCanvas.width / mainCanvas.height;
|
|
890
|
+
mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
|
|
891
|
+
mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
892
|
+
if (glCanvas)
|
|
835
893
|
{
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const fixedAspect = fixedWidth / fixedHeight;
|
|
839
|
-
mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
|
|
840
|
-
mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
841
|
-
if (glCanvas)
|
|
842
|
-
{
|
|
843
|
-
glCanvas.style.width = mainCanvas.style.width;
|
|
844
|
-
glCanvas.style.height = mainCanvas.style.height;
|
|
845
|
-
}
|
|
894
|
+
glCanvas.style.width = mainCanvas.style.width;
|
|
895
|
+
glCanvas.style.height = mainCanvas.style.height;
|
|
846
896
|
}
|
|
847
897
|
}
|
|
848
898
|
else
|
|
849
899
|
{
|
|
850
900
|
// clear and set size to same as window
|
|
851
|
-
mainCanvas.width = min(innerWidth,
|
|
852
|
-
mainCanvas.height = min(innerHeight,
|
|
901
|
+
mainCanvas.width = min(innerWidth, canvasMaxSize.x);
|
|
902
|
+
mainCanvas.height = min(innerHeight, canvasMaxSize.y);
|
|
853
903
|
}
|
|
854
904
|
|
|
855
|
-
// save canvas size and clear overlay canvas
|
|
856
|
-
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
857
|
-
mainContext.imageSmoothingEnabled = !pixelated; // disable smoothing for pixel art
|
|
858
|
-
|
|
859
905
|
// render sort then render while removing destroyed objects
|
|
860
|
-
|
|
906
|
+
enginePreRender();
|
|
861
907
|
gameRender();
|
|
862
908
|
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
863
909
|
for (const o of engineObjects)
|
|
864
910
|
o.destroyed || o.render();
|
|
865
911
|
gameRenderPost();
|
|
866
912
|
medalsRender();
|
|
913
|
+
touchGamepadRender();
|
|
867
914
|
debugRender();
|
|
868
915
|
glCopyToContext(mainContext);
|
|
869
916
|
|
|
@@ -875,7 +922,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
875
922
|
overlayContext.font = '1em monospace';
|
|
876
923
|
overlayContext.fillStyle = '#000';
|
|
877
924
|
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
878
|
-
+ drawCount + ' / ' + engineObjects.length + ' / ' +
|
|
925
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
926
|
+
+ ' ' + (glEnable ? 'GL' : '2D') ;
|
|
879
927
|
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
880
928
|
overlayContext.fillStyle = '#fff';
|
|
881
929
|
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
@@ -887,12 +935,27 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
887
935
|
tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
|
|
888
936
|
}
|
|
889
937
|
|
|
938
|
+
// called by engine to setup render system
|
|
939
|
+
function enginePreRender()
|
|
940
|
+
{
|
|
941
|
+
// save canvas size and clear canvases
|
|
942
|
+
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
943
|
+
|
|
944
|
+
// disable smoothing for pixel art
|
|
945
|
+
mainContext.imageSmoothingEnabled = !cavasPixelated;
|
|
946
|
+
|
|
947
|
+
// setup gl rendering if enabled
|
|
948
|
+
glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
|
|
949
|
+
}
|
|
890
950
|
|
|
891
951
|
///////////////////////////////////////////////////////////////////////////////
|
|
892
952
|
|
|
893
953
|
/** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
|
|
894
954
|
function engineObjectsUpdate()
|
|
895
955
|
{
|
|
956
|
+
// get list of solid objects for physics optimzation
|
|
957
|
+
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
958
|
+
|
|
896
959
|
// recursive object update
|
|
897
960
|
const updateObject = (o)=>
|
|
898
961
|
{
|
|
@@ -908,42 +971,38 @@ function engineObjectsUpdate()
|
|
|
908
971
|
|
|
909
972
|
// remove destroyed objects
|
|
910
973
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
911
|
-
engineCollideObjects = engineCollideObjects.filter(o=>!o.destroyed);
|
|
912
974
|
|
|
913
975
|
// increment frame and update time
|
|
914
|
-
time = ++frame /
|
|
976
|
+
time = ++frame / frameRate;
|
|
915
977
|
}
|
|
916
978
|
|
|
917
|
-
/**
|
|
979
|
+
/** Destroy and remove all objects */
|
|
918
980
|
function engineObjectsDestroy()
|
|
919
981
|
{
|
|
920
982
|
for (const o of engineObjects)
|
|
921
|
-
o.
|
|
983
|
+
o.parent || o.destroy();
|
|
922
984
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
923
985
|
}
|
|
924
986
|
|
|
925
987
|
/** Triggers a callback for each object within a given area
|
|
926
|
-
* @param {Vector2} [pos]
|
|
927
|
-
* @param {Number} [size]
|
|
928
|
-
* @param {Function} [callbackFunction]
|
|
988
|
+
* @param {Vector2} [pos] - Center of test area
|
|
989
|
+
* @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
|
|
990
|
+
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
929
991
|
* @param {Array} [objects=engineObjects] - List of objects to check */
|
|
930
992
|
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
931
993
|
{
|
|
932
|
-
if (!pos)
|
|
994
|
+
if (!pos) // all objects
|
|
933
995
|
{
|
|
934
|
-
// all objects
|
|
935
996
|
for (const o of objects)
|
|
936
997
|
callbackFunction(o);
|
|
937
998
|
}
|
|
938
|
-
else if (size.x != undefined)
|
|
999
|
+
else if (size.x != undefined) // bounding box test
|
|
939
1000
|
{
|
|
940
|
-
// aabb test
|
|
941
1001
|
for (const o of objects)
|
|
942
1002
|
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
943
1003
|
}
|
|
944
|
-
else
|
|
1004
|
+
else // circle test
|
|
945
1005
|
{
|
|
946
|
-
// circle test
|
|
947
1006
|
const sizeSquared = size*size;
|
|
948
1007
|
for (const o of objects)
|
|
949
1008
|
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
|
@@ -956,51 +1015,83 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
|
|
|
956
1015
|
'use strict';
|
|
957
1016
|
|
|
958
1017
|
/**
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
963
|
-
*
|
|
964
|
-
*
|
|
965
|
-
*
|
|
966
|
-
*
|
|
967
|
-
*
|
|
968
|
-
*
|
|
969
|
-
*
|
|
1018
|
+
* LittleJS Object Base Object Class
|
|
1019
|
+
* <br> - Base object class used by the engine
|
|
1020
|
+
* <br> - Automatically adds self to object list
|
|
1021
|
+
* <br> - Will be updated and rendered each frame
|
|
1022
|
+
* <br> - Renders as a sprite from a tilesheet by default
|
|
1023
|
+
* <br> - Can have color and addtive color applied
|
|
1024
|
+
* <br> - 2d Physics and collision system
|
|
1025
|
+
* <br> - Sorted by renderOrder
|
|
1026
|
+
* <br> - Objects can have children attached
|
|
1027
|
+
* <br> - Parents are updated before children, and set child transform
|
|
1028
|
+
* <br> - Call destroy() to get rid of objects
|
|
1029
|
+
* <br>
|
|
1030
|
+
* <br>The physics system used by objects is simple and fast with some caveats...
|
|
1031
|
+
* <br> - Collision uses the axis aligned size, the object's rotation angle is only for rendering
|
|
1032
|
+
* <br> - Objects are guaranteed to not intersect tile collision from physics
|
|
1033
|
+
* <br> - If an object starts or is moved inside tile collision, it will not collide with that tile
|
|
1034
|
+
* <br> - Collision for objects can be set to be solid to block other objects
|
|
1035
|
+
* <br> - Objects may get pushed into overlapping other solid objects, if so they will push away
|
|
1036
|
+
* <br> - Solid objects are more performance intensive and should be used sparingly
|
|
1037
|
+
* @example
|
|
1038
|
+
* // create an engine object, normally you would first extend the class with your own
|
|
1039
|
+
* const pos = vec2(2,3);
|
|
1040
|
+
* const object = new EngineObject(pos);
|
|
970
1041
|
*/
|
|
971
1042
|
class EngineObject
|
|
972
1043
|
{
|
|
973
|
-
/**
|
|
974
|
-
*
|
|
975
|
-
*
|
|
976
|
-
*
|
|
977
|
-
*
|
|
978
|
-
*
|
|
979
|
-
*
|
|
980
|
-
*
|
|
1044
|
+
/** Create an engine object and adds it to the list of objects
|
|
1045
|
+
* @param {Vector2} [position=new Vector2(0,0)] - World space position of the object
|
|
1046
|
+
* @param {Vector2} [size=objectDefaultSize] - World space size of the object
|
|
1047
|
+
* @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
|
|
1048
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
1049
|
+
* @param {Number} [angle=0] - Angle to rotate the object
|
|
1050
|
+
* @param {Color} [color] - Color to apply to tile when rendered
|
|
1051
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
981
1052
|
*/
|
|
982
|
-
constructor(pos=vec2(), size=
|
|
1053
|
+
constructor(pos=vec2(), size=objectDefaultSize, tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
|
|
983
1054
|
{
|
|
984
1055
|
// set passed in params
|
|
985
1056
|
ASSERT(pos && pos.x != undefined && size.x != undefined); // ensure pos and size are vec2s
|
|
1057
|
+
|
|
1058
|
+
/** @property {Vector2} - World space position of the object */
|
|
986
1059
|
this.pos = pos.copy();
|
|
1060
|
+
/** @property {Vector2} - World space width and height of the object */
|
|
987
1061
|
this.size = size;
|
|
1062
|
+
/** @property {Vector2} - Size of object used for drawing, uses size if not set */
|
|
1063
|
+
this.drawSize;
|
|
1064
|
+
/** @property {Number} - Tile to use to render object, untextured if -1 */
|
|
988
1065
|
this.tileIndex = tileIndex;
|
|
1066
|
+
/** @property {Vector2} - Size of tile in source pixels */
|
|
989
1067
|
this.tileSize = tileSize;
|
|
1068
|
+
/** @property {Number} - Angle to rotate the object */
|
|
990
1069
|
this.angle = angle;
|
|
1070
|
+
/** @property {Color} - Color to apply when rendered */
|
|
991
1071
|
this.color = color;
|
|
1072
|
+
/** @property {Color} - Additive color to apply when rendered */
|
|
1073
|
+
this.additiveColor;
|
|
1074
|
+
|
|
1075
|
+
// set object defaults
|
|
1076
|
+
/** @property {Number} [mass=objectDefaultMass] - How heavy the object is */
|
|
1077
|
+
this.mass = objectDefaultMass;
|
|
1078
|
+
/** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
|
|
1079
|
+
this.damping = objectDefaultDamping;
|
|
1080
|
+
/** @property {Number} [angleDamping=objectDefaultAngleDamping] - How much to slow down rotation each frame (0-1) */
|
|
1081
|
+
this.angleDamping = objectDefaultAngleDamping;
|
|
1082
|
+
/** @property {Number} [elasticity=objectDefaultElasticity] - How bouncy the object is when colliding (0-1) */
|
|
1083
|
+
this.elasticity = objectDefaultElasticity;
|
|
1084
|
+
/** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
|
|
1085
|
+
this.friction = objectDefaultFriction;
|
|
1086
|
+
/** @property {Number} [gravityScale=1] - How much to scale gravity by for this object */
|
|
1087
|
+
this.gravityScale = 1;
|
|
1088
|
+
/** @property {Number} [renderOrder=0] - Objects are sorted by render order */
|
|
1089
|
+
this.renderOrder = renderOrder;
|
|
992
1090
|
|
|
993
|
-
//
|
|
994
|
-
this.mass = defaultObjectMass;
|
|
995
|
-
this.damping = defaultObjectDamping;
|
|
996
|
-
this.angleDamping = defaultObjectAngleDamping;
|
|
997
|
-
this.elasticity = defaultObjectElasticity;
|
|
998
|
-
this.friction = defaultObjectFriction;
|
|
999
|
-
|
|
1000
|
-
// init other object stuff
|
|
1091
|
+
// init other internal object stuff
|
|
1001
1092
|
this.spawnTime = time;
|
|
1002
|
-
this.velocity = vec2(this.collideSolidObjects = this.
|
|
1003
|
-
this.collideTiles =
|
|
1093
|
+
this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
|
|
1094
|
+
this.collideTiles = 1;
|
|
1004
1095
|
this.children = [];
|
|
1005
1096
|
|
|
1006
1097
|
// add to list of objects
|
|
@@ -1020,8 +1111,8 @@ class EngineObject
|
|
|
1020
1111
|
}
|
|
1021
1112
|
|
|
1022
1113
|
// limit max speed to prevent missing collisions
|
|
1023
|
-
this.velocity.x = clamp(this.velocity.x,
|
|
1024
|
-
this.velocity.y = clamp(this.velocity.y,
|
|
1114
|
+
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
1115
|
+
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
1025
1116
|
|
|
1026
1117
|
// apply physics
|
|
1027
1118
|
const oldPos = this.pos.copy();
|
|
@@ -1050,14 +1141,14 @@ class EngineObject
|
|
|
1050
1141
|
{
|
|
1051
1142
|
// check collisions against solid objects
|
|
1052
1143
|
const epsilon = 1e-3; // necessary to push slightly outside of the collision
|
|
1053
|
-
for (const o of
|
|
1144
|
+
for (const o of engineObjectsCollide)
|
|
1054
1145
|
{
|
|
1055
1146
|
// non solid objects don't collide with eachother
|
|
1056
|
-
if (!this.isSolid & !o.isSolid || o.destroyed || o.parent)
|
|
1147
|
+
if (!this.isSolid & !o.isSolid || o.destroyed || o.parent || o == this)
|
|
1057
1148
|
continue;
|
|
1058
1149
|
|
|
1059
1150
|
// check collision
|
|
1060
|
-
if (!isOverlapping(this.pos, this.size, o.pos, o.size)
|
|
1151
|
+
if (!isOverlapping(this.pos, this.size, o.pos, o.size))
|
|
1061
1152
|
continue;
|
|
1062
1153
|
|
|
1063
1154
|
// pass collision to objects
|
|
@@ -1069,7 +1160,7 @@ class EngineObject
|
|
|
1069
1160
|
// if already was touching, try to push away
|
|
1070
1161
|
const deltaPos = oldPos.subtract(o.pos);
|
|
1071
1162
|
const length = deltaPos.length();
|
|
1072
|
-
const pushAwayAccel = .001; // push away if
|
|
1163
|
+
const pushAwayAccel = .001; // push away if already overlapping
|
|
1073
1164
|
const velocity = length < .01 ? randVector(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
|
|
1074
1165
|
this.velocity = this.velocity.add(velocity);
|
|
1075
1166
|
if (o.mass) // push away if not fixed
|
|
@@ -1080,16 +1171,15 @@ class EngineObject
|
|
|
1080
1171
|
}
|
|
1081
1172
|
|
|
1082
1173
|
// check for collision
|
|
1083
|
-
const
|
|
1084
|
-
const
|
|
1085
|
-
const
|
|
1086
|
-
const
|
|
1087
|
-
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sx;
|
|
1174
|
+
const sizeBoth = this.size.add(o.size);
|
|
1175
|
+
const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity; // prefer to push up if small delta
|
|
1176
|
+
const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
|
|
1177
|
+
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
|
|
1088
1178
|
|
|
1089
1179
|
if (smallStepUp || isBlockedY || !isBlockedX) // resolve y collision
|
|
1090
1180
|
{
|
|
1091
1181
|
// push outside object collision
|
|
1092
|
-
this.pos.y = o.pos.y + (
|
|
1182
|
+
this.pos.y = o.pos.y + (sizeBoth.y/2 + epsilon) * sign(oldPos.y - o.pos.y);
|
|
1093
1183
|
if (o.groundObject && wasMovingDown || !o.mass)
|
|
1094
1184
|
{
|
|
1095
1185
|
// set ground object if landed on something
|
|
@@ -1112,15 +1202,14 @@ class EngineObject
|
|
|
1112
1202
|
|
|
1113
1203
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1114
1204
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1115
|
-
this.velocity.y = lerp(elasticity,
|
|
1116
|
-
o.velocity.y = lerp(elasticity,
|
|
1205
|
+
this.velocity.y = lerp(elasticity, inelastic, elastic0);
|
|
1206
|
+
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
1117
1207
|
}
|
|
1118
|
-
debugPhysics && smallStepUp && (abs(oldPos.x - o.pos.x)*2 > sx) && console.log('stepUp', oldPos.y - o.pos.y);
|
|
1119
1208
|
}
|
|
1120
1209
|
if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
|
|
1121
1210
|
{
|
|
1122
1211
|
// push outside collision
|
|
1123
|
-
this.pos.x = o.pos.x + (
|
|
1212
|
+
this.pos.x = o.pos.x + (sizeBoth.x/2 + epsilon) * sign(oldPos.x - o.pos.x);
|
|
1124
1213
|
if (o.mass)
|
|
1125
1214
|
{
|
|
1126
1215
|
// inelastic collision
|
|
@@ -1134,13 +1223,12 @@ class EngineObject
|
|
|
1134
1223
|
|
|
1135
1224
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1136
1225
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1137
|
-
this.velocity.x = lerp(elasticity,
|
|
1138
|
-
o.velocity.x = lerp(elasticity,
|
|
1226
|
+
this.velocity.x = lerp(elasticity, inelastic, elastic0);
|
|
1227
|
+
o.velocity.x = lerp(elasticity, inelastic, elastic1);
|
|
1139
1228
|
}
|
|
1140
1229
|
else // bounce if other object is fixed
|
|
1141
1230
|
this.velocity.x *= -this.elasticity;
|
|
1142
1231
|
}
|
|
1143
|
-
|
|
1144
1232
|
debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
|
|
1145
1233
|
}
|
|
1146
1234
|
}
|
|
@@ -1149,8 +1237,6 @@ class EngineObject
|
|
|
1149
1237
|
// check collision against tiles
|
|
1150
1238
|
if (tileCollisionTest(this.pos, this.size, this))
|
|
1151
1239
|
{
|
|
1152
|
-
//debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
1153
|
-
|
|
1154
1240
|
// if already was stuck in collision, don't do anything
|
|
1155
1241
|
// this should not happen unless something starts in collision
|
|
1156
1242
|
if (!tileCollisionTest(oldPos, this.size, this))
|
|
@@ -1207,19 +1293,19 @@ class EngineObject
|
|
|
1207
1293
|
|
|
1208
1294
|
/** Called to check if a tile collision should be resolved
|
|
1209
1295
|
* @param {Number} tileData - the value of the tile at the position
|
|
1210
|
-
* @param {Vector2} pos
|
|
1211
|
-
* @return {Boolean} true if the collision should be resolved */
|
|
1296
|
+
* @param {Vector2} pos - tile where the collision occured
|
|
1297
|
+
* @return {Boolean} - true if the collision should be resolved */
|
|
1212
1298
|
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
1213
1299
|
|
|
1214
1300
|
/** Called to check if a tile raycast hit
|
|
1215
1301
|
* @param {Number} tileData - the value of the tile at the position
|
|
1216
|
-
* @param {Vector2} pos
|
|
1217
|
-
* @return {Boolean} true if the raycast should hit */
|
|
1302
|
+
* @param {Vector2} pos - tile where the raycast is
|
|
1303
|
+
* @return {Boolean} - true if the raycast should hit */
|
|
1218
1304
|
collideWithTileRaycast(tileData, pos) { return tileData > 0; }
|
|
1219
1305
|
|
|
1220
1306
|
/** Called to check if a tile raycast hit
|
|
1221
1307
|
* @param {EngineObject} object - the object to test against
|
|
1222
|
-
* @return {Boolean} true if the collision should be resolved
|
|
1308
|
+
* @return {Boolean} - true if the collision should be resolved
|
|
1223
1309
|
*/
|
|
1224
1310
|
collideWithObject(o) { return 1; }
|
|
1225
1311
|
|
|
@@ -1263,41 +1349,42 @@ class EngineObject
|
|
|
1263
1349
|
|
|
1264
1350
|
/** Set how this object collides
|
|
1265
1351
|
* @param {boolean} [collideSolidObjects=0] - Does it collide with solid objects
|
|
1266
|
-
* @param {boolean} [isSolid=0]
|
|
1267
|
-
* @param {boolean} [collideTiles=1]
|
|
1352
|
+
* @param {boolean} [isSolid=0] - Does it collide with and block other objects (expensive in large numbers)
|
|
1353
|
+
* @param {boolean} [collideTiles=1] - Does it collide with the tile collision */
|
|
1268
1354
|
setCollision(collideSolidObjects=0, isSolid=0, collideTiles=1)
|
|
1269
1355
|
{
|
|
1270
1356
|
ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
|
|
1271
1357
|
|
|
1272
|
-
// track collidable objects in separate list
|
|
1273
|
-
if (collideSolidObjects && !this.collideSolidObjects)
|
|
1274
|
-
{
|
|
1275
|
-
ASSERT(!engineCollideObjects.includes(this));
|
|
1276
|
-
engineCollideObjects.push(this);
|
|
1277
|
-
}
|
|
1278
|
-
else if (!collideSolidObjects && this.collideSolidObjects)
|
|
1279
|
-
{
|
|
1280
|
-
ASSERT(engineCollideObjects.includes(this))
|
|
1281
|
-
engineCollideObjects.splice(engineCollideObjects.indexOf(this), 1);
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
1358
|
this.collideSolidObjects = collideSolidObjects;
|
|
1285
1359
|
this.isSolid = isSolid;
|
|
1286
1360
|
this.collideTiles = collideTiles;
|
|
1287
1361
|
}
|
|
1288
1362
|
}
|
|
1289
1363
|
/**
|
|
1290
|
-
*
|
|
1291
|
-
*
|
|
1292
|
-
*
|
|
1293
|
-
*
|
|
1294
|
-
*
|
|
1295
|
-
*
|
|
1364
|
+
* LittleJS Drawing System
|
|
1365
|
+
* <br> - Hybrid with both Canvas2D and WebGL available
|
|
1366
|
+
* <br> - Super fast tile sheet rendering with WebGL
|
|
1367
|
+
* <br> - Can apply rotation, mirror, color and additive color
|
|
1368
|
+
* <br> - Many useful utility functions
|
|
1369
|
+
* <br>
|
|
1370
|
+
* <br>LittleJS uses a hybrid rendering solution with the best of both Canvas2D and WebGL.
|
|
1371
|
+
* <br>There are 3 canvas/contexts available to draw to...
|
|
1372
|
+
* <br> - mainCanvas - 2D background canvas, non WebGL stuff like tile layers are drawn here.
|
|
1373
|
+
* <br> - glCanvas - Used by the accelerated WebGL batch rendering system.
|
|
1374
|
+
* <br> - overlayCanvas - Another 2D canvas that appears on top of the other 2 canvases.
|
|
1375
|
+
* <br>
|
|
1376
|
+
* <br>The WebGL rendering system is very fast with some caveats...
|
|
1377
|
+
* <br> - The default setup supports only 1 tile sheet, to support more call glCreateTexture and glSetTexture
|
|
1378
|
+
* <br> - Switching blend modes (additive) or textures causes another draw call which is expensive in excess
|
|
1379
|
+
* <br> - Group additive rendering together using renderOrder to mitigate this issue
|
|
1380
|
+
* <br>
|
|
1381
|
+
* <br>The LittleJS rendering solution is intentionally simple, feel free to adjust it for your needs!
|
|
1382
|
+
* @namespace Draw
|
|
1296
1383
|
*/
|
|
1297
1384
|
|
|
1298
1385
|
'use strict';
|
|
1299
1386
|
|
|
1300
|
-
/**
|
|
1387
|
+
/** Tile sheet for batch rendering system
|
|
1301
1388
|
* @type {Image}
|
|
1302
1389
|
* @memberof Draw */
|
|
1303
1390
|
const tileImage = new Image();
|
|
@@ -1322,7 +1409,7 @@ let overlayCanvas;
|
|
|
1322
1409
|
* @memberof Draw */
|
|
1323
1410
|
let overlayContext;
|
|
1324
1411
|
|
|
1325
|
-
/** The size of the main canvas (and other secondary canvases
|
|
1412
|
+
/** The size of the main canvas (and other secondary canvases)
|
|
1326
1413
|
* @type {Vector2}
|
|
1327
1414
|
* @memberof Draw */
|
|
1328
1415
|
let mainCanvasSize = vec2();
|
|
@@ -1341,42 +1428,39 @@ const screenToWorld = (screenPos)=>
|
|
|
1341
1428
|
const worldToScreen = (worldPos)=>
|
|
1342
1429
|
worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
|
|
1343
1430
|
|
|
1344
|
-
/** Draw textured tile centered
|
|
1345
|
-
* @param {Vector2} pos
|
|
1346
|
-
* @param {Vector2} [size=new Vector2(1,1)]
|
|
1347
|
-
* @param {Number} [tileIndex=-1]
|
|
1348
|
-
* @param {Vector2} [tileSize=
|
|
1349
|
-
* @param {Color} [color=new Color(1,1,1)]
|
|
1350
|
-
* @param {Number} [angle=0]
|
|
1351
|
-
* @param {Boolean} [mirror=0]
|
|
1352
|
-
* @param {Color} [additiveColor=new Color(0,0,0,0)]
|
|
1431
|
+
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
1432
|
+
* @param {Vector2} pos - Center of the tile in world space
|
|
1433
|
+
* @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space, width and height
|
|
1434
|
+
* @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
|
|
1435
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
|
|
1436
|
+
* @param {Color} [color=new Color(1,1,1)] - Color to modulate with
|
|
1437
|
+
* @param {Number} [angle=0] - Angle to rotate by
|
|
1438
|
+
* @param {Boolean} [mirror=0] - If true image is flipped along the Y axis
|
|
1439
|
+
* @param {Color} [additiveColor=new Color(0,0,0,0)] - Additive color to be applied
|
|
1440
|
+
* @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
1353
1441
|
* @memberof Draw */
|
|
1354
|
-
function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=
|
|
1355
|
-
additiveColor=new Color(0,0,0,0))
|
|
1442
|
+
function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle=0, mirror,
|
|
1443
|
+
additiveColor=new Color(0,0,0,0), useWebGL=glEnable)
|
|
1356
1444
|
{
|
|
1357
1445
|
showWatermark && ++drawCount;
|
|
1358
|
-
if (glEnable)
|
|
1446
|
+
if (glEnable && useWebGL)
|
|
1359
1447
|
{
|
|
1360
|
-
if (tileIndex < 0)
|
|
1448
|
+
if (tileIndex < 0 || !tileImage.width)
|
|
1361
1449
|
{
|
|
1362
|
-
// if negative tile index, force untextured
|
|
1450
|
+
// if negative tile index or image not found, force untextured
|
|
1363
1451
|
glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
|
|
1364
1452
|
}
|
|
1365
1453
|
else
|
|
1366
1454
|
{
|
|
1367
1455
|
// calculate uvs and render
|
|
1368
|
-
const cols =
|
|
1369
|
-
const uvSizeX = tileSize.x
|
|
1370
|
-
const uvSizeY = tileSize.y
|
|
1456
|
+
const cols = tileImageSize.x / tileSize.x |0;
|
|
1457
|
+
const uvSizeX = tileSize.x / tileImageSize.x;
|
|
1458
|
+
const uvSizeY = tileSize.y / tileImageSize.y;
|
|
1371
1459
|
const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
|
|
1372
|
-
|
|
1373
|
-
// shrink uvs to prevent bleeding
|
|
1374
|
-
const shrinkTilesX = tileBleedShrinkFix * tileImageSizeInverse.x;
|
|
1375
|
-
const shrinkTilesY = tileBleedShrinkFix * tileImageSizeInverse.y;
|
|
1376
1460
|
|
|
1377
1461
|
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
1378
|
-
uvX +
|
|
1379
|
-
uvX -
|
|
1462
|
+
uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
|
|
1463
|
+
uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
|
|
1380
1464
|
color.rgbaInt(), additiveColor.rgbaInt());
|
|
1381
1465
|
}
|
|
1382
1466
|
}
|
|
@@ -1394,11 +1478,11 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=defaultTileSize, col
|
|
|
1394
1478
|
else
|
|
1395
1479
|
{
|
|
1396
1480
|
// calculate uvs and render
|
|
1397
|
-
const cols =
|
|
1398
|
-
const sX = (tileIndex%cols)*tileSize.x +
|
|
1399
|
-
const sY = (tileIndex/cols|0)*tileSize.y +
|
|
1400
|
-
const sWidth = tileSize.x - 2*
|
|
1401
|
-
const sHeight = tileSize.y - 2*
|
|
1481
|
+
const cols = tileImageSize.x / tileSize.x |0;
|
|
1482
|
+
const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
|
|
1483
|
+
const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
|
|
1484
|
+
const sWidth = tileSize.x - 2*tileFixBleedScale;
|
|
1485
|
+
const sHeight = tileSize.y - 2*tileFixBleedScale;
|
|
1402
1486
|
context.globalAlpha = color.a; // only alpha is supported
|
|
1403
1487
|
context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
|
|
1404
1488
|
}
|
|
@@ -1406,41 +1490,44 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=defaultTileSize, col
|
|
|
1406
1490
|
}
|
|
1407
1491
|
}
|
|
1408
1492
|
|
|
1409
|
-
/** Draw colored
|
|
1493
|
+
/** Draw colored rect centered on pos
|
|
1410
1494
|
* @param {Vector2} pos
|
|
1411
1495
|
* @param {Vector2} [size=new Vector2(1,1)]
|
|
1412
1496
|
* @param {Color} [color=new Color(1,1,1)]
|
|
1413
1497
|
* @param {Number} [angle=0]
|
|
1498
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1414
1499
|
* @memberof Draw */
|
|
1415
|
-
function drawRect(pos, size, color, angle)
|
|
1500
|
+
function drawRect(pos, size, color, angle, useWebGL)
|
|
1416
1501
|
{
|
|
1417
|
-
drawTile(pos, size, -1,
|
|
1502
|
+
drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
|
|
1418
1503
|
}
|
|
1419
1504
|
|
|
1420
1505
|
/** Draw textured tile centered on pos in screen space
|
|
1421
|
-
* @param {Vector2} pos
|
|
1422
|
-
* @param {Vector2} [size=new Vector2(1,1)]
|
|
1423
|
-
* @param {Number} [tileIndex=-1]
|
|
1424
|
-
* @param {Vector2} [tileSize=
|
|
1506
|
+
* @param {Vector2} pos - Center of the tile
|
|
1507
|
+
* @param {Vector2} [size=new Vector2(1,1)] - Size of the tile
|
|
1508
|
+
* @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
|
|
1509
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
|
|
1425
1510
|
* @param {Color} [color=new Color]
|
|
1426
1511
|
* @param {Number} [angle=0]
|
|
1427
1512
|
* @param {Boolean} [mirror=0]
|
|
1428
1513
|
* @param {Color} [additiveColor=new Color(0,0,0,0)]
|
|
1514
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1429
1515
|
* @memberof Draw */
|
|
1430
|
-
function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor)
|
|
1516
|
+
function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL)
|
|
1431
1517
|
{
|
|
1432
|
-
drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor);
|
|
1518
|
+
drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL);
|
|
1433
1519
|
}
|
|
1434
1520
|
|
|
1435
|
-
/** Draw colored
|
|
1521
|
+
/** Draw colored rectangle in screen space
|
|
1436
1522
|
* @param {Vector2} pos
|
|
1437
1523
|
* @param {Vector2} [size=new Vector2(1,1)]
|
|
1438
1524
|
* @param {Color} [color=new Color(1,1,1)]
|
|
1439
1525
|
* @param {Number} [angle=0]
|
|
1526
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1440
1527
|
* @memberof Draw */
|
|
1441
|
-
function drawRectScreenSpace(pos, size, color, angle)
|
|
1528
|
+
function drawRectScreenSpace(pos, size, color, angle, useWebGL)
|
|
1442
1529
|
{
|
|
1443
|
-
|
|
1530
|
+
drawTileSrceenSpace(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
|
|
1444
1531
|
}
|
|
1445
1532
|
|
|
1446
1533
|
/** Draw colored line between two points
|
|
@@ -1448,15 +1535,16 @@ function drawRectScreenSpace(pos, size, color, angle)
|
|
|
1448
1535
|
* @param {Vector2} posB
|
|
1449
1536
|
* @param {Number} [thickness=.1]
|
|
1450
1537
|
* @param {Color} [color=new Color(1,1,1)]
|
|
1538
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1451
1539
|
* @memberof Draw */
|
|
1452
|
-
function drawLine(posA, posB, thickness=.1, color)
|
|
1540
|
+
function drawLine(posA, posB, thickness=.1, color, useWebGL)
|
|
1453
1541
|
{
|
|
1454
1542
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
1455
1543
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
1456
|
-
drawRect(posA.add(halfDelta), size, color, halfDelta.angle());
|
|
1544
|
+
drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), 0, 0, useWebGL);
|
|
1457
1545
|
}
|
|
1458
1546
|
|
|
1459
|
-
/** Draw directly to a 2d canvas context in world space
|
|
1547
|
+
/** Draw directly to a 2d canvas context in world space
|
|
1460
1548
|
* @param {Vector2} pos
|
|
1461
1549
|
* @param {Vector2} size
|
|
1462
1550
|
* @param {Number} angle
|
|
@@ -1472,7 +1560,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
|
|
|
1472
1560
|
context.save();
|
|
1473
1561
|
context.translate(pos.x+.5|0, pos.y-.5|0);
|
|
1474
1562
|
context.rotate(angle);
|
|
1475
|
-
context.scale(mirror
|
|
1563
|
+
context.scale(mirror ? -size.x : size.x, size.y);
|
|
1476
1564
|
drawFunction(context);
|
|
1477
1565
|
context.restore();
|
|
1478
1566
|
}
|
|
@@ -1486,7 +1574,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
|
|
|
1486
1574
|
* @param {Color} [lineColor=new Color(0,0,0)]
|
|
1487
1575
|
* @param {String} [textAlign='center']
|
|
1488
1576
|
* @memberof Draw */
|
|
1489
|
-
function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=
|
|
1577
|
+
function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault)
|
|
1490
1578
|
{
|
|
1491
1579
|
pos = worldToScreen(pos);
|
|
1492
1580
|
overlayContext.font = size*cameraScale + 'px '+ font;
|
|
@@ -1502,12 +1590,13 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
|
|
|
1502
1590
|
overlayContext.fillText(text, pos.x, pos.y);
|
|
1503
1591
|
}
|
|
1504
1592
|
|
|
1505
|
-
/** Enable
|
|
1593
|
+
/** Enable normal or additive blend mode
|
|
1506
1594
|
* @param {Boolean} [additive=0]
|
|
1595
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1507
1596
|
* @memberof Draw */
|
|
1508
|
-
function setBlendMode(additive)
|
|
1597
|
+
function setBlendMode(additive, useWebGL=glEnable)
|
|
1509
1598
|
{
|
|
1510
|
-
if (glEnable)
|
|
1599
|
+
if (glEnable && useWebGL)
|
|
1511
1600
|
glSetBlendMode(additive);
|
|
1512
1601
|
else
|
|
1513
1602
|
mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
@@ -1541,11 +1630,12 @@ function toggleFullscreen()
|
|
|
1541
1630
|
}
|
|
1542
1631
|
}
|
|
1543
1632
|
/**
|
|
1544
|
-
*
|
|
1545
|
-
*
|
|
1546
|
-
*
|
|
1547
|
-
*
|
|
1548
|
-
*
|
|
1633
|
+
* LittleJS Input System
|
|
1634
|
+
* <br> - Tracks key down, pressed, and released
|
|
1635
|
+
* <br> - Also tracks mouse buttons, position, and wheel
|
|
1636
|
+
* <br> - Supports multiple gamepads
|
|
1637
|
+
* <br> - Virtual gamepad for touch devices with touchGamepadSize
|
|
1638
|
+
* @namespace Input
|
|
1549
1639
|
*/
|
|
1550
1640
|
|
|
1551
1641
|
'use strict';
|
|
@@ -1573,7 +1663,7 @@ const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][
|
|
|
1573
1663
|
|
|
1574
1664
|
/** Clears all input
|
|
1575
1665
|
* @memberof Input */
|
|
1576
|
-
const clearInput = ()=> inputData
|
|
1666
|
+
const clearInput = ()=> inputData = [[]];
|
|
1577
1667
|
|
|
1578
1668
|
/** Returns true if mouse button is down
|
|
1579
1669
|
* @param {Number} button
|
|
@@ -1609,7 +1699,7 @@ let mouseWheel = 0;
|
|
|
1609
1699
|
|
|
1610
1700
|
/** Returns true if user is using gamepad (has more recently pressed a gamepad button)
|
|
1611
1701
|
* @memberof Input */
|
|
1612
|
-
let
|
|
1702
|
+
let isUsingGamepad = 0;
|
|
1613
1703
|
|
|
1614
1704
|
/** Returns true if gamepad button is down
|
|
1615
1705
|
* @param {Number} button
|
|
@@ -1642,7 +1732,9 @@ const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepa
|
|
|
1642
1732
|
///////////////////////////////////////////////////////////////////////////////
|
|
1643
1733
|
// Input update called by engine
|
|
1644
1734
|
|
|
1645
|
-
|
|
1735
|
+
// store input as a bit field for each key: 1 = isDown, 2 = wasPressed, 4 = wasReleased
|
|
1736
|
+
// mouse and keyboard are stored together in device 0, gamepads are in devices > 0
|
|
1737
|
+
let inputData = [[]];
|
|
1646
1738
|
|
|
1647
1739
|
function inputUpdate()
|
|
1648
1740
|
{
|
|
@@ -1668,34 +1760,38 @@ function inputUpdatePost()
|
|
|
1668
1760
|
///////////////////////////////////////////////////////////////////////////////
|
|
1669
1761
|
// Keyboard event handlers
|
|
1670
1762
|
|
|
1671
|
-
onkeydown = e=>
|
|
1763
|
+
onkeydown = (e)=>
|
|
1672
1764
|
{
|
|
1673
1765
|
if (debug && e.target != document.body) return;
|
|
1674
|
-
e.repeat || (inputData[
|
|
1766
|
+
e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
|
|
1675
1767
|
debug || e.preventDefault();
|
|
1676
1768
|
}
|
|
1677
|
-
onkeyup = e=>
|
|
1769
|
+
onkeyup = (e)=>
|
|
1678
1770
|
{
|
|
1679
1771
|
if (debug && e.target != document.body) return;
|
|
1680
1772
|
inputData[0][remapKeyCode(e.keyCode)] = 4;
|
|
1681
1773
|
}
|
|
1682
|
-
const remapKeyCode = c=>
|
|
1774
|
+
const remapKeyCode = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
1683
1775
|
|
|
1684
1776
|
///////////////////////////////////////////////////////////////////////////////
|
|
1685
1777
|
// Mouse event handlers
|
|
1686
1778
|
|
|
1687
|
-
onmousedown = e=> {inputData[
|
|
1688
|
-
onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
1689
|
-
onmousemove = e=>
|
|
1779
|
+
onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
1780
|
+
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
1781
|
+
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
1782
|
+
onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
1783
|
+
oncontextmenu = (e)=> !1; // prevent right click menu
|
|
1784
|
+
|
|
1785
|
+
// convert a mouse or touch event position to screen space
|
|
1786
|
+
const mouseToScreen = (mousePos)=>
|
|
1690
1787
|
{
|
|
1691
|
-
|
|
1692
|
-
|
|
1788
|
+
if (!mainCanvas)
|
|
1789
|
+
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
1790
|
+
|
|
1693
1791
|
const rect = mainCanvas.getBoundingClientRect();
|
|
1694
|
-
|
|
1695
|
-
|
|
1792
|
+
return mainCanvasSize.multiply(
|
|
1793
|
+
vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
|
|
1696
1794
|
}
|
|
1697
|
-
onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
1698
|
-
oncontextmenu = e=> !1; // prevent right click menu
|
|
1699
1795
|
|
|
1700
1796
|
///////////////////////////////////////////////////////////////////////////////
|
|
1701
1797
|
// Gamepad input
|
|
@@ -1703,6 +1799,21 @@ oncontextmenu = e=> !1; // prevent right click menu
|
|
|
1703
1799
|
const stickData = [];
|
|
1704
1800
|
function gamepadsUpdate()
|
|
1705
1801
|
{
|
|
1802
|
+
if (touchGamepadEnable && touchGamepadTimer.isSet())
|
|
1803
|
+
{
|
|
1804
|
+
// read virtual analog stick
|
|
1805
|
+
const sticks = stickData[0] || (stickData[0] = []);
|
|
1806
|
+
sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
|
|
1807
|
+
|
|
1808
|
+
// read virtual gamepad buttons
|
|
1809
|
+
const data = inputData[1] || (inputData[1] = []);
|
|
1810
|
+
for (let i=10; i--;)
|
|
1811
|
+
{
|
|
1812
|
+
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
1813
|
+
data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1706
1817
|
if (!gamepadsEnable || !navigator.getGamepads || !document.hasFocus() && !debug)
|
|
1707
1818
|
return;
|
|
1708
1819
|
|
|
@@ -1720,8 +1831,8 @@ function gamepadsUpdate()
|
|
|
1720
1831
|
// read clamp dead zone of analog sticks
|
|
1721
1832
|
const deadZone = .3, deadZoneMax = .8;
|
|
1722
1833
|
const applyDeadZone = (v)=>
|
|
1723
|
-
v > deadZone ? percent( v,
|
|
1724
|
-
v < -deadZone ? -percent(-v,
|
|
1834
|
+
v > deadZone ? percent( v, deadZone, deadZoneMax) :
|
|
1835
|
+
v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
|
|
1725
1836
|
|
|
1726
1837
|
// read analog sticks
|
|
1727
1838
|
for (let j = 0; j < gamepad.axes.length-1; j+=2)
|
|
@@ -1732,10 +1843,11 @@ function gamepadsUpdate()
|
|
|
1732
1843
|
{
|
|
1733
1844
|
const button = gamepad.buttons[j];
|
|
1734
1845
|
data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
|
|
1735
|
-
|
|
1846
|
+
isUsingGamepad |= !i && button.pressed;
|
|
1847
|
+
touchGamepadEnable && touchGamepadTimer.unset(); // disable touch gamepad if using real gamepad
|
|
1736
1848
|
}
|
|
1737
|
-
|
|
1738
|
-
if (
|
|
1849
|
+
|
|
1850
|
+
if (gamepadDirectionEmulateStick)
|
|
1739
1851
|
{
|
|
1740
1852
|
// copy dpad to left analog stick when pressed
|
|
1741
1853
|
const dpad = vec2(gamepadIsDown(15,i) - gamepadIsDown(14,i), gamepadIsDown(12,i) - gamepadIsDown(13,i));
|
|
@@ -1746,18 +1858,31 @@ function gamepadsUpdate()
|
|
|
1746
1858
|
}
|
|
1747
1859
|
}
|
|
1748
1860
|
|
|
1861
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1862
|
+
|
|
1863
|
+
/** Pulse the vibration hardware if it exists
|
|
1864
|
+
* @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
|
|
1865
|
+
* @memberof Input */
|
|
1866
|
+
const vibrate = (pattern)=> vibrateEnable && Navigator.vibrate && Navigator.vibrate(pattern);
|
|
1867
|
+
|
|
1868
|
+
/** Cancel any ongoing vibration
|
|
1869
|
+
* @memberof Input */
|
|
1870
|
+
const vibrateStop = ()=> vibrate(0);
|
|
1871
|
+
|
|
1749
1872
|
///////////////////////////////////////////////////////////////////////////////
|
|
1750
1873
|
// Touch input
|
|
1751
1874
|
|
|
1752
1875
|
/** True if a touch device has been detected
|
|
1753
1876
|
* @const {boolean}
|
|
1754
1877
|
* @memberof Input */
|
|
1755
|
-
const isTouchDevice =
|
|
1878
|
+
const isTouchDevice = window.ontouchstart !== undefined;
|
|
1879
|
+
|
|
1880
|
+
// try to enable touch mouse
|
|
1756
1881
|
if (isTouchDevice)
|
|
1757
1882
|
{
|
|
1758
1883
|
// handle all touch events the same way
|
|
1759
1884
|
let wasTouching, hadTouchInput;
|
|
1760
|
-
ontouchstart = ontouchmove = ontouchend = e=>
|
|
1885
|
+
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
1761
1886
|
{
|
|
1762
1887
|
e.button = 0; // all touches are left click
|
|
1763
1888
|
|
|
@@ -1782,31 +1907,158 @@ if (isTouchDevice)
|
|
|
1782
1907
|
return !e.cancelable;
|
|
1783
1908
|
}
|
|
1784
1909
|
}
|
|
1910
|
+
|
|
1911
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1912
|
+
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
1913
|
+
|
|
1914
|
+
// touch input internal variables
|
|
1915
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2(), touchGamepadAnalog = 1;
|
|
1916
|
+
|
|
1917
|
+
// create the touch gamepad, called automatically by the engine
|
|
1918
|
+
function touchGamepadCreate()
|
|
1919
|
+
{
|
|
1920
|
+
if (!touchGamepadEnable || !isTouchDevice)
|
|
1921
|
+
return;
|
|
1922
|
+
|
|
1923
|
+
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
1924
|
+
{
|
|
1925
|
+
if (!touchGamepadEnable)
|
|
1926
|
+
return;
|
|
1927
|
+
|
|
1928
|
+
// clear touch gamepad input
|
|
1929
|
+
touchGamepadStick = vec2();
|
|
1930
|
+
touchGamepadButtons = [];
|
|
1931
|
+
|
|
1932
|
+
const touching = e.touches.length;
|
|
1933
|
+
if (touching)
|
|
1934
|
+
{
|
|
1935
|
+
touchGamepadTimer.isSet() || zzfx(0) ; // fix mobile audio, force it to play a sound the first time
|
|
1936
|
+
|
|
1937
|
+
// set that gamepad is active
|
|
1938
|
+
isUsingGamepad = 1;
|
|
1939
|
+
touchGamepadTimer.set();
|
|
1940
|
+
|
|
1941
|
+
if (paused)
|
|
1942
|
+
{
|
|
1943
|
+
// touch anywhere to press start when paused
|
|
1944
|
+
touchGamepadButtons[9] = 1;
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
// get center of left and right sides
|
|
1950
|
+
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
1951
|
+
const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
|
|
1952
|
+
const startCenter = mainCanvasSize.scale(.5);
|
|
1953
|
+
|
|
1954
|
+
// check each touch point
|
|
1955
|
+
for (const touch of e.touches)
|
|
1956
|
+
{
|
|
1957
|
+
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
1958
|
+
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
1959
|
+
{
|
|
1960
|
+
// virtual analog stick
|
|
1961
|
+
if (touchGamepadAnalog)
|
|
1962
|
+
touchGamepadStick = touchPos.subtract(stickCenter).clampLength(touchGamepadSize/2).scale(2/touchGamepadSize);
|
|
1963
|
+
else
|
|
1964
|
+
{
|
|
1965
|
+
// 8 way dpad
|
|
1966
|
+
const angle = touchPos.subtract(stickCenter).angle();
|
|
1967
|
+
touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
1971
|
+
{
|
|
1972
|
+
// virtual face buttons
|
|
1973
|
+
const button = touchPos.subtract(buttonCenter).direction();
|
|
1974
|
+
touchGamepadButtons[button] = 1;
|
|
1975
|
+
}
|
|
1976
|
+
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
1977
|
+
{
|
|
1978
|
+
// virtual start button in center
|
|
1979
|
+
touchGamepadButtons[9] = 1;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
// render the touch gamepad, called automatically by the engine
|
|
1986
|
+
function touchGamepadRender()
|
|
1987
|
+
{
|
|
1988
|
+
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
1989
|
+
return;
|
|
1990
|
+
|
|
1991
|
+
// fade off when not touching or paused
|
|
1992
|
+
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
1993
|
+
if (!alpha || paused)
|
|
1994
|
+
return;
|
|
1995
|
+
|
|
1996
|
+
// setup the canvas
|
|
1997
|
+
overlayContext.save();
|
|
1998
|
+
overlayContext.globalAlpha = alpha*touchGamepadAlpha;
|
|
1999
|
+
overlayContext.strokeStyle = '#fff';
|
|
2000
|
+
overlayContext.lineWidth = 3;
|
|
2001
|
+
|
|
2002
|
+
// draw left analog stick
|
|
2003
|
+
overlayContext.fillStyle = gamepadStick(0).length() > 0 ? '#fff' : '#000';
|
|
2004
|
+
overlayContext.beginPath();
|
|
2005
|
+
overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize/2, 0,9);
|
|
2006
|
+
overlayContext.fill();
|
|
2007
|
+
overlayContext.stroke();
|
|
2008
|
+
|
|
2009
|
+
// draw right face buttons
|
|
2010
|
+
const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2011
|
+
for (let i=4; i--;)
|
|
2012
|
+
{
|
|
2013
|
+
const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize/2));
|
|
2014
|
+
overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
2015
|
+
overlayContext.beginPath();
|
|
2016
|
+
overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
2017
|
+
overlayContext.fill();
|
|
2018
|
+
overlayContext.stroke();
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// set canvas back to normal
|
|
2022
|
+
overlayContext.restore();
|
|
2023
|
+
}
|
|
1785
2024
|
/**
|
|
1786
|
-
*
|
|
1787
|
-
*
|
|
1788
|
-
*
|
|
1789
|
-
*
|
|
1790
|
-
*
|
|
1791
|
-
*
|
|
1792
|
-
*
|
|
2025
|
+
* LittleJS Audio System
|
|
2026
|
+
* <br> - <a href=https://killedbyapixel.github.io/ZzFX/>ZzFX Sound Effects</a>
|
|
2027
|
+
* <br> - <a href=https://keithclark.github.io/ZzFXM/>ZzFXM Music</a>
|
|
2028
|
+
* <br> - Caches sounds and music for fast playback
|
|
2029
|
+
* <br> - Can attenuate and apply stereo panning to sounds
|
|
2030
|
+
* <br> - Ability to play mp3, ogg, and wave files
|
|
2031
|
+
* <br> - Speech synthesis wrapper functions
|
|
1793
2032
|
*/
|
|
1794
2033
|
|
|
1795
2034
|
'use strict';
|
|
1796
2035
|
|
|
1797
|
-
/**
|
|
2036
|
+
/**
|
|
2037
|
+
* Sound Object - Stores a zzfx sound for later use and can be played positionally
|
|
2038
|
+
* <br>
|
|
2039
|
+
* <br><b><a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a></b>
|
|
2040
|
+
* @example
|
|
2041
|
+
* // create a sound
|
|
2042
|
+
* const sound_example = new Sound([.5,.5]);
|
|
2043
|
+
*
|
|
2044
|
+
* // play the sound
|
|
2045
|
+
* sound_example.play();
|
|
2046
|
+
*/
|
|
1798
2047
|
class Sound
|
|
1799
2048
|
{
|
|
1800
2049
|
/** Create a sound object and cache the zzfx samples for later use
|
|
1801
2050
|
* @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
|
|
1802
|
-
* @param {Number} [range=
|
|
1803
|
-
* @param {Number} [taper=
|
|
2051
|
+
* @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
|
|
2052
|
+
* @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
|
|
1804
2053
|
*/
|
|
1805
|
-
constructor(zzfxSound, range=
|
|
2054
|
+
constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
|
|
1806
2055
|
{
|
|
1807
2056
|
if (!soundEnable) return;
|
|
1808
2057
|
|
|
2058
|
+
/** @property {Number} - World space max range of sound, will not play if camera is farther away */
|
|
1809
2059
|
this.range = range;
|
|
2060
|
+
|
|
2061
|
+
/** @property {Number} - At what percentage of range should it start tapering off */
|
|
1810
2062
|
this.taper = taper;
|
|
1811
2063
|
|
|
1812
2064
|
// get randomness from sound parameters
|
|
@@ -1840,7 +2092,7 @@ class Sound
|
|
|
1840
2092
|
return; // out of range
|
|
1841
2093
|
|
|
1842
2094
|
// attenuate volume by distance
|
|
1843
|
-
volume *= percent(lengthSquared**.5, range*this.taper
|
|
2095
|
+
volume *= percent(lengthSquared**.5, range, range*this.taper);
|
|
1844
2096
|
}
|
|
1845
2097
|
|
|
1846
2098
|
// get pan from screen space coords
|
|
@@ -1866,7 +2118,36 @@ class Sound
|
|
|
1866
2118
|
}
|
|
1867
2119
|
}
|
|
1868
2120
|
|
|
1869
|
-
/**
|
|
2121
|
+
/**
|
|
2122
|
+
* Music Object - Stores a zzfx music track for later use
|
|
2123
|
+
* <br>
|
|
2124
|
+
* <br><b><a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a></b>
|
|
2125
|
+
* @example
|
|
2126
|
+
* // create some music
|
|
2127
|
+
* const music_example = new Music(
|
|
2128
|
+
* [
|
|
2129
|
+
* [ // instruments
|
|
2130
|
+
* [,0,400] // simple note
|
|
2131
|
+
* ],
|
|
2132
|
+
* [ // patterns
|
|
2133
|
+
* [ // pattern 1
|
|
2134
|
+
* [ // channel 0
|
|
2135
|
+
* 0, -1, // instrument 0, left speaker
|
|
2136
|
+
* 1, 0, 9, 1 // channel notes
|
|
2137
|
+
* ],
|
|
2138
|
+
* [ // channel 1
|
|
2139
|
+
* 0, 1, // instrument 1, right speaker
|
|
2140
|
+
* 0, 12, 17, -1 // channel notes
|
|
2141
|
+
* ]
|
|
2142
|
+
* ],
|
|
2143
|
+
* ],
|
|
2144
|
+
* [0, 0, 0, 0], // sequence, play pattern 0 four times
|
|
2145
|
+
* 90 // BPM
|
|
2146
|
+
* ]);
|
|
2147
|
+
*
|
|
2148
|
+
* // play the music
|
|
2149
|
+
* music_example.play();
|
|
2150
|
+
*/
|
|
1870
2151
|
class Music
|
|
1871
2152
|
{
|
|
1872
2153
|
/** Create a music object and cache the zzfx music samples for later use
|
|
@@ -1903,7 +2184,7 @@ function playAudioFile(url, volume=1, loop=1)
|
|
|
1903
2184
|
if (!soundEnable) return;
|
|
1904
2185
|
|
|
1905
2186
|
const audio = new Audio(url);
|
|
1906
|
-
audio.volume =
|
|
2187
|
+
audio.volume = soundVolume * volume;
|
|
1907
2188
|
audio.loop = loop;
|
|
1908
2189
|
audio.play();
|
|
1909
2190
|
return audio;
|
|
@@ -1928,7 +2209,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
1928
2209
|
// build utterance and speak
|
|
1929
2210
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
1930
2211
|
utterance.lang = language;
|
|
1931
|
-
utterance.volume = 2*volume*
|
|
2212
|
+
utterance.volume = 2*volume*soundVolume;
|
|
1932
2213
|
utterance.rate = rate;
|
|
1933
2214
|
utterance.pitch = pitch;
|
|
1934
2215
|
speechSynthesis.speak(utterance);
|
|
@@ -1937,7 +2218,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
1937
2218
|
|
|
1938
2219
|
/** Stop all queued speech
|
|
1939
2220
|
* @memberof Audio */
|
|
1940
|
-
const
|
|
2221
|
+
const speakStop = ()=> speechSynthesis && speechSynthesis.cancel();
|
|
1941
2222
|
|
|
1942
2223
|
/** Get frequency of a note on a musical scale
|
|
1943
2224
|
* @param {Number} semitoneOffset - How many semitones away from the root note
|
|
@@ -1968,6 +2249,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
1968
2249
|
if (!audioContext)
|
|
1969
2250
|
audioContext = new (window.AudioContext||webkitAudioContext);
|
|
1970
2251
|
|
|
2252
|
+
// fix stalled audio
|
|
2253
|
+
audioContext.resume();
|
|
2254
|
+
|
|
2255
|
+
// prevent sounds from building up if they can't be played
|
|
2256
|
+
if (audioContext.state != 'running')
|
|
2257
|
+
return;
|
|
2258
|
+
|
|
1971
2259
|
// create buffer and source
|
|
1972
2260
|
const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, zzfxR),
|
|
1973
2261
|
source = audioContext.createBufferSource();
|
|
@@ -1978,11 +2266,18 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
1978
2266
|
source.playbackRate.value = rate;
|
|
1979
2267
|
source.loop = loop;
|
|
1980
2268
|
|
|
1981
|
-
// create
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
2269
|
+
// create and connect gain node (createGain is more widley spported then GainNode construtor)
|
|
2270
|
+
const gainNode = audioContext.createGain();
|
|
2271
|
+
gainNode.gain.value = soundVolume*volume;
|
|
2272
|
+
gainNode.connect(audioContext.destination);
|
|
2273
|
+
|
|
2274
|
+
// connect source to gain
|
|
2275
|
+
(
|
|
2276
|
+
window.StereoPannerNode ? // create pan node if possible
|
|
2277
|
+
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
|
|
2278
|
+
: source
|
|
2279
|
+
)
|
|
2280
|
+
.connect(gainNode);
|
|
1986
2281
|
|
|
1987
2282
|
// play and return sound
|
|
1988
2283
|
source.start();
|
|
@@ -1993,6 +2288,8 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
1993
2288
|
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.1.8 by Frank Force
|
|
1994
2289
|
|
|
1995
2290
|
/** Generate and play a ZzFX sound
|
|
2291
|
+
* <br>
|
|
2292
|
+
* <br><b><a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a></b>
|
|
1996
2293
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
1997
2294
|
* @return {Array} - Array of audio samples
|
|
1998
2295
|
* @memberof Audio */
|
|
@@ -2049,7 +2346,7 @@ function zzfxG
|
|
|
2049
2346
|
1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
|
|
2050
2347
|
: 1) *
|
|
2051
2348
|
sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
|
|
2052
|
-
volume *
|
|
2349
|
+
volume * soundVolume * ( // envelope
|
|
2053
2350
|
i < attack ? i/attack : // attack
|
|
2054
2351
|
i < attack + decay ? // decay
|
|
2055
2352
|
1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
|
|
@@ -2195,27 +2492,31 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
2195
2492
|
return [leftChannelBuffer, rightChannelBuffer];
|
|
2196
2493
|
}
|
|
2197
2494
|
/**
|
|
2198
|
-
*
|
|
2199
|
-
*
|
|
2200
|
-
*
|
|
2201
|
-
*
|
|
2202
|
-
*
|
|
2203
|
-
*
|
|
2204
|
-
*
|
|
2205
|
-
*
|
|
2495
|
+
* LittleJS Tile Layer System
|
|
2496
|
+
* <br> - Caches arrays of tiles to off screen canvas for fast rendering
|
|
2497
|
+
* <br> - Unlimted numbers of layers, allocates canvases as needed
|
|
2498
|
+
* <br> - Interfaces with EngineObject for collision
|
|
2499
|
+
* <br> - Collision layer is separate from visible layers
|
|
2500
|
+
* <br> - It is recommended to have a visible layer that matches the collision
|
|
2501
|
+
* <br> - Tile layers can be drawn to using their context with canvas2d
|
|
2502
|
+
* <br> - Drawn directly to the main canvas without using WebGL
|
|
2503
|
+
* @namespace TileCollision
|
|
2206
2504
|
*/
|
|
2207
2505
|
|
|
2208
2506
|
'use strict';
|
|
2209
2507
|
|
|
2210
|
-
|
|
2211
|
-
|
|
2508
|
+
/** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access
|
|
2509
|
+
* @memberof TileCollision */
|
|
2510
|
+
let tileCollision = [];
|
|
2212
2511
|
|
|
2213
|
-
|
|
2214
|
-
|
|
2512
|
+
/** Size of the tile collision layer
|
|
2513
|
+
* @type {Vector2}
|
|
2514
|
+
* @memberof TileCollision */
|
|
2515
|
+
let tileCollisionSize = vec2();
|
|
2215
2516
|
|
|
2216
2517
|
/** Clear and initialize tile collision
|
|
2217
2518
|
* @param {Vector2} size
|
|
2218
|
-
* @memberof
|
|
2519
|
+
* @memberof TileCollision */
|
|
2219
2520
|
function initTileCollision(size)
|
|
2220
2521
|
{
|
|
2221
2522
|
tileCollisionSize = size;
|
|
@@ -2227,14 +2528,14 @@ function initTileCollision(size)
|
|
|
2227
2528
|
/** Set tile collision data
|
|
2228
2529
|
* @param {Vector2} pos
|
|
2229
2530
|
* @param {Number} [data=0]
|
|
2230
|
-
* @memberof
|
|
2531
|
+
* @memberof TileCollision */
|
|
2231
2532
|
const setTileCollisionData = (pos, data=0)=>
|
|
2232
2533
|
pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
|
|
2233
2534
|
|
|
2234
2535
|
/** Get tile collision data
|
|
2235
2536
|
* @param {Vector2} pos
|
|
2236
2537
|
* @return {Number}
|
|
2237
|
-
* @memberof
|
|
2538
|
+
* @memberof TileCollision */
|
|
2238
2539
|
const getTileCollisionData = (pos)=>
|
|
2239
2540
|
pos.arrayCheck(tileCollisionSize) ? tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] : 0;
|
|
2240
2541
|
|
|
@@ -2243,13 +2544,13 @@ const getTileCollisionData = (pos)=>
|
|
|
2243
2544
|
* @param {Vector2} [size=new Vector2(1,1)]
|
|
2244
2545
|
* @param {EngineObject} [object]
|
|
2245
2546
|
* @return {Boolean}
|
|
2246
|
-
* @memberof
|
|
2547
|
+
* @memberof TileCollision */
|
|
2247
2548
|
function tileCollisionTest(pos, size=vec2(), object)
|
|
2248
2549
|
{
|
|
2249
|
-
const minX = max(
|
|
2250
|
-
const minY = max(
|
|
2251
|
-
const maxX = min(pos.x + size.x/2, tileCollisionSize.x
|
|
2252
|
-
const maxY = min(pos.y + size.y/2, tileCollisionSize.y
|
|
2550
|
+
const minX = max(pos.x - size.x/2|0, 0);
|
|
2551
|
+
const minY = max(pos.y - size.y/2|0, 0);
|
|
2552
|
+
const maxX = min(pos.x + size.x/2, tileCollisionSize.x);
|
|
2553
|
+
const maxY = min(pos.y + size.y/2, tileCollisionSize.y);
|
|
2253
2554
|
for (let y = minY; y < maxY; ++y)
|
|
2254
2555
|
for (let x = minX; x < maxX; ++x)
|
|
2255
2556
|
{
|
|
@@ -2264,7 +2565,7 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
2264
2565
|
* @param {Vector2} posEnd
|
|
2265
2566
|
* @param {EngineObject} [object]
|
|
2266
2567
|
* @return {Vector2}
|
|
2267
|
-
* @memberof
|
|
2568
|
+
* @memberof TileCollision */
|
|
2268
2569
|
function tileCollisionRaycast(posStart, posEnd, object)
|
|
2269
2570
|
{
|
|
2270
2571
|
// test if a ray collides with tiles from start to end
|
|
@@ -2298,23 +2599,32 @@ function tileCollisionRaycast(posStart, posEnd, object)
|
|
|
2298
2599
|
///////////////////////////////////////////////////////////////////////////////
|
|
2299
2600
|
// Tile Layer Rendering System
|
|
2300
2601
|
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2602
|
+
/**
|
|
2603
|
+
* Tile layer data object stores info about how to render a tile
|
|
2604
|
+
* @example
|
|
2605
|
+
* // create tile layer data with tile index 0 and random orientation and color
|
|
2606
|
+
* const tileIndex = 0;
|
|
2607
|
+
* const direction = randInt(4)
|
|
2608
|
+
* const mirror = randInt(2);
|
|
2609
|
+
* const color = randColor();
|
|
2610
|
+
* const data = new TileLayerData(tileIndex, direction, mirror, color);
|
|
2611
|
+
*/
|
|
2305
2612
|
class TileLayerData
|
|
2306
2613
|
{
|
|
2307
|
-
/** Create a tile layer data object
|
|
2308
|
-
* @param {Number} [tile]
|
|
2309
|
-
* @param {Number} [direction=0]
|
|
2310
|
-
* @param {Boolean} [mirror=0]
|
|
2311
|
-
* @param {Color} [color=new Color(1,1,1)] - Color of the tile
|
|
2312
|
-
*/
|
|
2614
|
+
/** Create a tile layer data object, one for each tile in a TileLayer
|
|
2615
|
+
* @param {Number} [tile] - The tile to use, untextured if undefined
|
|
2616
|
+
* @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments
|
|
2617
|
+
* @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis
|
|
2618
|
+
* @param {Color} [color=new Color(1,1,1)] - Color of the tile */
|
|
2313
2619
|
constructor(tile, direction=0, mirror=0, color=new Color)
|
|
2314
2620
|
{
|
|
2621
|
+
/** @property {Number} - The tile to use, untextured if undefined */
|
|
2315
2622
|
this.tile = tile;
|
|
2623
|
+
/** @property {Number} - Integer direction of tile, in 90 degree increments */
|
|
2316
2624
|
this.direction = direction;
|
|
2625
|
+
/** @property {Boolean} - If the tile should be mirrored along the x axis */
|
|
2317
2626
|
this.mirror = mirror;
|
|
2627
|
+
/** @property {Color} - Color of the tile */
|
|
2318
2628
|
this.color = color;
|
|
2319
2629
|
}
|
|
2320
2630
|
|
|
@@ -2322,44 +2632,49 @@ class TileLayerData
|
|
|
2322
2632
|
clear() { this.tile = this.direction = this.mirror = 0; color = new Color; }
|
|
2323
2633
|
}
|
|
2324
2634
|
|
|
2325
|
-
/**
|
|
2635
|
+
/**
|
|
2636
|
+
* Tile layer object - cached rendering system for tile layers
|
|
2637
|
+
* <br> - Each Tile layer is rendered to an off screen canvas
|
|
2638
|
+
* <br> - To allow dynamic modifications, layers are rendered using canvas 2d
|
|
2639
|
+
* <br> - Some devices like mobile phones are limited to 4k texture resolution
|
|
2640
|
+
* <br> - So with 16x16 tiles this limits layers to 256x256 on mobile devices
|
|
2641
|
+
* @extends EngineObject
|
|
2642
|
+
* @example
|
|
2643
|
+
* // create tile collision and visible tile layer
|
|
2644
|
+
* initTileCollision(vec2(200,100));
|
|
2645
|
+
* const tileLayer = new TileLayer();
|
|
2646
|
+
*/
|
|
2326
2647
|
class TileLayer extends EngineObject
|
|
2327
2648
|
{
|
|
2328
|
-
/** Create a tile layer
|
|
2649
|
+
/** Create a tile layer object
|
|
2329
2650
|
* @param {Vector2} [position=new Vector2(0,0)] - World space position
|
|
2330
|
-
* @param {Vector2} [size=
|
|
2331
|
-
* @param {Vector2} [
|
|
2332
|
-
* @param {
|
|
2651
|
+
* @param {Vector2} [size=objectDefaultSize] - World space size
|
|
2652
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
|
|
2653
|
+
* @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
|
|
2654
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
2333
2655
|
*/
|
|
2334
|
-
constructor(pos, size, scale=vec2(1), renderOrder=0)
|
|
2656
|
+
constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
|
|
2335
2657
|
{
|
|
2336
|
-
super(pos, size);
|
|
2658
|
+
super(pos, size, -1, tileSize, 0, undefined, renderOrder);
|
|
2337
2659
|
|
|
2338
|
-
|
|
2339
|
-
this.canvas =
|
|
2660
|
+
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
2661
|
+
this.canvas = document.createElement('canvas');
|
|
2662
|
+
/** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
2340
2663
|
this.context = this.canvas.getContext('2d');
|
|
2664
|
+
/** @property {Vector2} - How much to scale this layer when rendered */
|
|
2341
2665
|
this.scale = scale;
|
|
2342
|
-
|
|
2343
|
-
this.
|
|
2344
|
-
this.flushGLBeforeRender = 1;
|
|
2666
|
+
/** @property {Boolean} [isOverlay=0] - If true this layer will render to overlay canvas and appear above all objects */
|
|
2667
|
+
this.isOverlay;
|
|
2345
2668
|
|
|
2346
2669
|
// init tile data
|
|
2347
2670
|
this.data = [];
|
|
2348
2671
|
for (let j = this.size.area(); j--;)
|
|
2349
2672
|
this.data.push(new TileLayerData());
|
|
2350
2673
|
}
|
|
2351
|
-
|
|
2352
|
-
/** Destroy this tile layer */
|
|
2353
|
-
destroy()
|
|
2354
|
-
{
|
|
2355
|
-
// add canvas back to the cache
|
|
2356
|
-
tileLayerCanvasCache.push(this.canvas);
|
|
2357
|
-
super.destroy();
|
|
2358
|
-
}
|
|
2359
2674
|
|
|
2360
2675
|
/** Set data at a given position in the array
|
|
2361
|
-
* @param {Vector2} position
|
|
2362
|
-
* @param {TileLayerData} data
|
|
2676
|
+
* @param {Vector2} position - Local position in array
|
|
2677
|
+
* @param {TileLayerData} data - Data to set
|
|
2363
2678
|
* @param {Boolean} [redraw=0] - Force the tile to redraw if true */
|
|
2364
2679
|
setData(layerPos, data, redraw)
|
|
2365
2680
|
{
|
|
@@ -2384,50 +2699,48 @@ class TileLayer extends EngineObject
|
|
|
2384
2699
|
{
|
|
2385
2700
|
ASSERT(mainContext != this.context); // must call redrawEnd() after drawing tiles
|
|
2386
2701
|
|
|
2387
|
-
// flush and copy gl canvas because tile canvas does not use
|
|
2388
|
-
|
|
2702
|
+
// flush and copy gl canvas because tile canvas does not use webgl
|
|
2703
|
+
glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
2389
2704
|
|
|
2390
|
-
// draw the entire cached level onto the
|
|
2705
|
+
// draw the entire cached level onto the canvas
|
|
2391
2706
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
2392
|
-
mainContext.drawImage
|
|
2707
|
+
(this.isOverlay ? overlayContext : mainContext).drawImage
|
|
2393
2708
|
(
|
|
2394
2709
|
this.canvas, pos.x, pos.y,
|
|
2395
2710
|
cameraScale*this.size.x*this.scale.x, cameraScale*this.size.y*this.scale.y
|
|
2396
2711
|
);
|
|
2397
2712
|
}
|
|
2398
2713
|
|
|
2399
|
-
/** Draw all the tile data to an offscreen canvas
|
|
2714
|
+
/** Draw all the tile data to an offscreen canvas
|
|
2715
|
+
* - This may be slow in some browsers
|
|
2716
|
+
*/
|
|
2400
2717
|
redraw()
|
|
2401
2718
|
{
|
|
2402
|
-
this.redrawStart();
|
|
2719
|
+
this.redrawStart(1);
|
|
2403
2720
|
this.drawAllTileData();
|
|
2404
2721
|
this.redrawEnd();
|
|
2405
2722
|
}
|
|
2406
2723
|
|
|
2407
2724
|
/** Call to start the redraw process
|
|
2408
|
-
* @param {Boolean} [clear=
|
|
2409
|
-
redrawStart(clear =
|
|
2725
|
+
* @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
|
|
2726
|
+
redrawStart(clear = 0)
|
|
2410
2727
|
{
|
|
2411
|
-
// clear and set size
|
|
2412
|
-
const width = this.size.x * this.tileSize.x;
|
|
2413
|
-
const height = this.size.y * this.tileSize.y;
|
|
2414
2728
|
if (clear)
|
|
2415
2729
|
{
|
|
2416
|
-
|
|
2417
|
-
this.canvas.
|
|
2730
|
+
// clear and set size
|
|
2731
|
+
this.canvas.width = this.size.x * this.tileSize.x;
|
|
2732
|
+
this.canvas.height = this.size.y * this.tileSize.y;
|
|
2418
2733
|
}
|
|
2419
2734
|
|
|
2420
2735
|
// save current render settings
|
|
2421
|
-
this.savedRenderSettings = [
|
|
2736
|
+
this.savedRenderSettings = [mainCanvas, mainContext, cameraPos, cameraScale];
|
|
2422
2737
|
|
|
2423
|
-
//
|
|
2424
|
-
cameraScale = this.tileSize.x;
|
|
2425
|
-
cameraPos = this.size.scale(.5);
|
|
2738
|
+
// use normal rendering system to render the tiles
|
|
2426
2739
|
mainCanvas = this.canvas;
|
|
2427
2740
|
mainContext = this.context;
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2741
|
+
cameraPos = this.size.scale(.5);
|
|
2742
|
+
cameraScale = this.tileSize.x;
|
|
2743
|
+
enginePreRender();
|
|
2431
2744
|
}
|
|
2432
2745
|
|
|
2433
2746
|
/** Call to end the redraw process */
|
|
@@ -2438,7 +2751,7 @@ class TileLayer extends EngineObject
|
|
|
2438
2751
|
//debugSaveCanvas(this.canvas);
|
|
2439
2752
|
|
|
2440
2753
|
// set stuff back to normal
|
|
2441
|
-
[
|
|
2754
|
+
[mainCanvas, mainContext, cameraPos, cameraScale] = this.savedRenderSettings;
|
|
2442
2755
|
}
|
|
2443
2756
|
|
|
2444
2757
|
/** Draw the tile at a given position
|
|
@@ -2466,13 +2779,13 @@ class TileLayer extends EngineObject
|
|
|
2466
2779
|
this.drawTileData(vec2(x,y));
|
|
2467
2780
|
}
|
|
2468
2781
|
|
|
2469
|
-
/** Draw directly to the
|
|
2782
|
+
/** Draw directly to the 2D canvas in world space (bipass webgl)
|
|
2470
2783
|
* @param {Vector2} pos
|
|
2471
2784
|
* @param {Vector2} size
|
|
2472
|
-
* @param {Number} angle
|
|
2473
|
-
* @param {Boolean} mirror
|
|
2785
|
+
* @param {Number} [angle=0]
|
|
2786
|
+
* @param {Boolean} [mirror=0]
|
|
2474
2787
|
* @param {Function} drawFunction */
|
|
2475
|
-
drawCanvas2D(pos, size, angle, mirror, drawFunction)
|
|
2788
|
+
drawCanvas2D(pos, size, angle=0, mirror, drawFunction)
|
|
2476
2789
|
{
|
|
2477
2790
|
const context = this.context;
|
|
2478
2791
|
context.save();
|
|
@@ -2480,7 +2793,7 @@ class TileLayer extends EngineObject
|
|
|
2480
2793
|
size = size.multiply(this.tileSize);
|
|
2481
2794
|
context.translate(pos.x, this.canvas.height - pos.y);
|
|
2482
2795
|
context.rotate(angle);
|
|
2483
|
-
context.scale(mirror
|
|
2796
|
+
context.scale(mirror ? -size.x : size.x, size.y);
|
|
2484
2797
|
drawFunction(context);
|
|
2485
2798
|
context.restore();
|
|
2486
2799
|
}
|
|
@@ -2489,11 +2802,11 @@ class TileLayer extends EngineObject
|
|
|
2489
2802
|
* @param {Vector2} pos
|
|
2490
2803
|
* @param {Vector2} [size=new Vector2(1,1)]
|
|
2491
2804
|
* @param {Number} [tileIndex=-1]
|
|
2492
|
-
* @param {Vector2} [tileSize=
|
|
2805
|
+
* @param {Vector2} [tileSize=tileSizeDefault]
|
|
2493
2806
|
* @param {Color} [color=new Color(1,1,1)]
|
|
2494
2807
|
* @param {Number} [angle=0]
|
|
2495
2808
|
* @param {Boolean} [mirror=0] */
|
|
2496
|
-
drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=
|
|
2809
|
+
drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle, mirror)
|
|
2497
2810
|
{
|
|
2498
2811
|
this.drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
2499
2812
|
{
|
|
@@ -2506,7 +2819,7 @@ class TileLayer extends EngineObject
|
|
|
2506
2819
|
else
|
|
2507
2820
|
{
|
|
2508
2821
|
const cols = tileImage.width/tileSize.x;
|
|
2509
|
-
context.globalAlpha = color.a; //
|
|
2822
|
+
context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
|
|
2510
2823
|
context.drawImage(tileImage,
|
|
2511
2824
|
(tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.x,
|
|
2512
2825
|
tileSize.x, tileSize.y, -.5, -.5, 1, 1);
|
|
@@ -2519,7 +2832,7 @@ class TileLayer extends EngineObject
|
|
|
2519
2832
|
* @param {Vector2} [size=new Vector2(1,1)]
|
|
2520
2833
|
* @param {Color} [color=new Color(1,1,1)]
|
|
2521
2834
|
* @param {Number} [angle=0] */
|
|
2522
|
-
drawRect(pos, size, color, angle) { this.drawTile(pos, size, -1, 0, color, angle
|
|
2835
|
+
drawRect(pos, size, color, angle) { this.drawTile(pos, size, -1, 0, color, angle); }
|
|
2523
2836
|
}
|
|
2524
2837
|
/*
|
|
2525
2838
|
LittleJS Particle System
|
|
@@ -2532,37 +2845,50 @@ class TileLayer extends EngineObject
|
|
|
2532
2845
|
|
|
2533
2846
|
/**
|
|
2534
2847
|
* Particle Emitter - Spawns particles with the given settings
|
|
2848
|
+
* @extends EngineObject
|
|
2849
|
+
* @example
|
|
2850
|
+
* // create a particle emitter
|
|
2851
|
+
* let pos = vec2(2,3);
|
|
2852
|
+
* let particleEmiter = new ParticleEmitter
|
|
2853
|
+
* (
|
|
2854
|
+
* pos, 1, 0, 500, PI, // pos, emitSize, emitTime, emitRate, emiteCone
|
|
2855
|
+
* 0, vec2(16), // tileIndex, tileSize
|
|
2856
|
+
* new Color, new Color(0,0,0), // colorStartA, colorStartB
|
|
2857
|
+
* new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
|
|
2858
|
+
* 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
2859
|
+
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
2860
|
+
* .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
|
|
2861
|
+
* );
|
|
2535
2862
|
*/
|
|
2536
2863
|
class ParticleEmitter extends EngineObject
|
|
2537
2864
|
{
|
|
2538
|
-
/**
|
|
2539
|
-
*
|
|
2540
|
-
*
|
|
2541
|
-
*
|
|
2542
|
-
*
|
|
2543
|
-
*
|
|
2544
|
-
*
|
|
2545
|
-
*
|
|
2546
|
-
*
|
|
2547
|
-
*
|
|
2548
|
-
*
|
|
2549
|
-
*
|
|
2550
|
-
*
|
|
2551
|
-
*
|
|
2552
|
-
*
|
|
2553
|
-
*
|
|
2554
|
-
*
|
|
2555
|
-
*
|
|
2556
|
-
*
|
|
2557
|
-
*
|
|
2558
|
-
*
|
|
2559
|
-
*
|
|
2560
|
-
*
|
|
2561
|
-
*
|
|
2562
|
-
*
|
|
2563
|
-
*
|
|
2564
|
-
*
|
|
2565
|
-
* @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
|
|
2865
|
+
/** Create a particle system with the given settings
|
|
2866
|
+
* @param {Vector2} position - World space position of the emitter
|
|
2867
|
+
* @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
2868
|
+
* @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
|
|
2869
|
+
* @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
|
|
2870
|
+
* @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
|
|
2871
|
+
* @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied
|
|
2872
|
+
* @param {Number} [tileSize=tileSizeDefault] - Tile size for particles
|
|
2873
|
+
* @param {Color} [colorStartA=new Color(1,1,1)] - Color at start of life 1, randomized between start colors
|
|
2874
|
+
* @param {Color} [colorStartB=new Color(1,1,1)] - Color at start of life 2, randomized between start colors
|
|
2875
|
+
* @param {Color} [colorEndA=new Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
|
|
2876
|
+
* @param {Color} [colorEndB=new Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
|
|
2877
|
+
* @param {Number} [particleTime=.5] - How long particles live
|
|
2878
|
+
* @param {Number} [sizeStart=.1] - How big are particles at start
|
|
2879
|
+
* @param {Number} [sizeEnd=1] - How big are particles at end
|
|
2880
|
+
* @param {Number} [speed=.1] - How fast are particles when spawned
|
|
2881
|
+
* @param {Number} [angleSpeed=.05] - How fast are particles rotating
|
|
2882
|
+
* @param {Number} [damping=1] - How much to dampen particle speed
|
|
2883
|
+
* @param {Number} [angleDamping=1] - How much to dampen particle angular speed
|
|
2884
|
+
* @param {Number} [gravityScale=0] - How much does gravity effect particles
|
|
2885
|
+
* @param {Number} [particleConeAngle=PI] - Cone for start particle angle
|
|
2886
|
+
* @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life
|
|
2887
|
+
* @param {Number} [randomness=.2] - Apply extra randomness percent
|
|
2888
|
+
* @param {Boolean} [collideTiles=0] - Do particles collide against tiles
|
|
2889
|
+
* @param {Boolean} [additive=0] - Should particles use addtive blend
|
|
2890
|
+
* @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
|
|
2891
|
+
* @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
|
|
2566
2892
|
*/
|
|
2567
2893
|
constructor
|
|
2568
2894
|
(
|
|
@@ -2572,7 +2898,7 @@ class ParticleEmitter extends EngineObject
|
|
|
2572
2898
|
emitRate = 100,
|
|
2573
2899
|
emitConeAngle = PI,
|
|
2574
2900
|
tileIndex = -1,
|
|
2575
|
-
tileSize =
|
|
2901
|
+
tileSize = tileSizeDefault,
|
|
2576
2902
|
colorStartA = new Color,
|
|
2577
2903
|
colorStartB = new Color,
|
|
2578
2904
|
colorEndA = new Color(1,1,1,0),
|
|
@@ -2594,37 +2920,61 @@ class ParticleEmitter extends EngineObject
|
|
|
2594
2920
|
renderOrder = additive ? 1e9 : 0
|
|
2595
2921
|
)
|
|
2596
2922
|
{
|
|
2597
|
-
super(pos, new Vector2, tileIndex, tileSize);
|
|
2923
|
+
super(pos, new Vector2, tileIndex, tileSize, 0, undefined, renderOrder);
|
|
2598
2924
|
|
|
2599
2925
|
// emitter settings
|
|
2926
|
+
/** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
2600
2927
|
this.emitSize = emitSize
|
|
2928
|
+
/** @property {Number} - How long to stay alive (0 is forever) */
|
|
2601
2929
|
this.emitTime = emitTime;
|
|
2930
|
+
/** @property {Number} - How many particles per second to spawn, does not emit if 0 */
|
|
2602
2931
|
this.emitRate = emitRate;
|
|
2932
|
+
/** @property {Number} - Local angle to apply velocity to particles from emitter */
|
|
2603
2933
|
this.emitConeAngle = emitConeAngle;
|
|
2604
2934
|
|
|
2605
2935
|
// color settings
|
|
2936
|
+
/** @property {Color} - Color at start of life 1, randomized between start colors */
|
|
2606
2937
|
this.colorStartA = colorStartA;
|
|
2938
|
+
/** @property {Color} - Color at start of life 2, randomized between start colors */
|
|
2607
2939
|
this.colorStartB = colorStartB;
|
|
2940
|
+
/** @property {Color} - Color at end of life 1, randomized between end colors */
|
|
2608
2941
|
this.colorEndA = colorEndA;
|
|
2942
|
+
/** @property {Color} - Color at end of life 2, randomized between end colors */
|
|
2609
2943
|
this.colorEndB = colorEndB;
|
|
2944
|
+
/** @property {Boolean} - Should color be randomized linearly or across each component */
|
|
2610
2945
|
this.randomColorLinear = randomColorLinear;
|
|
2611
2946
|
|
|
2612
2947
|
// particle settings
|
|
2948
|
+
/** @property {Number} - How long particles live */
|
|
2613
2949
|
this.particleTime = particleTime;
|
|
2950
|
+
/** @property {Number} - How big are particles at start */
|
|
2614
2951
|
this.sizeStart = sizeStart;
|
|
2952
|
+
/** @property {Number} - How big are particles at end */
|
|
2615
2953
|
this.sizeEnd = sizeEnd;
|
|
2954
|
+
/** @property {Number} - How fast are particles when spawned */
|
|
2616
2955
|
this.speed = speed;
|
|
2956
|
+
/** @property {Number} - How fast are particles rotating */
|
|
2617
2957
|
this.angleSpeed = angleSpeed;
|
|
2958
|
+
/** @property {Number} - How much to dampen particle speed */
|
|
2618
2959
|
this.damping = damping;
|
|
2960
|
+
/** @property {Number} - How much to dampen particle angular speed */
|
|
2619
2961
|
this.angleDamping = angleDamping;
|
|
2962
|
+
/** @property {Number} - How much does gravity effect particles */
|
|
2620
2963
|
this.gravityScale = gravityScale;
|
|
2964
|
+
/** @property {Number} - Cone for start particle angle */
|
|
2621
2965
|
this.particleConeAngle = particleConeAngle;
|
|
2966
|
+
/** @property {Number} - How quick to fade in particles at start/end in percent of life */
|
|
2622
2967
|
this.fadeRate = fadeRate;
|
|
2968
|
+
/** @property {Number} - Apply extra randomness percent */
|
|
2623
2969
|
this.randomness = randomness;
|
|
2970
|
+
/** @property {Number} - Do particles collide against tiles */
|
|
2624
2971
|
this.collideTiles = collideTiles;
|
|
2972
|
+
/** @property {Number} - Should particles use addtive blend */
|
|
2625
2973
|
this.additive = additive;
|
|
2626
|
-
|
|
2627
|
-
this.trailScale =
|
|
2974
|
+
/** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */
|
|
2975
|
+
this.trailScale = 0;
|
|
2976
|
+
|
|
2977
|
+
// internal variables
|
|
2628
2978
|
this.emitTimeBuffer = 0;
|
|
2629
2979
|
}
|
|
2630
2980
|
|
|
@@ -2638,9 +2988,9 @@ class ParticleEmitter extends EngineObject
|
|
|
2638
2988
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
2639
2989
|
{
|
|
2640
2990
|
// emit particles
|
|
2641
|
-
if (this.emitRate)
|
|
2991
|
+
if (this.emitRate * particleEmitRateScale)
|
|
2642
2992
|
{
|
|
2643
|
-
const rate = 1/this.emitRate;
|
|
2993
|
+
const rate = 1/this.emitRate/particleEmitRateScale;
|
|
2644
2994
|
for (this.emitTimeBuffer += timeDelta; this.emitTimeBuffer > 0; this.emitTimeBuffer -= rate)
|
|
2645
2995
|
this.emitParticle();
|
|
2646
2996
|
}
|
|
@@ -2667,34 +3017,34 @@ class ParticleEmitter extends EngineObject
|
|
|
2667
3017
|
const randomizeScale = (v)=> v + v*rand(randomness, -randomness);
|
|
2668
3018
|
|
|
2669
3019
|
// randomize particle settings
|
|
2670
|
-
const particleTime
|
|
2671
|
-
const sizeStart
|
|
2672
|
-
const sizeEnd
|
|
2673
|
-
const speed
|
|
2674
|
-
const angleSpeed
|
|
2675
|
-
const coneAngle
|
|
2676
|
-
const colorStart
|
|
2677
|
-
const colorEnd
|
|
3020
|
+
const particleTime = randomizeScale(this.particleTime);
|
|
3021
|
+
const sizeStart = randomizeScale(this.sizeStart);
|
|
3022
|
+
const sizeEnd = randomizeScale(this.sizeEnd);
|
|
3023
|
+
const speed = randomizeScale(this.speed);
|
|
3024
|
+
const angleSpeed = randomizeScale(this.angleSpeed) * randSign();
|
|
3025
|
+
const coneAngle = rand(this.emitConeAngle, -this.emitConeAngle);
|
|
3026
|
+
const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
|
|
3027
|
+
const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
|
|
2678
3028
|
|
|
2679
3029
|
// build particle settings
|
|
2680
|
-
particle.colorStart
|
|
2681
|
-
particle.colorEndDelta
|
|
2682
|
-
particle.velocity
|
|
2683
|
-
particle.angleVelocity
|
|
2684
|
-
particle.lifeTime
|
|
2685
|
-
particle.sizeStart
|
|
2686
|
-
particle.sizeEndDelta
|
|
2687
|
-
particle.fadeRate
|
|
2688
|
-
particle.damping
|
|
2689
|
-
particle.angleDamping
|
|
2690
|
-
particle.elasticity
|
|
2691
|
-
particle.friction
|
|
2692
|
-
particle.gravityScale
|
|
2693
|
-
particle.collideTiles
|
|
2694
|
-
particle.additive
|
|
2695
|
-
particle.renderOrder
|
|
2696
|
-
particle.trailScale
|
|
2697
|
-
particle.mirror
|
|
3030
|
+
particle.colorStart = colorStart;
|
|
3031
|
+
particle.colorEndDelta = colorEnd.subtract(colorStart);
|
|
3032
|
+
particle.velocity = (new Vector2).setAngle(this.angle + coneAngle, speed);
|
|
3033
|
+
particle.angleVelocity = angleSpeed;
|
|
3034
|
+
particle.lifeTime = particleTime;
|
|
3035
|
+
particle.sizeStart = sizeStart;
|
|
3036
|
+
particle.sizeEndDelta = sizeEnd - sizeStart;
|
|
3037
|
+
particle.fadeRate = this.fadeRate;
|
|
3038
|
+
particle.damping = this.damping;
|
|
3039
|
+
particle.angleDamping = this.angleDamping;
|
|
3040
|
+
particle.elasticity = this.elasticity;
|
|
3041
|
+
particle.friction = this.friction;
|
|
3042
|
+
particle.gravityScale = this.gravityScale;
|
|
3043
|
+
particle.collideTiles = this.collideTiles;
|
|
3044
|
+
particle.additive = this.additive;
|
|
3045
|
+
particle.renderOrder = this.renderOrder;
|
|
3046
|
+
particle.trailScale = this.trailScale;
|
|
3047
|
+
particle.mirror = rand()<.5;
|
|
2698
3048
|
|
|
2699
3049
|
// setup callbacks for particles
|
|
2700
3050
|
particle.destroyCallback = this.particleDestroyCallback;
|
|
@@ -2711,15 +3061,16 @@ class ParticleEmitter extends EngineObject
|
|
|
2711
3061
|
///////////////////////////////////////////////////////////////////////////////
|
|
2712
3062
|
/**
|
|
2713
3063
|
* Particle Object - Created automatically by Particle Emitters
|
|
3064
|
+
* @extends EngineObject
|
|
2714
3065
|
*/
|
|
2715
3066
|
class Particle extends EngineObject
|
|
2716
3067
|
{
|
|
2717
3068
|
/**
|
|
2718
3069
|
* Create a particle with the given settings
|
|
2719
3070
|
* @param {Vector2} position - World space position of the particle
|
|
2720
|
-
* @param {Number} [tileIndex=-1]
|
|
2721
|
-
* @param {Vector2} [tileSize=
|
|
2722
|
-
* @param {Number} [angle=0]
|
|
3071
|
+
* @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1
|
|
3072
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
3073
|
+
* @param {Number} [angle=0] - Angle to rotate the particle
|
|
2723
3074
|
*/
|
|
2724
3075
|
constructor(pos, tileIndex, tileSize, angle) { super(pos, new Vector2, tileIndex, tileSize, angle); }
|
|
2725
3076
|
|
|
@@ -2766,11 +3117,11 @@ class Particle extends EngineObject
|
|
|
2766
3117
|
}
|
|
2767
3118
|
}
|
|
2768
3119
|
/**
|
|
2769
|
-
*
|
|
2770
|
-
*
|
|
2771
|
-
*
|
|
2772
|
-
*
|
|
2773
|
-
*
|
|
3120
|
+
* LittleJS Medal System
|
|
3121
|
+
* <br> - Tracks and displays medals
|
|
3122
|
+
* <br> - Saves medals to local storage
|
|
3123
|
+
* <br> - Newgrounds and OS13k integration
|
|
3124
|
+
* @namespace Medals
|
|
2774
3125
|
*/
|
|
2775
3126
|
|
|
2776
3127
|
'use strict';
|
|
@@ -2789,32 +3140,42 @@ let medalsPreventUnlock;
|
|
|
2789
3140
|
let newgrounds;
|
|
2790
3141
|
|
|
2791
3142
|
// Engine internal variables not exposed to documentation
|
|
2792
|
-
let medalsDisplayQueue = [], medalsSaveName,
|
|
3143
|
+
let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
|
|
2793
3144
|
|
|
2794
3145
|
///////////////////////////////////////////////////////////////////////////////
|
|
2795
3146
|
|
|
2796
3147
|
/** Initialize medals with a save name used for storage
|
|
2797
|
-
* <br> - Checks if medals are unlocked
|
|
2798
3148
|
* <br> - Call this after creating all medals
|
|
3149
|
+
* <br> - Checks if medals are unlocked
|
|
2799
3150
|
* @param {String} saveName
|
|
2800
3151
|
* @memberof Medals */
|
|
2801
3152
|
function medalsInit(saveName)
|
|
2802
3153
|
{
|
|
2803
3154
|
// check if medals are unlocked
|
|
2804
3155
|
medalsSaveName = saveName;
|
|
2805
|
-
debugMedals || medals.forEach(medal=> medal.unlocked = localStorage[medal.storageKey()]);
|
|
3156
|
+
debugMedals || medals.forEach(medal=> medal.unlocked = 1 || localStorage[medal.storageKey()]);
|
|
2806
3157
|
}
|
|
2807
3158
|
|
|
2808
|
-
/**
|
|
3159
|
+
/**
|
|
3160
|
+
* Medal Object - Tracks an unlockable medal
|
|
3161
|
+
* @example
|
|
3162
|
+
* // create a medal
|
|
3163
|
+
* const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
|
|
3164
|
+
*
|
|
3165
|
+
* // initialize medals
|
|
3166
|
+
* medalsInit('Example Game');
|
|
3167
|
+
*
|
|
3168
|
+
* // unlock the medal
|
|
3169
|
+
* medal_example.unlock();
|
|
3170
|
+
*/
|
|
2809
3171
|
class Medal
|
|
2810
3172
|
{
|
|
2811
|
-
/**
|
|
2812
|
-
*
|
|
2813
|
-
*
|
|
2814
|
-
*
|
|
2815
|
-
*
|
|
2816
|
-
*
|
|
2817
|
-
* @param {String} [src] - Image location for the medal
|
|
3173
|
+
/** Create an medal object and adds it to the list of medals
|
|
3174
|
+
* @param {Number} id - The unique identifier of the medal
|
|
3175
|
+
* @param {String} name - Name of the medal
|
|
3176
|
+
* @param {String} [description] - Description of the medal
|
|
3177
|
+
* @param {String} [icon='🏆'] - Icon for the medal
|
|
3178
|
+
* @param {String} [src] - Image location for the medal
|
|
2818
3179
|
*/
|
|
2819
3180
|
constructor(id, name, description='', icon='🏆', src)
|
|
2820
3181
|
{
|
|
@@ -2825,13 +3186,9 @@ class Medal
|
|
|
2825
3186
|
this.name = name;
|
|
2826
3187
|
this.description = description;
|
|
2827
3188
|
this.icon = icon;
|
|
2828
|
-
|
|
3189
|
+
this.image = new Image();
|
|
2829
3190
|
if (src)
|
|
2830
|
-
{
|
|
2831
|
-
// load image
|
|
2832
|
-
this.image = new Image();
|
|
2833
3191
|
this.image.src = src;
|
|
2834
|
-
}
|
|
2835
3192
|
}
|
|
2836
3193
|
|
|
2837
3194
|
/** Unlocks a medal if not already unlocked */
|
|
@@ -2841,7 +3198,7 @@ class Medal
|
|
|
2841
3198
|
return;
|
|
2842
3199
|
|
|
2843
3200
|
// save the medal
|
|
2844
|
-
ASSERT(medalsSaveName); //
|
|
3201
|
+
ASSERT(medalsSaveName); // save name must be set
|
|
2845
3202
|
localStorage[this.storageKey()] = this.unlocked = 1;
|
|
2846
3203
|
medalsDisplayQueue.push(this);
|
|
2847
3204
|
|
|
@@ -2856,27 +3213,28 @@ class Medal
|
|
|
2856
3213
|
render(hidePercent=0)
|
|
2857
3214
|
{
|
|
2858
3215
|
const context = overlayContext;
|
|
2859
|
-
const
|
|
3216
|
+
const width = min(medalDisplayWidth, mainCanvas.width);
|
|
3217
|
+
const x = overlayCanvas.width - width;
|
|
2860
3218
|
const y = -medalDisplayHeight*hidePercent;
|
|
2861
3219
|
|
|
2862
3220
|
// draw containing rect and clip to that region
|
|
2863
3221
|
context.save();
|
|
2864
3222
|
context.beginPath();
|
|
2865
3223
|
context.fillStyle = '#ddd'
|
|
2866
|
-
context.fill(context.rect(x, y,
|
|
2867
|
-
context.strokeStyle =
|
|
2868
|
-
context.lineWidth =
|
|
3224
|
+
context.fill(context.rect(x, y, width, medalDisplayHeight));
|
|
3225
|
+
context.strokeStyle = '#000';
|
|
3226
|
+
context.lineWidth = 3;
|
|
2869
3227
|
context.stroke();
|
|
2870
3228
|
context.clip();
|
|
2871
3229
|
|
|
3230
|
+
// draw the icon and text
|
|
2872
3231
|
this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
|
|
2873
|
-
|
|
2874
|
-
// draw the text
|
|
2875
3232
|
context.textAlign = 'left';
|
|
2876
|
-
context.font = '
|
|
2877
|
-
context.fillText(this.name, x+medalDisplayIconSize+
|
|
2878
|
-
context.font = '
|
|
2879
|
-
context.
|
|
3233
|
+
context.font = '38px '+ fontDefault;
|
|
3234
|
+
context.fillText(this.name, x+medalDisplayIconSize+30, y+28);
|
|
3235
|
+
context.font = '24px '+ fontDefault;
|
|
3236
|
+
context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
|
|
3237
|
+
context.restore();
|
|
2880
3238
|
}
|
|
2881
3239
|
|
|
2882
3240
|
/** Render the icon for a medal
|
|
@@ -2888,21 +3246,18 @@ class Medal
|
|
|
2888
3246
|
{
|
|
2889
3247
|
// draw the image or icon
|
|
2890
3248
|
const context = overlayContext;
|
|
3249
|
+
context.fillStyle = '#000';
|
|
2891
3250
|
context.textAlign = 'center';
|
|
2892
3251
|
context.textBaseline = 'middle';
|
|
2893
|
-
context.font = size*.
|
|
2894
|
-
|
|
2895
|
-
if (this.image)
|
|
3252
|
+
context.font = size*.7 + 'px '+ fontDefault;
|
|
3253
|
+
if (this.image.src)
|
|
2896
3254
|
context.drawImage(this.image, x-size/2, y-size/2, size, size);
|
|
2897
3255
|
else
|
|
2898
3256
|
context.fillText(this.icon, x, y); // show icon if there is no image
|
|
2899
3257
|
}
|
|
2900
3258
|
|
|
2901
3259
|
// Get local storage key used by the medal
|
|
2902
|
-
storageKey()
|
|
2903
|
-
{
|
|
2904
|
-
return medalsSaveName + '_medal_' + this.id;
|
|
2905
|
-
}
|
|
3260
|
+
storageKey() { return medalsSaveName + '_' + this.id; }
|
|
2906
3261
|
}
|
|
2907
3262
|
|
|
2908
3263
|
// engine automatically renders medals
|
|
@@ -2913,11 +3268,11 @@ function medalsRender()
|
|
|
2913
3268
|
|
|
2914
3269
|
// update first medal in queue
|
|
2915
3270
|
const medal = medalsDisplayQueue[0];
|
|
2916
|
-
const time = timeReal -
|
|
2917
|
-
if (!
|
|
2918
|
-
|
|
3271
|
+
const time = timeReal - medalsDisplayTimeLast;
|
|
3272
|
+
if (!medalsDisplayTimeLast)
|
|
3273
|
+
medalsDisplayTimeLast = timeReal;
|
|
2919
3274
|
else if (time > medalDisplayTime)
|
|
2920
|
-
medalsDisplayQueue.shift(
|
|
3275
|
+
medalsDisplayQueue.shift(medalsDisplayTimeLast = 0);
|
|
2921
3276
|
else
|
|
2922
3277
|
{
|
|
2923
3278
|
// slide on/off medals
|
|
@@ -2931,14 +3286,19 @@ function medalsRender()
|
|
|
2931
3286
|
|
|
2932
3287
|
///////////////////////////////////////////////////////////////////////////////
|
|
2933
3288
|
|
|
2934
|
-
/**
|
|
3289
|
+
/**
|
|
3290
|
+
* Newgrounds API wrapper object
|
|
3291
|
+
* @example
|
|
3292
|
+
* // create a newgrounds object, replace the app id and cipher with your own
|
|
3293
|
+
* const app_id = '53123:1ZuSTQ9l';
|
|
3294
|
+
* const cipher = 'enF0vGH@Mj/FRASKL23Q==';
|
|
3295
|
+
* newgrounds = new Newgrounds(app_id, cipher);
|
|
3296
|
+
*/
|
|
2935
3297
|
class Newgrounds
|
|
2936
3298
|
{
|
|
2937
|
-
/**
|
|
2938
|
-
*
|
|
2939
|
-
*
|
|
2940
|
-
* @param {String} [cipher] - The encryption Key (AES-128/Base64)
|
|
2941
|
-
*/
|
|
3299
|
+
/** Create a newgrounds object
|
|
3300
|
+
* @param {Number} app_id - The newgrounds App ID
|
|
3301
|
+
* @param {String} [cipher] - The encryption Key (AES-128/Base64) */
|
|
2942
3302
|
constructor(app_id, cipher)
|
|
2943
3303
|
{
|
|
2944
3304
|
ASSERT(!newgrounds && app_id);
|
|
@@ -2966,7 +3326,6 @@ class Newgrounds
|
|
|
2966
3326
|
if (medal)
|
|
2967
3327
|
{
|
|
2968
3328
|
// copy newgrounds medal data
|
|
2969
|
-
medal.image = new Image();
|
|
2970
3329
|
medal.image.src = newgroundsMedal['icon'];
|
|
2971
3330
|
medal.name = newgroundsMedal['name'];
|
|
2972
3331
|
medal.description = newgroundsMedal['description'];
|
|
@@ -2990,7 +3349,7 @@ class Newgrounds
|
|
|
2990
3349
|
unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, 1); }
|
|
2991
3350
|
|
|
2992
3351
|
/** Send message to post score
|
|
2993
|
-
* @param {Number} id
|
|
3352
|
+
* @param {Number} id - The scoreboard id
|
|
2994
3353
|
* @param {Number} value - The score value */
|
|
2995
3354
|
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
|
|
2996
3355
|
|
|
@@ -2998,21 +3357,21 @@ class Newgrounds
|
|
|
2998
3357
|
logView() { return this.call('App.logView', {'host':this.host}, 1); }
|
|
2999
3358
|
|
|
3000
3359
|
/** Get scores from a scoreboard
|
|
3001
|
-
* @param {Number} id
|
|
3002
|
-
* @param {String} [user=0]
|
|
3360
|
+
* @param {Number} id - The scoreboard id
|
|
3361
|
+
* @param {String} [user=0] - A user's id or name
|
|
3003
3362
|
* @param {Number} [social=0] - If true, only social scores will be loaded
|
|
3004
|
-
* @param {Number} [skip=0]
|
|
3363
|
+
* @param {Number} [skip=0] - Number of scores to skip before start
|
|
3005
3364
|
* @param {Number} [limit=10] - Number of scores to include in the list
|
|
3006
|
-
* @return {Object}
|
|
3365
|
+
* @return {Object} - The response JSON object
|
|
3007
3366
|
*/
|
|
3008
3367
|
getScores(id, user=0, social=0, skip=0, limit=10)
|
|
3009
3368
|
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
3010
3369
|
|
|
3011
3370
|
/** Send a message to call a component of the Newgrounds API
|
|
3012
|
-
* @param {String} component
|
|
3371
|
+
* @param {String} component - Name of the component
|
|
3013
3372
|
* @param {Object} [parameters=0] - Parameters to use for call
|
|
3014
|
-
* @param {Boolean} [async=0]
|
|
3015
|
-
* @return {Object}
|
|
3373
|
+
* @param {Boolean} [async=0] - If true, don't wait for response before continuing (avoid stall)
|
|
3374
|
+
* @return {Object} - The response JSON object
|
|
3016
3375
|
*/
|
|
3017
3376
|
call(component, parameters=0, async=0)
|
|
3018
3377
|
{
|
|
@@ -3056,14 +3415,14 @@ class Newgrounds
|
|
|
3056
3415
|
|
|
3057
3416
|
const CryptoJS=()=>eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",...']charCodeAtUinyxpf',"for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
|
|
3058
3417
|
/**
|
|
3059
|
-
*
|
|
3060
|
-
*
|
|
3061
|
-
*
|
|
3062
|
-
*
|
|
3063
|
-
*
|
|
3064
|
-
*
|
|
3065
|
-
*
|
|
3066
|
-
*
|
|
3418
|
+
* LittleJS WebGL Interface
|
|
3419
|
+
* <br> - All webgl used by the engine is wrapped up here
|
|
3420
|
+
* <br> - For normal stuff you won't need to see or call anything in this file
|
|
3421
|
+
* <br> - For advanced stuff there are helper functions to create shaders, textures, etc
|
|
3422
|
+
* <br> - Can be disabled with glEnable to revert to 2D canvas rendering
|
|
3423
|
+
* <br> - Batches sprite rendering on GPU for incredibly fast performance
|
|
3424
|
+
* <br> - Sprite transform math is done in the shader where possible
|
|
3425
|
+
* @namespace WebGL
|
|
3067
3426
|
*/
|
|
3068
3427
|
|
|
3069
3428
|
'use strict';
|
|
@@ -3084,7 +3443,7 @@ let glContext;
|
|
|
3084
3443
|
let glTileTexture;
|
|
3085
3444
|
|
|
3086
3445
|
// WebGL internal variables not exposed to documentation
|
|
3087
|
-
let glActiveTexture, glShader, glPositionData, glColorData, glBatchCount,
|
|
3446
|
+
let glActiveTexture, glShader, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
|
|
3088
3447
|
|
|
3089
3448
|
///////////////////////////////////////////////////////////////////////////////
|
|
3090
3449
|
|
|
@@ -3095,19 +3454,16 @@ function glInit()
|
|
|
3095
3454
|
|
|
3096
3455
|
// create the canvas and tile texture
|
|
3097
3456
|
glCanvas = document.createElement('canvas');
|
|
3098
|
-
glContext = glCanvas.getContext('webgl'
|
|
3457
|
+
glContext = glCanvas.getContext('webgl');
|
|
3458
|
+
glCanvas.style = styleCanvas;
|
|
3099
3459
|
glTileTexture = glCreateTexture(tileImage);
|
|
3100
3460
|
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
// firefox is much faster without copying the gl buffer so we just overlay it with some tradeoffs
|
|
3104
|
-
document.body.appendChild(glCanvas);
|
|
3105
|
-
glCanvas.style = mainCanvas.style.cssText;
|
|
3106
|
-
}
|
|
3461
|
+
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
3462
|
+
glOverlay && document.body.appendChild(glCanvas);
|
|
3107
3463
|
|
|
3108
3464
|
// setup vertex and fragment shaders
|
|
3109
3465
|
glShader = glCreateProgram(
|
|
3110
|
-
'precision
|
|
3466
|
+
'precision highp float;'+ // use highp for better accuracy, lowp for better perf
|
|
3111
3467
|
'uniform mat4 m;'+ // transform matrix
|
|
3112
3468
|
'attribute float a;'+ // angle
|
|
3113
3469
|
'attribute vec2 p,s,t;'+ // position, size, uv
|
|
@@ -3119,7 +3475,7 @@ function glInit()
|
|
|
3119
3475
|
'v=t;d=c;e=b;'+ // pass stuff to fragment shader
|
|
3120
3476
|
'}' // end of shader
|
|
3121
3477
|
,
|
|
3122
|
-
'precision
|
|
3478
|
+
'precision highp float;'+ // use highp for better accuracy, lowp for better perf
|
|
3123
3479
|
'varying vec2 v;'+ // uv
|
|
3124
3480
|
'varying vec4 d,e;'+ // color, additiveColor
|
|
3125
3481
|
'uniform sampler2D j;'+ // texture
|
|
@@ -3135,6 +3491,7 @@ function glInit()
|
|
|
3135
3491
|
glColorData = new Uint32Array(glVertexData);
|
|
3136
3492
|
|
|
3137
3493
|
// setup the vertex data array
|
|
3494
|
+
let offset = glBatchCount = 0;
|
|
3138
3495
|
const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
|
|
3139
3496
|
{
|
|
3140
3497
|
const location = glContext.getAttribLocation(glShader, name);
|
|
@@ -3142,7 +3499,6 @@ function glInit()
|
|
|
3142
3499
|
glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
|
|
3143
3500
|
offset += size*typeSize;
|
|
3144
3501
|
}
|
|
3145
|
-
let offset = glDirty = glBatchCount = 0;
|
|
3146
3502
|
initVertexAttribArray('a', gl_FLOAT, 4, 1); // angle
|
|
3147
3503
|
initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
|
|
3148
3504
|
initVertexAttribArray('s', gl_FLOAT, 4, 2); // size
|
|
@@ -3158,14 +3514,8 @@ function glSetBlendMode(additive)
|
|
|
3158
3514
|
{
|
|
3159
3515
|
if (!glEnable) return;
|
|
3160
3516
|
|
|
3161
|
-
if (additive != glAdditive)
|
|
3162
|
-
glFlush();
|
|
3163
|
-
|
|
3164
3517
|
// setup blending
|
|
3165
3518
|
glAdditive = additive;
|
|
3166
|
-
const destBlend = additive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
|
|
3167
|
-
glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
|
|
3168
|
-
glContext.enable(gl_BLEND);
|
|
3169
3519
|
}
|
|
3170
3520
|
|
|
3171
3521
|
/** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
|
|
@@ -3175,7 +3525,8 @@ function glSetBlendMode(additive)
|
|
|
3175
3525
|
function glSetTexture(texture=glTileTexture)
|
|
3176
3526
|
{
|
|
3177
3527
|
if (!glEnable) return;
|
|
3178
|
-
|
|
3528
|
+
|
|
3529
|
+
// must flush cache with the old texture to set a new one
|
|
3179
3530
|
if (texture != glActiveTexture)
|
|
3180
3531
|
glFlush();
|
|
3181
3532
|
|
|
@@ -3240,36 +3591,35 @@ function glCreateBuffer(bufferType, size, usage)
|
|
|
3240
3591
|
return buffer;
|
|
3241
3592
|
}
|
|
3242
3593
|
|
|
3243
|
-
/** Create WebGL texture from an image
|
|
3594
|
+
/** Create WebGL texture from an image and set the texture settings
|
|
3244
3595
|
* @param {Image} image
|
|
3245
3596
|
* @return {WebGLTexture}
|
|
3246
3597
|
* @memberof WebGL */
|
|
3247
3598
|
function glCreateTexture(image)
|
|
3248
3599
|
{
|
|
3249
|
-
if (!glEnable) return;
|
|
3600
|
+
if (!glEnable || !image || !image.width) return;
|
|
3250
3601
|
|
|
3251
3602
|
// build the texture
|
|
3252
3603
|
const texture = glContext.createTexture();
|
|
3253
3604
|
glContext.bindTexture(gl_TEXTURE_2D, texture);
|
|
3254
3605
|
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
3255
|
-
|
|
3606
|
+
|
|
3256
3607
|
// use point filtering for pixelated rendering
|
|
3257
|
-
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER,
|
|
3258
|
-
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER,
|
|
3608
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
|
|
3609
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, cavasPixelated ? gl_NEAREST : gl_LINEAR);
|
|
3259
3610
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
|
|
3260
3611
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
|
|
3261
3612
|
return texture;
|
|
3262
3613
|
}
|
|
3263
3614
|
|
|
3264
3615
|
// called automatically by engine before render
|
|
3265
|
-
function glPreRender(width, height)
|
|
3616
|
+
function glPreRender(width, height, cameraX, cameraY, cameraScale)
|
|
3266
3617
|
{
|
|
3267
3618
|
if (!glEnable) return;
|
|
3268
3619
|
|
|
3269
3620
|
// clear and set to same size as main canvas
|
|
3270
|
-
glCanvas.width = width;
|
|
3271
|
-
|
|
3272
|
-
glContext.viewport(0, 0, width, height);
|
|
3621
|
+
glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
|
|
3622
|
+
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3273
3623
|
|
|
3274
3624
|
// set up the shader
|
|
3275
3625
|
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
|
|
@@ -3284,7 +3634,7 @@ function glPreRender(width, height)
|
|
|
3284
3634
|
sx, 0, 0, 0,
|
|
3285
3635
|
0, sy, 0, 0,
|
|
3286
3636
|
1, 1, -1, 1,
|
|
3287
|
-
-1-sx*
|
|
3637
|
+
-1-sx*cameraX, -1-sy*cameraY, 0, 0
|
|
3288
3638
|
])
|
|
3289
3639
|
);
|
|
3290
3640
|
}
|
|
@@ -3295,11 +3645,16 @@ function glFlush()
|
|
|
3295
3645
|
{
|
|
3296
3646
|
if (!glEnable || !glBatchCount) return;
|
|
3297
3647
|
|
|
3648
|
+
const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
|
|
3649
|
+
glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
|
|
3650
|
+
glContext.enable(gl_BLEND);
|
|
3651
|
+
|
|
3298
3652
|
// draw all the sprites in the batch and reset the buffer
|
|
3299
3653
|
glContext.bufferSubData(gl_ARRAY_BUFFER, 0,
|
|
3300
3654
|
glPositionData.subarray(0, glBatchCount * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT));
|
|
3301
3655
|
glContext.drawArrays(gl_TRIANGLES, 0, glBatchCount * gl_VERTICES_PER_QUAD);
|
|
3302
3656
|
glBatchCount = 0;
|
|
3657
|
+
glBatchAdditive = glAdditive;
|
|
3303
3658
|
}
|
|
3304
3659
|
|
|
3305
3660
|
/** Draw any sprites still in the buffer, copy to main canvas and clear
|
|
@@ -3308,29 +3663,38 @@ function glFlush()
|
|
|
3308
3663
|
* @memberof WebGL */
|
|
3309
3664
|
function glCopyToContext(context, forceDraw)
|
|
3310
3665
|
{
|
|
3311
|
-
if (!glEnable || !
|
|
3666
|
+
if (!glEnable || !glBatchCount) return;
|
|
3312
3667
|
|
|
3313
3668
|
glFlush();
|
|
3669
|
+
|
|
3670
|
+
// do not draw in overlay mode because the canvas is visible
|
|
3314
3671
|
if (!glOverlay || forceDraw)
|
|
3315
|
-
|
|
3316
|
-
// do not draw/clear in overlay mode because the canvas is visible
|
|
3317
|
-
context.drawImage(glCanvas, 0, glAdditive = glDirty = 0);
|
|
3318
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3319
|
-
}
|
|
3672
|
+
context.drawImage(glCanvas, 0, 0);
|
|
3320
3673
|
}
|
|
3321
3674
|
|
|
3322
|
-
|
|
3323
|
-
|
|
3675
|
+
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
3676
|
+
* @param x
|
|
3677
|
+
* @param y
|
|
3678
|
+
* @param sizeX
|
|
3679
|
+
* @param sizeY
|
|
3680
|
+
* @param angle
|
|
3681
|
+
* @param uv0X
|
|
3682
|
+
* @param uv0Y
|
|
3683
|
+
* @param uv1X
|
|
3684
|
+
* @param uv1Y
|
|
3685
|
+
* @param [rgba=0xffffffff]
|
|
3686
|
+
* @param [rgbaAdditive=0]
|
|
3687
|
+
* @memberof WebGL */
|
|
3688
|
+
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffffff, rgbaAdditive=0)
|
|
3324
3689
|
{
|
|
3325
3690
|
if (!glEnable) return;
|
|
3326
|
-
|
|
3327
|
-
// flush if there is no room for more verts
|
|
3328
|
-
if (glBatchCount == gl_MAX_BATCH)
|
|
3691
|
+
|
|
3692
|
+
// flush if there is no room for more verts or if different blend mode
|
|
3693
|
+
if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
|
|
3329
3694
|
glFlush();
|
|
3330
3695
|
|
|
3331
3696
|
// setup 2 triangles to form a quad
|
|
3332
3697
|
let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
|
|
3333
|
-
glDirty = 1;
|
|
3334
3698
|
|
|
3335
3699
|
// vertex 0
|
|
3336
3700
|
glPositionData[offset++] = angle;
|