littlejsengine 1.0.14 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.bat +5 -1
- package/docs/Audio.html +2268 -0
- package/docs/Color.html +2989 -0
- package/docs/Debug.html +2933 -0
- package/docs/Draw.html +4092 -0
- package/docs/EngineObject.html +4458 -0
- package/docs/Input.html +2482 -0
- package/docs/Medal.html +1007 -0
- package/docs/Medals.html +628 -0
- package/docs/Music.html +584 -0
- package/docs/Newgrounds.html +1399 -0
- package/docs/Particle.html +4535 -0
- package/docs/ParticleEmitter.html +7770 -0
- package/docs/Random.html +1774 -0
- package/docs/Settings.html +2679 -0
- package/docs/Sound.html +1249 -0
- package/docs/TileCollision.html +1409 -0
- package/docs/TileLayer.html +6948 -0
- package/docs/TileLayerData.html +1052 -0
- package/docs/Timer.html +1183 -0
- package/docs/Utilities.html +3281 -0
- package/docs/Vector2.html +4207 -0
- package/docs/WebGL.html +2327 -0
- package/docs/engine.js.html +429 -0
- package/docs/engineAudio.js.html +609 -0
- package/docs/engineDebug.js.html +725 -0
- package/docs/engineDraw.js.html +419 -0
- package/docs/engineInput.js.html +401 -0
- package/docs/engineMedals.js.html +461 -0
- package/docs/engineObject.js.html +518 -0
- package/docs/engineParticles.js.html +441 -0
- package/docs/engineSettings.js.html +349 -0
- package/docs/engineTileLayer.js.html +513 -0
- package/docs/engineUtilities.js.html +647 -0
- package/docs/engineWebGL.js.html +522 -0
- package/docs/fonts/MavenPro-Regular.ttf +0 -0
- package/docs/fonts/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Muli-Black.ttf +0 -0
- package/docs/fonts/OFL-hind.txt +93 -0
- package/docs/fonts/OFL-montserrat.txt +93 -0
- package/docs/global.html +1691 -0
- package/docs/index.css +6 -0
- package/docs/index.html +247 -0
- package/docs/scripts/fix-code-block.js +53 -0
- package/docs/scripts/fix-navbar.js +25 -0
- package/docs/scripts/linenumber.js +25 -0
- package/docs/scripts/misc.js +217 -0
- package/docs/scripts/resize.js +85 -0
- package/docs/scripts/search.js +83 -0
- package/docs/scripts/third-party/Apache-License-2.0.txt +202 -0
- package/docs/scripts/third-party/fuse.js +9 -0
- package/docs/scripts/third-party/lang-css.js +2 -0
- package/docs/scripts/third-party/prettify.js +28 -0
- package/docs/static/favicon.png +0 -0
- package/docs/static/index.css +4 -0
- package/docs/styles/clean-jsdoc-theme-base.css +393 -0
- package/docs/styles/clean-jsdoc-theme-dark.css +324 -0
- package/docs/styles/clean-jsdoc-theme-light.css +319 -0
- package/docs/styles/reset.css +346 -0
- package/docs/styles/third-party/ionicons.min.css +11 -0
- package/docs/styles/third-party/prettify-jsdoc.css +111 -0
- package/docs/styles/third-party/prettify-tomorrow.css +5 -0
- package/engine/engine.all.js +1997 -744
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.release.js +1743 -569
- package/engine/engine.js +107 -39
- package/engine/engineAudio.js +152 -34
- package/engine/{build.bat → engineBuild.bat} +10 -10
- package/engine/{buildSetup.bat → engineBuildSetup.bat} +0 -0
- package/engine/engineDebug.js +105 -32
- package/engine/engineDraw.js +137 -32
- package/engine/engineInput.js +111 -37
- package/engine/{engineMedal.js → engineMedals.js} +129 -56
- package/engine/engineObject.js +117 -67
- package/engine/engineParticles.js +282 -0
- package/engine/engineRelease.js +3 -9
- package/engine/engineSettings.js +190 -0
- package/engine/engineTileLayer.js +147 -44
- package/engine/engineUtilities.js +488 -0
- package/engine/engineWebGL.js +113 -74
- package/examples/breakout/game.js +1 -1
- package/examples/breakout/gameObjects.js +3 -3
- package/examples/breakout/index.html +3 -3
- package/examples/platformer/game.js +5 -7
- package/examples/platformer/gameEffects.js +7 -7
- package/examples/platformer/gameLevel.js +2 -2
- package/examples/platformer/gameObjects.js +4 -5
- package/examples/platformer/gamePlayer.js +3 -3
- package/examples/platformer/index.html +6 -6
- package/examples/puzzle/game.js +1 -2
- package/examples/puzzle/index.html +2 -2
- package/examples/stress/index.html +2 -8
- package/game.js +5 -11
- package/index.html +13 -13
- package/package.json +4 -4
- package/tiles.png +0 -0
- package/engine/engineConfig.js +0 -64
- package/engine/engineParticle.js +0 -199
- package/engine/engineUtil.js +0 -147
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
/*
|
|
2
|
-
LittleJS -
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/*
|
|
6
|
-
LittleJS - Build include file
|
|
7
|
-
By Frank Force 2021
|
|
8
|
-
|
|
9
|
-
This file is used for release builds in place of engineDebug.js
|
|
2
|
+
LittleJS - Release build include file
|
|
3
|
+
- This file is used for release builds in place of engineDebug.js
|
|
4
|
+
- Debug functionality will be disabled to lower size and increase performance
|
|
10
5
|
*/
|
|
11
6
|
|
|
12
|
-
|
|
13
7
|
'use strict';
|
|
14
8
|
|
|
15
9
|
const debug = 0;
|
|
@@ -34,104 +28,422 @@ const debugLine = ()=> {}
|
|
|
34
28
|
const debugAABB = ()=> {}
|
|
35
29
|
const debugClear = ()=> {}
|
|
36
30
|
const debugSaveCanvas = ()=> {}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*/
|
|
31
|
+
/**
|
|
32
|
+
* LittleJS Utility Classes and Functions
|
|
33
|
+
* <br> - General purpose math library
|
|
34
|
+
* <br> - Vector2 - fast, simple, easy 2D vector class
|
|
35
|
+
* <br> - Color - holds a rgba color with some math functions
|
|
36
|
+
* <br> - Timer - tracks time automatically
|
|
37
|
+
* @namespace Utilities
|
|
38
|
+
*/
|
|
45
39
|
|
|
46
40
|
'use strict';
|
|
47
41
|
|
|
42
|
+
/** A shortcut to get Math.PI
|
|
43
|
+
* @const
|
|
44
|
+
* @memberof Utilities */
|
|
45
|
+
const PI = Math.PI;
|
|
46
|
+
|
|
47
|
+
/** True if running a Chromium based browser
|
|
48
|
+
* @const
|
|
49
|
+
* @memberof Utilities */
|
|
50
|
+
const isChrome = window['chrome'];
|
|
51
|
+
|
|
52
|
+
/** Returns absoulte value of value passed in
|
|
53
|
+
* @param {Number} value
|
|
54
|
+
* @return {Number}
|
|
55
|
+
* @memberof Utilities */
|
|
56
|
+
const abs = (a)=> a < 0 ? -a : a;
|
|
57
|
+
|
|
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
|
+
/** Returns lowest of two values passed in
|
|
65
|
+
* @param {Number} valueA
|
|
66
|
+
* @param {Number} valueB
|
|
67
|
+
* @return {Number}
|
|
68
|
+
* @memberof Utilities */
|
|
69
|
+
const min = (a, b)=> a < b ? a : b;
|
|
70
|
+
|
|
71
|
+
/** Returns highest of two values passed in
|
|
72
|
+
* @param {Number} valueA
|
|
73
|
+
* @param {Number} valueB
|
|
74
|
+
* @return {Number}
|
|
75
|
+
* @memberof Utilities */
|
|
76
|
+
const max = (a, b)=> a > b ? a : b;
|
|
77
|
+
|
|
78
|
+
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
79
|
+
* @param {Number} dividend
|
|
80
|
+
* @param {Number} divisor
|
|
81
|
+
* @return {Number}
|
|
82
|
+
* @memberof Utilities */
|
|
83
|
+
const mod = (a, b)=> ((a % b) + b) % b;
|
|
84
|
+
|
|
85
|
+
/** Clamps the value beween max and min
|
|
86
|
+
* @param {Number} value
|
|
87
|
+
* @param {Number} [max=1]
|
|
88
|
+
* @param {Number} [min=0]
|
|
89
|
+
* @return {Number}
|
|
90
|
+
* @memberof Utilities */
|
|
91
|
+
const clamp = (v, max=1, min=0)=> (ASSERT(max > min), v < min ? min : v > max ? max : v);
|
|
92
|
+
|
|
93
|
+
/** Returns what percentage the value is between max and min
|
|
94
|
+
* @param {Number} value
|
|
95
|
+
* @param {Number} [max=1]
|
|
96
|
+
* @param {Number} [min=0]
|
|
97
|
+
* @return {Number}
|
|
98
|
+
* @memberof Utilities */
|
|
99
|
+
const percent = (v, max=1, min=0)=> max-min ? clamp((v-min) / (max-min)) : 0;
|
|
100
|
+
|
|
101
|
+
/** Linearly interpolates the percent value between max and min
|
|
102
|
+
* @param {Number} percent
|
|
103
|
+
* @param {Number} [max=1]
|
|
104
|
+
* @param {Number} [min=0]
|
|
105
|
+
* @return {Number}
|
|
106
|
+
* @memberof Utilities */
|
|
107
|
+
const lerp = (p, max=1, min=0)=> min + clamp(p) * (max-min);
|
|
108
|
+
|
|
109
|
+
/** Formats seconds to mm:ss style for display purposes
|
|
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
|
|
116
|
+
* @param {Number} value
|
|
117
|
+
* @return {Number}
|
|
118
|
+
* @memberof Utilities */
|
|
119
|
+
const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
|
|
120
|
+
|
|
121
|
+
/** Applies smoothstep function to the percentage value
|
|
122
|
+
* @param {Number} value
|
|
123
|
+
* @return {Number}
|
|
124
|
+
* @memberof Utilities */
|
|
125
|
+
const smoothStep = (p)=> p * p * (3 - 2 * p);
|
|
126
|
+
|
|
127
|
+
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
128
|
+
* @param {Vector2} pointA - Center of box A
|
|
129
|
+
* @param {Vector2} sizeA - Size of box A
|
|
130
|
+
* @param {Vector2} pointB - Center of box B
|
|
131
|
+
* @param {Vector2} sizeB - Size of box B
|
|
132
|
+
* @return {Boolean} - True if overlapping
|
|
133
|
+
* @memberof Utilities */
|
|
134
|
+
const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x & abs(pA.y - pB.y)*2 < sA.y + sB.y;
|
|
135
|
+
|
|
136
|
+
/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
|
|
137
|
+
* @param {Number} [frequency=1] - Frequency of the wave in Hz
|
|
138
|
+
* @param {Number} [amplitude=1] - Amplitude (max height) of the wave
|
|
139
|
+
* @param {Number} [t=time] - Value to use for time of the wave
|
|
140
|
+
* @return {Number} - Value waving between 0 and amplitude
|
|
141
|
+
* @memberof Utilities */
|
|
142
|
+
const wave = (frequency=1, amplitude=1, t=time)=> amplitude/2 * (1 - Math.cos(t*frequency*2*PI));
|
|
143
|
+
|
|
48
144
|
///////////////////////////////////////////////////////////////////////////////
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const randSign
|
|
145
|
+
|
|
146
|
+
/** Random global functions
|
|
147
|
+
* @namespace Random */
|
|
148
|
+
|
|
149
|
+
/** Returns a random value between the two values passed in
|
|
150
|
+
* @param {Number} [valueA=1]
|
|
151
|
+
* @param {Number} [valueB=0]
|
|
152
|
+
* @return {Number}
|
|
153
|
+
* @memberof Random */
|
|
154
|
+
const rand = (a=1, b=0)=> b + (a-b)*Math.random();
|
|
155
|
+
|
|
156
|
+
/** Returns a floored random value the two values passed in
|
|
157
|
+
* @param {Number} [valueA=1]
|
|
158
|
+
* @param {Number} [valueB=0]
|
|
159
|
+
* @return {Number}
|
|
160
|
+
* @memberof Random */
|
|
161
|
+
const randInt = (a=1, b=0)=> rand(a,b)|0;
|
|
162
|
+
|
|
163
|
+
/** Randomly returns either -1 or 1
|
|
164
|
+
* @return {Number}
|
|
165
|
+
* @memberof Random */
|
|
166
|
+
const randSign = ()=> (rand(2)|0)*2-1;
|
|
167
|
+
|
|
168
|
+
/** Returns a random Vector2 within a circular shape
|
|
169
|
+
* @param {Number} [radius=1]
|
|
170
|
+
* @param {Number} [minRadius=0]
|
|
171
|
+
* @return {Vector2}
|
|
172
|
+
* @memberof Random */
|
|
71
173
|
const randInCircle = (radius=1, minRadius=0)=> radius > 0 ? randVector(radius * rand(minRadius / radius, 1)**.5) : new Vector2;
|
|
72
|
-
const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
|
|
73
|
-
const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
|
|
74
|
-
linear ? cA.lerp(cB, rand()) : new Color(rand(cA.r,cB.r),rand(cA.g,cB.g),rand(cA.b,cB.b),rand(cA.a,cB.a));
|
|
75
174
|
|
|
76
|
-
|
|
77
|
-
|
|
175
|
+
/** Returns a random Vector2 with the passed in length
|
|
176
|
+
* @param {Number} [length=1]
|
|
177
|
+
* @return {Vector2}
|
|
178
|
+
* @memberof Random */
|
|
179
|
+
const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
|
|
180
|
+
|
|
181
|
+
/** Returns a random color between the two passed in colors, combine components if linear
|
|
182
|
+
* @param {Color} [colorA=new Color(1,1,1,1)]
|
|
183
|
+
* @param {Color} [colorB=new Color(0,0,0,1)]
|
|
184
|
+
* @param {Boolean} [linear]
|
|
185
|
+
* @return {Color}
|
|
186
|
+
* @memberof Random */
|
|
187
|
+
const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
|
|
188
|
+
linear ? cA.lerp(cB, rand()) : new Color(rand(cA.r,cB.r),rand(cA.g,cB.g),rand(cA.b,cB.b),rand(cA.a,cB.a));
|
|
189
|
+
|
|
190
|
+
/** The seed used by the randSeeded function, should not be 0
|
|
191
|
+
* @memberof Random */
|
|
192
|
+
let randSeed = 1;
|
|
193
|
+
|
|
194
|
+
/** Returns a seeded random value between the two values passed in using randSeed
|
|
195
|
+
* @param {Number} [valueA=1]
|
|
196
|
+
* @param {Number} [valueB=0]
|
|
197
|
+
* @return {Number}
|
|
198
|
+
* @memberof Random */
|
|
78
199
|
const randSeeded = (a=1, b=0)=>
|
|
79
200
|
{
|
|
80
|
-
randSeed ^= randSeed << 13; randSeed ^= randSeed >>> 17; randSeed ^= randSeed << 5;
|
|
201
|
+
randSeed ^= randSeed << 13; randSeed ^= randSeed >>> 17; randSeed ^= randSeed << 5; // xorshift
|
|
81
202
|
return b + (a-b)*abs(randSeed % 1e9)/1e9;
|
|
82
203
|
}
|
|
83
204
|
|
|
84
|
-
// create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
|
|
85
|
-
const vec2 = (x=0, y)=> x.x == undefined? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
|
|
86
|
-
|
|
87
205
|
///////////////////////////////////////////////////////////////////////////////
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
|
|
209
|
+
* @param {Number} [x=0]
|
|
210
|
+
* @param {Number} [y=0]
|
|
211
|
+
* @return {Vector2}
|
|
212
|
+
* @example
|
|
213
|
+
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
214
|
+
* let b = vec2(a); // copy a into b
|
|
215
|
+
* a = vec2(5); // set a to (5, 5)
|
|
216
|
+
* b = vec2(); // set b to (0, 0)
|
|
217
|
+
* @memberof Utilities
|
|
218
|
+
*/
|
|
219
|
+
const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 2D Vector object with vector math library
|
|
223
|
+
* @example
|
|
224
|
+
* let a = new Vector2(2, 3); // vector with coordinates (2, 3)
|
|
225
|
+
* let b = new Vector2; // vector with coordinates (0, 0)
|
|
226
|
+
* let c = vec2(4, 2); // use the vec2 function to make a Vector2
|
|
227
|
+
*/
|
|
88
228
|
class Vector2
|
|
89
229
|
{
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
230
|
+
/** Create a 2D vector with the x and y passed in, can also be created with vec2()
|
|
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
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Returns a new vector that is a copy of this
|
|
242
|
+
* @return {Vector2} */
|
|
243
|
+
copy() { return new Vector2(this.x, this.y); }
|
|
244
|
+
|
|
245
|
+
/** Returns a copy of this vector plus the vector passed in
|
|
246
|
+
* @param {Vector2} vector
|
|
247
|
+
* @return {Vector2} */
|
|
248
|
+
add(v) { ASSERT(v.x!=undefined); return new Vector2(this.x + v.x, this.y + v.y); }
|
|
249
|
+
|
|
250
|
+
/** Returns a copy of this vector minus the vector passed in
|
|
251
|
+
* @param {Vector2} vector
|
|
252
|
+
* @return {Vector2} */
|
|
253
|
+
subtract(v) { ASSERT(v.x!=undefined); return new Vector2(this.x - v.x, this.y - v.y); }
|
|
254
|
+
|
|
255
|
+
/** Returns a copy of this vector times the vector passed in
|
|
256
|
+
* @param {Vector2} vector
|
|
257
|
+
* @return {Vector2} */
|
|
258
|
+
multiply(v) { ASSERT(v.x!=undefined); return new Vector2(this.x * v.x, this.y * v.y); }
|
|
259
|
+
|
|
260
|
+
/** Returns a copy of this vector divided by the vector passed in
|
|
261
|
+
* @param {Vector2} vector
|
|
262
|
+
* @return {Vector2} */
|
|
263
|
+
divide(v) { ASSERT(v.x!=undefined); return new Vector2(this.x / v.x, this.y / v.y); }
|
|
264
|
+
|
|
265
|
+
/** Returns a copy of this vector scaled by the vector passed in
|
|
266
|
+
* @param {Number} scale
|
|
267
|
+
* @return {Vector2} */
|
|
268
|
+
scale(s) { ASSERT(s.x==undefined); return new Vector2(this.x * s, this.y * s); }
|
|
269
|
+
|
|
270
|
+
/** Returns the length of this vector
|
|
271
|
+
* @return {Number} */
|
|
272
|
+
length() { return this.lengthSquared()**.5; }
|
|
273
|
+
|
|
274
|
+
/** Returns the length of this vector squared
|
|
275
|
+
* @return {Number} */
|
|
276
|
+
lengthSquared() { return this.x**2 + this.y**2; }
|
|
277
|
+
|
|
278
|
+
/** Returns the distance from this vector to vector passed in
|
|
279
|
+
* @param {Vector2} vector
|
|
280
|
+
* @return {Number} */
|
|
281
|
+
distance(v) { return this.distanceSquared(v)**.5; }
|
|
282
|
+
|
|
283
|
+
/** Returns the distance squared from this vector to vector passed in
|
|
284
|
+
* @param {Vector2} vector
|
|
285
|
+
* @return {Number} */
|
|
286
|
+
distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
|
|
287
|
+
|
|
288
|
+
/** Returns a new vector in same direction as this one with the length passed in
|
|
289
|
+
* @param {Number} [length=1]
|
|
290
|
+
* @return {Vector2} */
|
|
291
|
+
normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(length); }
|
|
292
|
+
|
|
293
|
+
/** Returns a new vector clamped to length passed in
|
|
294
|
+
* @param {Number} [length=1]
|
|
295
|
+
* @return {Vector2} */
|
|
106
296
|
clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
297
|
+
|
|
298
|
+
/** Returns the dot product of this and the vector passed in
|
|
299
|
+
* @param {Vector2} vector
|
|
300
|
+
* @return {Number} */
|
|
301
|
+
dot(v) { ASSERT(v.x!=undefined); return this.x*v.x + this.y*v.y; }
|
|
302
|
+
|
|
303
|
+
/** Returns the cross product of this and the vector passed in
|
|
304
|
+
* @param {Vector2} vector
|
|
305
|
+
* @return {Number} */
|
|
306
|
+
cross(v) { ASSERT(v.x!=undefined); return this.x*v.y - this.y*v.x; }
|
|
307
|
+
|
|
308
|
+
/** Returns the angle of this vector, up is angle 0
|
|
309
|
+
* @return {Number} */
|
|
310
|
+
angle() { return Math.atan2(this.x, this.y); }
|
|
311
|
+
|
|
312
|
+
/** Sets this vector with angle and length passed in
|
|
313
|
+
* @param {Number} [angle=0]
|
|
314
|
+
* @param {Number} [length=1] */
|
|
315
|
+
setAngle(a=0, length=1) { this.x = length*Math.sin(a); this.y = length*Math.cos(a); return this; }
|
|
316
|
+
|
|
317
|
+
/** Returns copy of this vector rotated by the angle passed in
|
|
318
|
+
* @param {Number} angle
|
|
319
|
+
* @return {Vector2} */
|
|
320
|
+
rotate(a) { const c = Math.cos(a), s = Math.sin(a); return new Vector2(this.x*c-this.y*s, this.x*s+this.y*c); }
|
|
321
|
+
|
|
322
|
+
/** Returns the integer direction of this vector, corrosponding to multiples of 90 degree rotation (0-3)
|
|
323
|
+
* @return {Number} */
|
|
324
|
+
direction() { return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
|
|
325
|
+
|
|
326
|
+
/** Returns a copy of this vector that has been inverted
|
|
327
|
+
* @return {Vector2} */
|
|
328
|
+
invert() { return new Vector2(this.y, -this.x); }
|
|
329
|
+
|
|
330
|
+
/** Returns a copy of this vector with the axies flipped
|
|
331
|
+
* @return {Vector2} */
|
|
332
|
+
flip() { return new Vector2(this.y, this.x); }
|
|
333
|
+
|
|
334
|
+
/** Returns a copy of this vector with each axis floored
|
|
335
|
+
* @return {Vector2} */
|
|
336
|
+
floor() { return new Vector2(Math.floor(this.x), Math.floor(this.y)); }
|
|
337
|
+
|
|
338
|
+
/** Returns the area this vector covers as a rectangle
|
|
339
|
+
* @return {Number} */
|
|
340
|
+
area() { return this.x * this.y; }
|
|
341
|
+
|
|
342
|
+
/** Returns a new vector that is p percent between this and the vector passed in
|
|
343
|
+
* @param {Vector2} vector
|
|
344
|
+
* @param {Number} percent
|
|
345
|
+
* @return {Vector2} */
|
|
346
|
+
lerp(v, p) { ASSERT(v.x!=undefined); return this.add(v.subtract(this).scale(clamp(p))); }
|
|
347
|
+
|
|
348
|
+
/** Returns true if this vector is within the bounds of an array size passed in
|
|
349
|
+
* @param {Vector2} arraySize
|
|
350
|
+
* @return {Boolean} */
|
|
120
351
|
arrayCheck(arraySize) { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
|
|
121
352
|
}
|
|
122
353
|
|
|
123
354
|
///////////////////////////////////////////////////////////////////////////////
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Color object (red, green, blue, alpha) with some helpful functions
|
|
358
|
+
* @example
|
|
359
|
+
* let a = new Color; // white
|
|
360
|
+
* let b = new Color(1, 0, 0); // red
|
|
361
|
+
* let c = new Color(0, 0, 0, 0); // transparent black
|
|
362
|
+
*/
|
|
124
363
|
class Color
|
|
125
364
|
{
|
|
126
|
-
|
|
365
|
+
/** Create a color with the components passed in, white by default
|
|
366
|
+
* @param {Number} [red=1]
|
|
367
|
+
* @param {Number} [green=1]
|
|
368
|
+
* @param {Number} [blue=1]
|
|
369
|
+
* @param {Number} [alpha=1] */
|
|
370
|
+
constructor(r=1, g=1, b=1, a=1)
|
|
371
|
+
{
|
|
372
|
+
/** @property {Number} - Red */
|
|
373
|
+
this.r = r;
|
|
374
|
+
/** @property {Number} - Green */
|
|
375
|
+
this.g = g;
|
|
376
|
+
/** @property {Number} - Blue */
|
|
377
|
+
this.b = b;
|
|
378
|
+
/** @property {Number} - Alpha */
|
|
379
|
+
this.a = a;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** Returns a new color that is a copy of this
|
|
383
|
+
* @return {Color} */
|
|
384
|
+
copy() { return new Color(this.r, this.g, this.b, this.a); }
|
|
385
|
+
|
|
386
|
+
/** Returns a copy of this color plus the color passed in
|
|
387
|
+
* @param {Color} color
|
|
388
|
+
* @return {Color} */
|
|
389
|
+
add(c) { return new Color(this.r+c.r, this.g+c.g, this.b+c.b, this.a+c.a); }
|
|
127
390
|
|
|
128
|
-
|
|
129
|
-
|
|
391
|
+
/** Returns a copy of this color minus the color passed in
|
|
392
|
+
* @param {Color} color
|
|
393
|
+
* @return {Color} */
|
|
130
394
|
subtract(c) { return new Color(this.r-c.r, this.g-c.g, this.b-c.b, this.a-c.a); }
|
|
395
|
+
|
|
396
|
+
/** Returns a copy of this color times the color passed in
|
|
397
|
+
* @param {Color} color
|
|
398
|
+
* @return {Color} */
|
|
131
399
|
multiply(c) { return new Color(this.r*c.r, this.g*c.g, this.b*c.b, this.a*c.a); }
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
400
|
+
|
|
401
|
+
/** Returns a copy of this color divided by the color passed in
|
|
402
|
+
* @param {Color} color
|
|
403
|
+
* @return {Color} */
|
|
404
|
+
divide(c) { return new Color(this.r/c.r, this.g/c.g, this.b/c.b, this.a/c.a); }
|
|
405
|
+
|
|
406
|
+
/** Returns a copy of this color scaled by the value passed in, alpha can be scaled separately
|
|
407
|
+
* @param {Number} scale
|
|
408
|
+
* @param {Number} [alphaScale=scale]
|
|
409
|
+
* @return {Color} */
|
|
410
|
+
scale(s, a=s) { return new Color(this.r*s, this.g*s, this.b*s, this.a*a); }
|
|
411
|
+
|
|
412
|
+
/** Returns a copy of this color clamped to the valid range between 0 and 1
|
|
413
|
+
* @return {Color} */
|
|
414
|
+
clamp() { return new Color(clamp(this.r), clamp(this.g), clamp(this.b), clamp(this.a)); }
|
|
415
|
+
|
|
416
|
+
/** Returns a new color that is p percent between this and the color passed in
|
|
417
|
+
* @param {Color} color
|
|
418
|
+
* @param {Number} percent
|
|
419
|
+
* @return {Color} */
|
|
420
|
+
lerp(c, p) { return this.add(c.subtract(this).scale(clamp(p))); }
|
|
421
|
+
|
|
422
|
+
/** Sets this color given a hue, saturation, lightness , and alpha
|
|
423
|
+
* @param {Number} [hue=0]
|
|
424
|
+
* @param {Number} [saturation=0]
|
|
425
|
+
* @param {Number} [lightness=1]
|
|
426
|
+
* @param {Number} [alpha=1]
|
|
427
|
+
* @return {Color} */
|
|
428
|
+
setHSLA(h=0, s=0, l=1, a=1)
|
|
429
|
+
{
|
|
430
|
+
const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
|
|
431
|
+
f = (p, q, t)=>
|
|
432
|
+
(t = ((t%1)+1)%1) < 1/6 ? p+(q-p)*6*t :
|
|
433
|
+
t < 1/2 ? q :
|
|
434
|
+
t < 2/3 ? p+(q-p)*(2/3-t)*6 : p;
|
|
435
|
+
|
|
436
|
+
this.r = f(p, q, h + 1/3);
|
|
437
|
+
this.g = f(p, q, h);
|
|
438
|
+
this.b = f(p, q, h - 1/3);
|
|
439
|
+
this.a = a;
|
|
440
|
+
return this;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Returns a new color that has each component randomly adjusted
|
|
444
|
+
* @param {Number} [amount=.05]
|
|
445
|
+
* @param {Number} [alphaAmount=0]
|
|
446
|
+
* @return {Color} */
|
|
135
447
|
mutate(amount=.05, alphaAmount=0)
|
|
136
448
|
{
|
|
137
449
|
return new Color
|
|
@@ -142,117 +454,264 @@ class Color
|
|
|
142
454
|
this.a + rand(alphaAmount, -alphaAmount)
|
|
143
455
|
).clamp();
|
|
144
456
|
}
|
|
457
|
+
|
|
458
|
+
/** Returns this color expressed as an rgba string
|
|
459
|
+
* @return {String} */
|
|
145
460
|
rgba()
|
|
146
461
|
{
|
|
147
462
|
ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
|
|
148
463
|
return `rgb(${this.r*255|0},${this.g*255|0},${this.b*255|0},${this.a})`;
|
|
149
464
|
}
|
|
465
|
+
|
|
466
|
+
/** Returns this color expressed as 32 bit integer value
|
|
467
|
+
* @return {Number} */
|
|
150
468
|
rgbaInt()
|
|
151
469
|
{
|
|
152
470
|
ASSERT(this.r>=0 && this.r<=1 && this.g>=0 && this.g<=1 && this.b>=0 && this.b<=1 && this.a>=0 && this.a<=1);
|
|
153
471
|
return (this.r*255|0) + (this.g*255<<8) + (this.b*255<<16) + (this.a*255<<24);
|
|
154
472
|
}
|
|
155
|
-
setHSLA(h=0, s=0, l=1, a=1)
|
|
156
|
-
{
|
|
157
|
-
const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
|
|
158
|
-
f = (p, q, t)=>
|
|
159
|
-
(t = ((t%1)+1)%1) < 1/6 ? p+(q-p)*6*t :
|
|
160
|
-
t < 1/2 ? q :
|
|
161
|
-
t < 2/3 ? p+(q-p)*(2/3-t)*6 : p;
|
|
162
|
-
|
|
163
|
-
this.r = f(p, q, h + 1/3);
|
|
164
|
-
this.g = f(p, q, h);
|
|
165
|
-
this.b = f(p, q, h - 1/3);
|
|
166
|
-
this.a = a;
|
|
167
|
-
return this;
|
|
168
|
-
}
|
|
169
473
|
}
|
|
170
474
|
|
|
171
475
|
///////////////////////////////////////////////////////////////////////////////
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Timer object tracks how long has passed since it was set
|
|
479
|
+
* @example
|
|
480
|
+
* let a = new Timer; // creates a timer that is not set
|
|
481
|
+
* a.set(3); // sets the timer to 3 seconds
|
|
482
|
+
*
|
|
483
|
+
* let b = new Timer(1); // creates a timer with 1 second left
|
|
484
|
+
* b.unset(); // unsets the timer
|
|
485
|
+
*/
|
|
172
486
|
class Timer
|
|
173
487
|
{
|
|
174
|
-
|
|
488
|
+
/** Create a timer object set time passed in
|
|
489
|
+
* @param {Number} [timeLeft] - How much time left before the timer elapses in seconds */
|
|
490
|
+
constructor(timeLeft) { this.time = timeLeft == undefined ? undefined : time + timeLeft; this.setTime = timeLeft; }
|
|
175
491
|
|
|
492
|
+
/** Set the timer with seconds passed in
|
|
493
|
+
* @param {Number} [timeLeft=0] - How much time left before the timer is elapsed in seconds */
|
|
176
494
|
set(timeLeft=0) { this.time = time + timeLeft; this.setTime = timeLeft; }
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
495
|
+
|
|
496
|
+
/** Unset the timer */
|
|
497
|
+
unset() { this.time = undefined; }
|
|
498
|
+
|
|
499
|
+
/** Returns true if set
|
|
500
|
+
* @return {Boolean} */
|
|
501
|
+
isSet() { return this.time != undefined; }
|
|
502
|
+
|
|
503
|
+
/** Returns true if set and has not elapsed
|
|
504
|
+
* @return {Boolean} */
|
|
505
|
+
active() { return time <= this.time; }
|
|
506
|
+
|
|
507
|
+
/** Returns true if set and elapsed
|
|
508
|
+
* @return {Boolean} */
|
|
509
|
+
elapsed() { return time > this.time; }
|
|
510
|
+
|
|
511
|
+
/** Get how long since elapsed, returns 0 if not set
|
|
512
|
+
* @return {Number} */
|
|
513
|
+
get() { return this.isSet()? time - this.time : 0; }
|
|
514
|
+
|
|
515
|
+
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
516
|
+
* @return {Number} */
|
|
517
|
+
getPercent() { return this.isSet()? percent(this.time - time, 0, this.setTime) : 0; }
|
|
183
518
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
519
|
+
/**
|
|
520
|
+
* LittleJS Engine Settings
|
|
521
|
+
* @namespace Settings
|
|
522
|
+
*/
|
|
187
523
|
|
|
188
|
-
|
|
189
|
-
// display settings
|
|
524
|
+
'use strict';
|
|
190
525
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
526
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
527
|
+
// Display settings
|
|
528
|
+
|
|
529
|
+
/** The max size of the canvas, centered if window is larger
|
|
530
|
+
* @type {Vector2}
|
|
531
|
+
* @default
|
|
532
|
+
* @memberof Settings */
|
|
533
|
+
const maxSize = vec2(1920, 1200);
|
|
534
|
+
|
|
535
|
+
/** Fixed size of the canvas, if enabled cavnvas size never changes
|
|
536
|
+
* @type {Vector2}
|
|
537
|
+
* @default
|
|
538
|
+
* @memberof Settings */
|
|
539
|
+
let fixedSize = vec2();
|
|
540
|
+
|
|
541
|
+
/** Default font used for text rendering
|
|
542
|
+
* @default
|
|
543
|
+
* @memberof Settings */
|
|
544
|
+
let fontDefault = 'arial';
|
|
545
|
+
|
|
546
|
+
/** Use crisp pixels for pixel art if true
|
|
547
|
+
* @default
|
|
548
|
+
* @memberof Settings */
|
|
549
|
+
let pixelated = 1;
|
|
199
550
|
|
|
200
551
|
///////////////////////////////////////////////////////////////////////////////
|
|
201
|
-
//
|
|
552
|
+
// Tile sheet settings
|
|
202
553
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
554
|
+
/** Default size of tiles in pixels
|
|
555
|
+
* @type {Vector2}
|
|
556
|
+
* @default
|
|
557
|
+
* @memberof Settings */
|
|
558
|
+
const tileSizeDefault = vec2(16);
|
|
206
559
|
|
|
207
|
-
|
|
208
|
-
|
|
560
|
+
/** Prevent tile bleeding from neighbors in pixels
|
|
561
|
+
* @default
|
|
562
|
+
* @memberof Settings */
|
|
563
|
+
const tileBleedShrinkFix = .3;
|
|
209
564
|
|
|
210
|
-
|
|
211
|
-
|
|
565
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
566
|
+
// Object settings
|
|
567
|
+
|
|
568
|
+
/** Default size of objects
|
|
569
|
+
* @type {Vector2}
|
|
570
|
+
* @default
|
|
571
|
+
* @memberof Settings */
|
|
572
|
+
const objectDefaultSize = vec2(1);
|
|
573
|
+
|
|
574
|
+
/** Default object mass for collison calcuations (how heavy objects are)
|
|
575
|
+
* @default
|
|
576
|
+
* @memberof Settings */
|
|
577
|
+
const objectDefaultMass = 1;
|
|
578
|
+
|
|
579
|
+
/** How much to slow velocity by each frame (0-1)
|
|
580
|
+
* @default
|
|
581
|
+
* @memberof Settings */
|
|
582
|
+
const objectDefaultDamping = .99;
|
|
583
|
+
|
|
584
|
+
/** How much to slow angular velocity each frame (0-1)
|
|
585
|
+
* @default
|
|
586
|
+
* @memberof Settings */
|
|
587
|
+
const objectDefaultAngleDamping = .99;
|
|
588
|
+
|
|
589
|
+
/** How much to bounce when a collision occurs (0-1)
|
|
590
|
+
* @default
|
|
591
|
+
* @memberof Settings */
|
|
592
|
+
const objectDefaultElasticity = 0;
|
|
593
|
+
|
|
594
|
+
/** How much to slow when touching (0-1)
|
|
595
|
+
* @default
|
|
596
|
+
* @memberof Settings */
|
|
597
|
+
const objectDefaultFriction = .8;
|
|
598
|
+
|
|
599
|
+
/** Clamp max speed to avoid fast objects missing collisions
|
|
600
|
+
* @default
|
|
601
|
+
* @memberof Settings */
|
|
602
|
+
const objectMaxSpeed = 1;
|
|
603
|
+
|
|
604
|
+
/** How much gravity to apply to objects along the Y axis, negative is down
|
|
605
|
+
* @default
|
|
606
|
+
* @memberof Settings */
|
|
607
|
+
let gravity = 0;
|
|
212
608
|
|
|
213
609
|
///////////////////////////////////////////////////////////////////////////////
|
|
214
|
-
//
|
|
610
|
+
// Camera settings
|
|
611
|
+
|
|
612
|
+
/** Position of camera in world space
|
|
613
|
+
* @type {Vector2}
|
|
614
|
+
* @default
|
|
615
|
+
* @memberof Settings */
|
|
616
|
+
let cameraPos = vec2();
|
|
215
617
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const defaultObjectElasticity = 0; // how much to bounce 0-1
|
|
221
|
-
const defaultObjectFriction = .8; // how much to slow when touching 0-1
|
|
222
|
-
const maxObjectSpeed = 1; // camp max speed to avoid fast objects missing collisions
|
|
618
|
+
/** Scale of camera in world space
|
|
619
|
+
* @default
|
|
620
|
+
* @memberof Settings */
|
|
621
|
+
let cameraScale = max(tileSizeDefault.x, tileSizeDefault.y);
|
|
223
622
|
|
|
224
623
|
///////////////////////////////////////////////////////////////////////////////
|
|
225
|
-
//
|
|
624
|
+
// WebGL settings
|
|
226
625
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const
|
|
626
|
+
/** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
|
|
627
|
+
* @default
|
|
628
|
+
* @memberof Settings */
|
|
629
|
+
const glEnable = 1;
|
|
630
|
+
|
|
631
|
+
/** Fixes slow rendering in some browsers by not compositing the WebGL canvas
|
|
632
|
+
* @default
|
|
633
|
+
* @memberof Settings */
|
|
634
|
+
let glOverlay = 1;
|
|
231
635
|
|
|
232
636
|
///////////////////////////////////////////////////////////////////////////////
|
|
233
|
-
//
|
|
637
|
+
// Input settings
|
|
638
|
+
|
|
639
|
+
/** Should gamepads be allowed
|
|
640
|
+
* @default
|
|
641
|
+
* @memberof Settings */
|
|
642
|
+
const gamepadsEnable = 1;
|
|
234
643
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const
|
|
644
|
+
/** If true, the dpad input is also routed to the left analog stick (for better accessability)
|
|
645
|
+
* @default
|
|
646
|
+
* @memberof Settings */
|
|
647
|
+
const gamepadDirectionEmulateStick = 1;
|
|
648
|
+
|
|
649
|
+
/** If true touch input is routed to mouse functions
|
|
650
|
+
* @default
|
|
651
|
+
* @memberof Settings */
|
|
652
|
+
const inputTouchEnable = 1;
|
|
653
|
+
|
|
654
|
+
/** If true the WASD keys are also routed to the direction keys (for better accessability)
|
|
655
|
+
* @default
|
|
656
|
+
* @memberof Settings */
|
|
657
|
+
const inputWASDEmulateDirection = 1;
|
|
239
658
|
|
|
240
659
|
///////////////////////////////////////////////////////////////////////////////
|
|
241
|
-
//
|
|
660
|
+
// Audio settings
|
|
661
|
+
|
|
662
|
+
/** Volume scale to apply to all sound, music and speech
|
|
663
|
+
* @default
|
|
664
|
+
* @memberof Settings */
|
|
665
|
+
let soundVolume = .5;
|
|
666
|
+
|
|
667
|
+
/** All audio code can be disabled and removed from build
|
|
668
|
+
* @default
|
|
669
|
+
* @memberof Settings */
|
|
670
|
+
const soundEnable = 1;
|
|
242
671
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
672
|
+
/** Default range where sound no longer plays
|
|
673
|
+
* @default
|
|
674
|
+
* @memberof Settings */
|
|
675
|
+
const soundDefaultRange = 30;
|
|
676
|
+
|
|
677
|
+
/** Default range percent to start tapering off sound (0-1)
|
|
678
|
+
* @default
|
|
679
|
+
* @memberof Settings */
|
|
680
|
+
const soundDefaultTaper = .7;
|
|
681
|
+
|
|
682
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
683
|
+
// Medals settings
|
|
684
|
+
|
|
685
|
+
/** How long to show medals for in seconds
|
|
686
|
+
* @default
|
|
687
|
+
* @memberof Settings */
|
|
688
|
+
const medalDisplayTime = 5;
|
|
689
|
+
|
|
690
|
+
/** How quickly to slide on/off medals in seconds
|
|
691
|
+
* @default
|
|
692
|
+
* @memberof Settings */
|
|
693
|
+
const medalDisplaySlideTime = .5;
|
|
694
|
+
|
|
695
|
+
/** Width of medal display
|
|
696
|
+
* @default
|
|
697
|
+
* @memberof Settings */
|
|
698
|
+
const medalDisplayWidth = 640;
|
|
699
|
+
|
|
700
|
+
/** Height of medal display
|
|
701
|
+
* @default
|
|
702
|
+
* @memberof Settings */
|
|
703
|
+
const medalDisplayHeight = 99;
|
|
704
|
+
|
|
705
|
+
/** Size of icon in medal display
|
|
706
|
+
* @default
|
|
707
|
+
* @memberof Settings */
|
|
708
|
+
const medalDisplayIconSize = 80;
|
|
248
709
|
/*
|
|
249
|
-
LittleJS - The Tiny JavaScript Game Engine That Can
|
|
250
|
-
MIT License - Copyright
|
|
710
|
+
LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
711
|
+
MIT License - Copyright 2021 Frank Force
|
|
251
712
|
|
|
252
713
|
Engine Features
|
|
253
|
-
-
|
|
254
|
-
- Object oriented with base class engine object
|
|
255
|
-
- Engine handles core update loop
|
|
714
|
+
- Object oriented system with base class engine object
|
|
256
715
|
- Base class object handles update, physics, collision, rendering, etc
|
|
257
716
|
- Engine helper classes and functions like Vector2, Color, and Timer
|
|
258
717
|
- Super fast rendering system for tile sheets
|
|
@@ -260,30 +719,63 @@ const medalDisplayIconSize = 80; // size of icon in medal display
|
|
|
260
719
|
- Input processing system with gamepad and touchscreen support
|
|
261
720
|
- Tile layer rendering and collision system
|
|
262
721
|
- Particle effect system
|
|
263
|
-
-
|
|
722
|
+
- Medal system tracks and displays achievements
|
|
264
723
|
- Debug tools and debug rendering system
|
|
265
724
|
- Call engineInit() to start it up!
|
|
266
725
|
*/
|
|
267
726
|
|
|
268
727
|
'use strict';
|
|
269
728
|
|
|
729
|
+
/** Name of engine */
|
|
270
730
|
const engineName = 'LittleJS';
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
731
|
+
|
|
732
|
+
/** Version of engine */
|
|
733
|
+
const engineVersion = '1.1.8';
|
|
734
|
+
|
|
735
|
+
/** Frames per second to update objects
|
|
736
|
+
* @default */
|
|
737
|
+
const FPS = 60;
|
|
738
|
+
|
|
739
|
+
/** How many seconds each frame lasts, engine uses a fixed time step
|
|
740
|
+
* @default 1/60 */
|
|
741
|
+
const timeDelta = 1/FPS;
|
|
742
|
+
|
|
743
|
+
/** Array containing all engine objects */
|
|
744
|
+
let engineObjects = [];
|
|
745
|
+
|
|
746
|
+
/** Array containing only objects that are set to collide with other objects (for optimization) */
|
|
747
|
+
let engineCollideObjects = [];
|
|
748
|
+
|
|
749
|
+
/** Current update frame, used to calculate time */
|
|
750
|
+
let frame = 0;
|
|
751
|
+
|
|
752
|
+
/** Current engine time since start in seconds, derived from frame */
|
|
753
|
+
let time = 0;
|
|
754
|
+
|
|
755
|
+
/** Actual clock time since start in seconds (not affected by pause or frame rate clamping) */
|
|
756
|
+
let timeReal = 0;
|
|
757
|
+
|
|
758
|
+
/** Is the game paused? Causes time and objects to not be updated. */
|
|
759
|
+
let paused = 0;
|
|
760
|
+
|
|
761
|
+
// Engine internal variables not exposed to documentation
|
|
762
|
+
let frameTimeLastMS = 0, frameTimeBufferMS = 0, debugFPS = 0,
|
|
763
|
+
shrinkTilesX, shrinkTilesY, drawCount, tileImageSize, tileImageSizeInverse;
|
|
764
|
+
|
|
765
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
766
|
+
|
|
767
|
+
/** Start up LittleJS engine with your callback functions
|
|
768
|
+
* @param {Function} gameInit - Called once after the engine starts up, setup the game
|
|
769
|
+
* @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
|
|
770
|
+
* @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
|
|
771
|
+
* @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
|
|
772
|
+
* @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
|
|
773
|
+
* @param {String} [tileImageSource] - Tile image to use, everything starts when the image is finished loading
|
|
774
|
+
*/
|
|
283
775
|
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, tileImageSource)
|
|
284
776
|
{
|
|
285
|
-
// init engine when tiles load
|
|
286
|
-
tileImage.onload = ()=>
|
|
777
|
+
// init engine when tiles load or fail to load
|
|
778
|
+
tileImage.onerror = tileImage.onload = ()=>
|
|
287
779
|
{
|
|
288
780
|
// save tile image info
|
|
289
781
|
tileImageSizeInverse = vec2(1).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
@@ -323,7 +815,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
323
815
|
debugFPS = lerp(.05, 1e3/(frameTimeDeltaMS||1), debugFPS);
|
|
324
816
|
if (debug)
|
|
325
817
|
frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
|
|
326
|
-
|
|
818
|
+
timeReal += frameTimeDeltaMS / 1e3;
|
|
327
819
|
frameTimeBufferMS = min(frameTimeBufferMS + !paused * frameTimeDeltaMS, 50); // clamp incase of slow framerate
|
|
328
820
|
|
|
329
821
|
if (paused)
|
|
@@ -351,7 +843,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
351
843
|
// update game and objects
|
|
352
844
|
inputUpdate();
|
|
353
845
|
gameUpdate();
|
|
354
|
-
|
|
846
|
+
engineObjectsUpdate();
|
|
355
847
|
|
|
356
848
|
// do post update
|
|
357
849
|
debugUpdate();
|
|
@@ -363,31 +855,28 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
363
855
|
frameTimeBufferMS += deltaSmooth;
|
|
364
856
|
}
|
|
365
857
|
|
|
366
|
-
if (
|
|
858
|
+
if (fixedSize.x)
|
|
367
859
|
{
|
|
368
860
|
// clear set fixed size
|
|
369
|
-
mainCanvas.width =
|
|
370
|
-
mainCanvas.height =
|
|
861
|
+
mainCanvas.width = fixedSize.x;
|
|
862
|
+
mainCanvas.height = fixedSize.y;
|
|
371
863
|
|
|
372
|
-
if
|
|
864
|
+
// fit to window by adding space on top or bottom if necessary
|
|
865
|
+
const aspect = innerWidth / innerHeight;
|
|
866
|
+
const fixedAspect = mainCanvas.width / mainCanvas.height;
|
|
867
|
+
mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
|
|
868
|
+
mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
869
|
+
if (glCanvas)
|
|
373
870
|
{
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const fixedAspect = fixedWidth / fixedHeight;
|
|
377
|
-
mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
|
|
378
|
-
mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
379
|
-
if (glCanvas)
|
|
380
|
-
{
|
|
381
|
-
glCanvas.style.width = mainCanvas.style.width;
|
|
382
|
-
glCanvas.style.height = mainCanvas.style.height;
|
|
383
|
-
}
|
|
871
|
+
glCanvas.style.width = mainCanvas.style.width;
|
|
872
|
+
glCanvas.style.height = mainCanvas.style.height;
|
|
384
873
|
}
|
|
385
874
|
}
|
|
386
875
|
else
|
|
387
876
|
{
|
|
388
877
|
// clear and set size to same as window
|
|
389
|
-
mainCanvas.width = min(innerWidth,
|
|
390
|
-
mainCanvas.height = min(innerHeight,
|
|
878
|
+
mainCanvas.width = min(innerWidth, maxSize.x);
|
|
879
|
+
mainCanvas.height = min(innerHeight, maxSize.y);
|
|
391
880
|
}
|
|
392
881
|
|
|
393
882
|
// save canvas size and clear overlay canvas
|
|
@@ -425,7 +914,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
425
914
|
tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
|
|
426
915
|
}
|
|
427
916
|
|
|
428
|
-
|
|
917
|
+
|
|
918
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
919
|
+
|
|
920
|
+
/** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
|
|
921
|
+
function engineObjectsUpdate()
|
|
429
922
|
{
|
|
430
923
|
// recursive object update
|
|
431
924
|
const updateObject = (o)=>
|
|
@@ -447,52 +940,125 @@ function engineUpdateObjects()
|
|
|
447
940
|
// increment frame and update time
|
|
448
941
|
time = ++frame / FPS;
|
|
449
942
|
}
|
|
943
|
+
|
|
944
|
+
/** Detroy and remove all objects that are not persistent or descendants of a persistent object */
|
|
945
|
+
function engineObjectsDestroy()
|
|
946
|
+
{
|
|
947
|
+
for (const o of engineObjects)
|
|
948
|
+
o.persistent || o.parent || o.destroy();
|
|
949
|
+
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/** Triggers a callback for each object within a given area
|
|
953
|
+
* @param {Vector2} [pos] - Center of test area
|
|
954
|
+
* @param {Number} [size] - Radius of circle if float, rectangle size if Vector2
|
|
955
|
+
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
956
|
+
* @param {Array} [objects=engineObjects] - List of objects to check */
|
|
957
|
+
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
958
|
+
{
|
|
959
|
+
if (!pos)
|
|
960
|
+
{
|
|
961
|
+
// all objects
|
|
962
|
+
for (const o of objects)
|
|
963
|
+
callbackFunction(o);
|
|
964
|
+
}
|
|
965
|
+
else if (size.x != undefined)
|
|
966
|
+
{
|
|
967
|
+
// aabb test
|
|
968
|
+
for (const o of objects)
|
|
969
|
+
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
970
|
+
}
|
|
971
|
+
else
|
|
972
|
+
{
|
|
973
|
+
// circle test
|
|
974
|
+
const sizeSquared = size*size;
|
|
975
|
+
for (const o of objects)
|
|
976
|
+
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
450
979
|
/*
|
|
451
|
-
LittleJS Object
|
|
452
|
-
- Base object class used by the engine
|
|
453
|
-
- Automatically adds self to object list
|
|
454
|
-
- Will be updated and rendered each frame
|
|
455
|
-
- Renders as a sprite from a tilesheet by default
|
|
456
|
-
- Can have color and addtive color applied
|
|
457
|
-
- 2d Physics and collision system
|
|
458
|
-
- Sorted by renderOrder
|
|
459
|
-
- Objects can have children attached
|
|
460
|
-
- Parents are updated before children, and set child transform
|
|
461
|
-
- Call destroy() to get rid of objects
|
|
980
|
+
LittleJS Object System
|
|
462
981
|
*/
|
|
463
982
|
|
|
464
983
|
'use strict';
|
|
465
984
|
|
|
985
|
+
/**
|
|
986
|
+
* LittleJS Object Base Object Class
|
|
987
|
+
* <br> - Base object class used by the engine
|
|
988
|
+
* <br> - Automatically adds self to object list
|
|
989
|
+
* <br> - Will be updated and rendered each frame
|
|
990
|
+
* <br> - Renders as a sprite from a tilesheet by default
|
|
991
|
+
* <br> - Can have color and addtive color applied
|
|
992
|
+
* <br> - 2d Physics and collision system
|
|
993
|
+
* <br> - Sorted by renderOrder
|
|
994
|
+
* <br> - Objects can have children attached
|
|
995
|
+
* <br> - Parents are updated before children, and set child transform
|
|
996
|
+
* <br> - Call destroy() to get rid of objects
|
|
997
|
+
* @example
|
|
998
|
+
* // create an engine object, normally you would first extend the class with your own
|
|
999
|
+
* const pos = vec2(2,3);
|
|
1000
|
+
* const object = new EngineObject(pos);
|
|
1001
|
+
*/
|
|
466
1002
|
class EngineObject
|
|
467
1003
|
{
|
|
468
|
-
|
|
1004
|
+
/** Create an engine object and adds it to the list of objects
|
|
1005
|
+
* @param {Vector2} [position=new Vector2(0,0)] - World space position of the object
|
|
1006
|
+
* @param {Vector2} [size=objectDefaultSize] - World space size of the object
|
|
1007
|
+
* @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
|
|
1008
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
1009
|
+
* @param {Number} [angle=0] - Angle to rotate the object
|
|
1010
|
+
* @param {Color} [color] - Color to apply to tile when rendered
|
|
1011
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
1012
|
+
*/
|
|
1013
|
+
constructor(pos=vec2(), size=objectDefaultSize, tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
|
|
469
1014
|
{
|
|
470
1015
|
// set passed in params
|
|
471
1016
|
ASSERT(pos && pos.x != undefined && size.x != undefined); // ensure pos and size are vec2s
|
|
1017
|
+
|
|
1018
|
+
/** @property {Vector2} - World space position of the object */
|
|
472
1019
|
this.pos = pos.copy();
|
|
1020
|
+
/** @property {Vector2} - World space width and height of the object */
|
|
473
1021
|
this.size = size;
|
|
1022
|
+
/** @property {Vector2} - Size of object used for drawing, uses size if not set */
|
|
1023
|
+
this.drawSize;
|
|
1024
|
+
/** @property {Number} - Tile to use to render object, untextured if -1 */
|
|
474
1025
|
this.tileIndex = tileIndex;
|
|
1026
|
+
/** @property {Vector2} - Size of tile in source pixels */
|
|
475
1027
|
this.tileSize = tileSize;
|
|
1028
|
+
/** @property {Number} - Angle to rotate the object */
|
|
476
1029
|
this.angle = angle;
|
|
1030
|
+
/** @property {Color} - Color to apply when rendered */
|
|
477
1031
|
this.color = color;
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
this.
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
1032
|
+
/** @property {Color} - Additive color to apply when rendered */
|
|
1033
|
+
this.additiveColor;
|
|
1034
|
+
|
|
1035
|
+
// set object defaults
|
|
1036
|
+
/** @property {Number} [mass=objectDefaultMass] - How heavy the object is */
|
|
1037
|
+
this.mass = objectDefaultMass;
|
|
1038
|
+
/** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
|
|
1039
|
+
this.damping = objectDefaultDamping;
|
|
1040
|
+
/** @property {Number} [angleDamping=objectDefaultAngleDamping] - How much to slow down rotation each frame (0-1) */
|
|
1041
|
+
this.angleDamping = objectDefaultAngleDamping;
|
|
1042
|
+
/** @property {Number} [elasticity=objectDefaultElasticity] - How bouncy the object is when colliding (0-1) */
|
|
1043
|
+
this.elasticity = objectDefaultElasticity;
|
|
1044
|
+
/** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
|
|
1045
|
+
this.friction = objectDefaultFriction;
|
|
1046
|
+
/** @property {Number} [gravityScale=1] - How much to scale gravity by for this object */
|
|
1047
|
+
this.gravityScale = 1;
|
|
1048
|
+
/** @property {Number} [renderOrder=0] - Objects are sorted by render order */
|
|
1049
|
+
this.renderOrder = renderOrder;
|
|
1050
|
+
|
|
1051
|
+
// init other internal object stuff
|
|
487
1052
|
this.spawnTime = time;
|
|
488
|
-
this.velocity = vec2(this.collideSolidObjects = this.
|
|
489
|
-
this.collideTiles =
|
|
1053
|
+
this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
|
|
1054
|
+
this.collideTiles = 1;
|
|
490
1055
|
this.children = [];
|
|
491
1056
|
|
|
492
1057
|
// add to list of objects
|
|
493
1058
|
engineObjects.push(this);
|
|
494
1059
|
}
|
|
495
1060
|
|
|
1061
|
+
/** Update the object transform and physics, called automatically by engine once each frame */
|
|
496
1062
|
update()
|
|
497
1063
|
{
|
|
498
1064
|
const parent = this.parent;
|
|
@@ -505,8 +1071,8 @@ class EngineObject
|
|
|
505
1071
|
}
|
|
506
1072
|
|
|
507
1073
|
// limit max speed to prevent missing collisions
|
|
508
|
-
this.velocity.x = clamp(this.velocity.x,
|
|
509
|
-
this.velocity.y = clamp(this.velocity.y,
|
|
1074
|
+
this.velocity.x = clamp(this.velocity.x, objectMaxSpeed, -objectMaxSpeed);
|
|
1075
|
+
this.velocity.y = clamp(this.velocity.y, objectMaxSpeed, -objectMaxSpeed);
|
|
510
1076
|
|
|
511
1077
|
// apply physics
|
|
512
1078
|
const oldPos = this.pos.copy();
|
|
@@ -560,7 +1126,7 @@ class EngineObject
|
|
|
560
1126
|
if (o.mass) // push away if not fixed
|
|
561
1127
|
o.velocity = o.velocity.subtract(velocity);
|
|
562
1128
|
|
|
563
|
-
debugPhysics && debugAABB(this.pos,
|
|
1129
|
+
debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f00');
|
|
564
1130
|
continue;
|
|
565
1131
|
}
|
|
566
1132
|
|
|
@@ -626,7 +1192,7 @@ class EngineObject
|
|
|
626
1192
|
this.velocity.x *= -this.elasticity;
|
|
627
1193
|
}
|
|
628
1194
|
|
|
629
|
-
debugPhysics && debugAABB(this.pos,
|
|
1195
|
+
debugPhysics && debugAABB(this.pos, this.size, o.pos, o.size, '#f0f');
|
|
630
1196
|
}
|
|
631
1197
|
}
|
|
632
1198
|
if (this.collideTiles)
|
|
@@ -634,8 +1200,6 @@ class EngineObject
|
|
|
634
1200
|
// check collision against tiles
|
|
635
1201
|
if (tileCollisionTest(this.pos, this.size, this))
|
|
636
1202
|
{
|
|
637
|
-
//debugPhysics && debugRect(this.pos, this.size, '#ff0');
|
|
638
|
-
|
|
639
1203
|
// if already was stuck in collision, don't do anything
|
|
640
1204
|
// this should not happen unless something starts in collision
|
|
641
1205
|
if (!tileCollisionTest(oldPos, this.size, this))
|
|
@@ -648,13 +1212,20 @@ class EngineObject
|
|
|
648
1212
|
// set if landed on ground
|
|
649
1213
|
this.groundObject = wasMovingDown;
|
|
650
1214
|
|
|
651
|
-
//
|
|
652
|
-
this.pos.y = oldPos.y;
|
|
1215
|
+
// bounce velocity
|
|
653
1216
|
this.velocity.y *= -this.elasticity;
|
|
1217
|
+
|
|
1218
|
+
// adjust next velocity to settle on ground
|
|
1219
|
+
const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
|
|
1220
|
+
if (o < 0 && o > -1 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
|
|
1221
|
+
this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
|
|
1222
|
+
|
|
1223
|
+
// move to previous position
|
|
1224
|
+
this.pos.y = oldPos.y;
|
|
654
1225
|
}
|
|
655
1226
|
if (isBlockedX)
|
|
656
1227
|
{
|
|
657
|
-
//
|
|
1228
|
+
// move to previous position and bounce
|
|
658
1229
|
this.pos.x = oldPos.x;
|
|
659
1230
|
this.velocity.x *= -this.elasticity;
|
|
660
1231
|
}
|
|
@@ -663,12 +1234,14 @@ class EngineObject
|
|
|
663
1234
|
}
|
|
664
1235
|
}
|
|
665
1236
|
|
|
1237
|
+
/** Render the object, draws a tile by default, automatically called each frame, sorted by renderOrder */
|
|
666
1238
|
render()
|
|
667
1239
|
{
|
|
668
1240
|
// default object render
|
|
669
1241
|
drawTile(this.pos, this.drawSize || this.size, this.tileIndex, this.tileSize, this.color, this.angle, this.mirror, this.additiveColor);
|
|
670
1242
|
}
|
|
671
1243
|
|
|
1244
|
+
/** Destroy this object, destroy it's children, detach it's parent, and mark it for removal */
|
|
672
1245
|
destroy()
|
|
673
1246
|
{
|
|
674
1247
|
if (this.destroyed)
|
|
@@ -680,15 +1253,45 @@ class EngineObject
|
|
|
680
1253
|
for (const child of this.children)
|
|
681
1254
|
child.destroy(child.parent = 0);
|
|
682
1255
|
}
|
|
683
|
-
|
|
684
|
-
|
|
1256
|
+
|
|
1257
|
+
/** Called to check if a tile collision should be resolved
|
|
1258
|
+
* @param {Number} tileData - the value of the tile at the position
|
|
1259
|
+
* @param {Vector2} pos - tile where the collision occured
|
|
1260
|
+
* @return {Boolean} true if the collision should be resolved */
|
|
1261
|
+
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
1262
|
+
|
|
1263
|
+
/** Called to check if a tile raycast hit
|
|
1264
|
+
* @param {Number} tileData - the value of the tile at the position
|
|
1265
|
+
* @param {Vector2} pos - tile where the raycast is
|
|
1266
|
+
* @return {Boolean} true if the raycast should hit */
|
|
1267
|
+
collideWithTileRaycast(tileData, pos) { return tileData > 0; }
|
|
1268
|
+
|
|
1269
|
+
/** Called to check if a tile raycast hit
|
|
1270
|
+
* @param {EngineObject} object - the object to test against
|
|
1271
|
+
* @return {Boolean} true if the collision should be resolved
|
|
1272
|
+
*/
|
|
685
1273
|
collideWithObject(o) { return 1; }
|
|
1274
|
+
|
|
1275
|
+
/** How long since the object was created
|
|
1276
|
+
* @return {Number} */
|
|
686
1277
|
getAliveTime() { return time - this.spawnTime; }
|
|
687
|
-
applyAcceleration(a) { ASSERT(!this.isFixed()); this.velocity = this.velocity.add(a); }
|
|
688
|
-
applyForce(force) { this.applyAcceleration(force.scale(1/this.mass)); }
|
|
689
|
-
isFixed() { return !this.mass; }
|
|
690
|
-
getMirrorSign(s=1) { return this.mirror ? -s : s; }
|
|
691
1278
|
|
|
1279
|
+
/** Apply acceleration to this object (adjust velocity, not affected by mass)
|
|
1280
|
+
* @param {Vector2} acceleration */
|
|
1281
|
+
applyAcceleration(a) { if (this.mass) this.velocity = this.velocity.add(a); }
|
|
1282
|
+
|
|
1283
|
+
/** Apply force to this object (adjust velocity, affected by mass)
|
|
1284
|
+
* @param {Vector2} force */
|
|
1285
|
+
applyForce(force) { this.applyAcceleration(force.scale(1/this.mass)); }
|
|
1286
|
+
|
|
1287
|
+
/** Get the direction of the mirror
|
|
1288
|
+
* @return {Number} -1 if this.mirror is true, or 1 if not mirrored */
|
|
1289
|
+
getMirrorSign() { return this.mirror ? -1 : 1; }
|
|
1290
|
+
|
|
1291
|
+
/** Attaches a child to this with a given local transform
|
|
1292
|
+
* @param {EngineObject} child
|
|
1293
|
+
* @param {Vector2} [localPos=new Vector2]
|
|
1294
|
+
* @param {Number} [localAngle=0] */
|
|
692
1295
|
addChild(child, localPos=vec2(), localAngle=0)
|
|
693
1296
|
{
|
|
694
1297
|
ASSERT(!child.parent && !this.children.includes(child));
|
|
@@ -697,6 +1300,9 @@ class EngineObject
|
|
|
697
1300
|
child.localPos = localPos.copy();
|
|
698
1301
|
child.localAngle = localAngle;
|
|
699
1302
|
}
|
|
1303
|
+
|
|
1304
|
+
/** Removes a child from this one
|
|
1305
|
+
* @param {EngineObject} child */
|
|
700
1306
|
removeChild(child)
|
|
701
1307
|
{
|
|
702
1308
|
ASSERT(child.parent == this && this.children.includes(child));
|
|
@@ -704,7 +1310,11 @@ class EngineObject
|
|
|
704
1310
|
child.parent = 0;
|
|
705
1311
|
}
|
|
706
1312
|
|
|
707
|
-
|
|
1313
|
+
/** Set how this object collides
|
|
1314
|
+
* @param {boolean} [collideSolidObjects=0] - Does it collide with solid objects
|
|
1315
|
+
* @param {boolean} [isSolid=0] - Does it collide with and block other objects (expensive in large numbers)
|
|
1316
|
+
* @param {boolean} [collideTiles=1] - Does it collide with the tile collision */
|
|
1317
|
+
setCollision(collideSolidObjects=0, isSolid=0, collideTiles=1)
|
|
708
1318
|
{
|
|
709
1319
|
ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
|
|
710
1320
|
|
|
@@ -725,62 +1335,81 @@ class EngineObject
|
|
|
725
1335
|
this.collideTiles = collideTiles;
|
|
726
1336
|
}
|
|
727
1337
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
function forEachObject(pos, size, callbackFunction, objects=engineObjects)
|
|
738
|
-
{
|
|
739
|
-
if (!pos)
|
|
740
|
-
{
|
|
741
|
-
// all objects
|
|
742
|
-
for (const o of objects)
|
|
743
|
-
callbackFunction(o);
|
|
744
|
-
}
|
|
745
|
-
else if (size.x != undefined)
|
|
746
|
-
{
|
|
747
|
-
// aabb test
|
|
748
|
-
for (const o of objects)
|
|
749
|
-
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
750
|
-
}
|
|
751
|
-
else
|
|
752
|
-
{
|
|
753
|
-
// circle test
|
|
754
|
-
const sizeSquared = size*size;
|
|
755
|
-
for (const o of objects)
|
|
756
|
-
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
/*
|
|
760
|
-
LittleJS Drawing System
|
|
761
|
-
|
|
762
|
-
- Super fast tile sheet rendering
|
|
763
|
-
- Utility functions for webgl
|
|
764
|
-
*/
|
|
1338
|
+
/**
|
|
1339
|
+
* LittleJS Drawing System
|
|
1340
|
+
* <br> - Hybrid with both Canvas2D and WebGL available
|
|
1341
|
+
* <br> - Super fast tile sheet rendering with WebGL
|
|
1342
|
+
* <br> - Can apply rotation, mirror, color and additive color
|
|
1343
|
+
* <br> - Many useful utility functions
|
|
1344
|
+
* @namespace Draw
|
|
1345
|
+
*/
|
|
765
1346
|
|
|
766
1347
|
'use strict';
|
|
767
1348
|
|
|
768
|
-
|
|
1349
|
+
/** Tile sheet for batch rendering system
|
|
1350
|
+
* @type {Image}
|
|
1351
|
+
* @memberof Draw */
|
|
1352
|
+
const tileImage = new Image();
|
|
1353
|
+
|
|
1354
|
+
/** The primary 2D canvas visible to the user
|
|
1355
|
+
* @type {HTMLCanvasElement}
|
|
1356
|
+
* @memberof Draw */
|
|
1357
|
+
let mainCanvas;
|
|
1358
|
+
|
|
1359
|
+
/** 2d context for mainCanvas
|
|
1360
|
+
* @type {CanvasRenderingContext2D}
|
|
1361
|
+
* @memberof Draw */
|
|
1362
|
+
let mainContext;
|
|
1363
|
+
|
|
1364
|
+
/** A canvas that appears on top of everything the same size as mainCanvas
|
|
1365
|
+
* @type {HTMLCanvasElement}
|
|
1366
|
+
* @memberof Draw */
|
|
1367
|
+
let overlayCanvas;
|
|
1368
|
+
|
|
1369
|
+
/** 2d context for overlayCanvas
|
|
1370
|
+
* @type {CanvasRenderingContext2D}
|
|
1371
|
+
* @memberof Draw */
|
|
1372
|
+
let overlayContext;
|
|
1373
|
+
|
|
1374
|
+
/** The size of the main canvas (and other secondary canvases)
|
|
1375
|
+
* @type {Vector2}
|
|
1376
|
+
* @memberof Draw */
|
|
1377
|
+
let mainCanvasSize = vec2();
|
|
1378
|
+
|
|
1379
|
+
/** Convert from screen to world space coordinates
|
|
1380
|
+
* @param {Vector2} screenPos
|
|
1381
|
+
* @return {Vector2}
|
|
1382
|
+
* @memberof Draw */
|
|
769
1383
|
const screenToWorld = (screenPos)=>
|
|
770
1384
|
screenPos.add(vec2(.5)).subtract(mainCanvasSize.scale(.5)).multiply(vec2(1/cameraScale,-1/cameraScale)).add(cameraPos);
|
|
1385
|
+
|
|
1386
|
+
/** Convert from world to screen space coordinates
|
|
1387
|
+
* @param {Vector2} worldPos
|
|
1388
|
+
* @return {Vector2}
|
|
1389
|
+
* @memberof Draw */
|
|
771
1390
|
const worldToScreen = (worldPos)=>
|
|
772
1391
|
worldPos.subtract(cameraPos).multiply(vec2(cameraScale,-cameraScale)).add(mainCanvasSize.scale(.5)).subtract(vec2(.5));
|
|
773
1392
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
1393
|
+
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
1394
|
+
* @param {Vector2} pos - Center of the tile in world space
|
|
1395
|
+
* @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space, width and height
|
|
1396
|
+
* @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
|
|
1397
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
|
|
1398
|
+
* @param {Color} [color=new Color(1,1,1)] - Color to modulate with
|
|
1399
|
+
* @param {Number} [angle=0] - Angle to rotate by
|
|
1400
|
+
* @param {Boolean} [mirror=0] - If true image is flipped along the Y axis
|
|
1401
|
+
* @param {Color} [additiveColor=new Color(0,0,0,0)] - Additive color to be applied
|
|
1402
|
+
* @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
1403
|
+
* @memberof Draw */
|
|
1404
|
+
function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle=0, mirror,
|
|
1405
|
+
additiveColor=new Color(0,0,0,0), useWebGL=glEnable)
|
|
777
1406
|
{
|
|
778
1407
|
showWatermark && ++drawCount;
|
|
779
|
-
if (glEnable)
|
|
1408
|
+
if (glEnable && useWebGL)
|
|
780
1409
|
{
|
|
781
|
-
if (tileIndex < 0)
|
|
1410
|
+
if (tileIndex < 0 || !tileImage.width)
|
|
782
1411
|
{
|
|
783
|
-
// if negative tile index, force untextured
|
|
1412
|
+
// if negative tile index or image not found, force untextured
|
|
784
1413
|
glDraw(pos.x, pos.y, size.x, size.y, angle, 0, 0, 0, 0, 0, color.rgbaInt());
|
|
785
1414
|
}
|
|
786
1415
|
else
|
|
@@ -827,33 +1456,68 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=defaultTileSize, col
|
|
|
827
1456
|
}
|
|
828
1457
|
}
|
|
829
1458
|
|
|
830
|
-
|
|
831
|
-
|
|
1459
|
+
/** Draw colored rect centered on pos
|
|
1460
|
+
* @param {Vector2} pos
|
|
1461
|
+
* @param {Vector2} [size=new Vector2(1,1)]
|
|
1462
|
+
* @param {Color} [color=new Color(1,1,1)]
|
|
1463
|
+
* @param {Number} [angle=0]
|
|
1464
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1465
|
+
* @memberof Draw */
|
|
1466
|
+
function drawRect(pos, size, color, angle, useWebGL)
|
|
832
1467
|
{
|
|
833
|
-
drawTile(pos, size, -1,
|
|
1468
|
+
drawTile(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
|
|
834
1469
|
}
|
|
835
1470
|
|
|
836
|
-
|
|
837
|
-
|
|
1471
|
+
/** Draw textured tile centered on pos in screen space
|
|
1472
|
+
* @param {Vector2} pos - Center of the tile
|
|
1473
|
+
* @param {Vector2} [size=new Vector2(1,1)] - Size of the tile
|
|
1474
|
+
* @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
|
|
1475
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
|
|
1476
|
+
* @param {Color} [color=new Color]
|
|
1477
|
+
* @param {Number} [angle=0]
|
|
1478
|
+
* @param {Boolean} [mirror=0]
|
|
1479
|
+
* @param {Color} [additiveColor=new Color(0,0,0,0)]
|
|
1480
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1481
|
+
* @memberof Draw */
|
|
1482
|
+
function drawTileScreenSpace(pos, size=vec2(1), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL)
|
|
838
1483
|
{
|
|
839
|
-
drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor);
|
|
1484
|
+
drawTile(screenToWorld(pos), size.scale(1/cameraScale), tileIndex, tileSize, color, angle, mirror, additiveColor, useWebGL);
|
|
840
1485
|
}
|
|
841
1486
|
|
|
842
|
-
|
|
843
|
-
|
|
1487
|
+
/** Draw colored rectangle in screen space
|
|
1488
|
+
* @param {Vector2} pos
|
|
1489
|
+
* @param {Vector2} [size=new Vector2(1,1)]
|
|
1490
|
+
* @param {Color} [color=new Color(1,1,1)]
|
|
1491
|
+
* @param {Number} [angle=0]
|
|
1492
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1493
|
+
* @memberof Draw */
|
|
1494
|
+
function drawRectScreenSpace(pos, size, color, angle, useWebGL)
|
|
844
1495
|
{
|
|
845
|
-
|
|
1496
|
+
drawTileSrceenSpace(pos, size, -1, tileSizeDefault, color, angle, 0, 0, useWebGL);
|
|
846
1497
|
}
|
|
847
1498
|
|
|
848
|
-
|
|
849
|
-
|
|
1499
|
+
/** Draw colored line between two points
|
|
1500
|
+
* @param {Vector2} posA
|
|
1501
|
+
* @param {Vector2} posB
|
|
1502
|
+
* @param {Number} [thickness=.1]
|
|
1503
|
+
* @param {Color} [color=new Color(1,1,1)]
|
|
1504
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1505
|
+
* @memberof Draw */
|
|
1506
|
+
function drawLine(posA, posB, thickness=.1, color, useWebGL)
|
|
850
1507
|
{
|
|
851
1508
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
852
1509
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
853
|
-
drawRect(posA.add(halfDelta), size, color, halfDelta.angle());
|
|
1510
|
+
drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), 0, 0, useWebGL);
|
|
854
1511
|
}
|
|
855
1512
|
|
|
856
|
-
|
|
1513
|
+
/** Draw directly to a 2d canvas context in world space
|
|
1514
|
+
* @param {Vector2} pos
|
|
1515
|
+
* @param {Vector2} size
|
|
1516
|
+
* @param {Number} angle
|
|
1517
|
+
* @param {Boolean} mirror
|
|
1518
|
+
* @param {Function} drawFunction
|
|
1519
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
1520
|
+
* @memberof Draw */
|
|
857
1521
|
function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainContext)
|
|
858
1522
|
{
|
|
859
1523
|
// create canvas transform from world space to screen space
|
|
@@ -867,8 +1531,16 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
|
|
|
867
1531
|
context.restore();
|
|
868
1532
|
}
|
|
869
1533
|
|
|
870
|
-
|
|
871
|
-
|
|
1534
|
+
/** Draw text on overlay canvas in world space
|
|
1535
|
+
* @param {String} text
|
|
1536
|
+
* @param {Vector2} pos
|
|
1537
|
+
* @param {Number} [size=1]
|
|
1538
|
+
* @param {Color} [color=new Color(1,1,1)]
|
|
1539
|
+
* @param {Number} [lineWidth=0]
|
|
1540
|
+
* @param {Color} [lineColor=new Color(0,0,0)]
|
|
1541
|
+
* @param {String} [textAlign='center']
|
|
1542
|
+
* @memberof Draw */
|
|
1543
|
+
function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault)
|
|
872
1544
|
{
|
|
873
1545
|
pos = worldToScreen(pos);
|
|
874
1546
|
overlayContext.font = size*cameraScale + 'px '+ font;
|
|
@@ -884,16 +1556,28 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
|
|
|
884
1556
|
overlayContext.fillText(text, pos.x, pos.y);
|
|
885
1557
|
}
|
|
886
1558
|
|
|
887
|
-
|
|
888
|
-
|
|
1559
|
+
/** Enable normal or additive blend mode
|
|
1560
|
+
* @param {Boolean} [additive=0]
|
|
1561
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1562
|
+
* @memberof Draw */
|
|
1563
|
+
function setBlendMode(additive, useWebGL=glEnable)
|
|
889
1564
|
{
|
|
890
|
-
glEnable
|
|
1565
|
+
if (glEnable && useWebGL)
|
|
1566
|
+
glSetBlendMode(additive);
|
|
1567
|
+
else
|
|
1568
|
+
mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
891
1569
|
}
|
|
892
1570
|
|
|
893
1571
|
///////////////////////////////////////////////////////////////////////////////
|
|
894
|
-
//
|
|
1572
|
+
// Fullscreen mode
|
|
895
1573
|
|
|
1574
|
+
/** Returns true if fullscreen mode is active
|
|
1575
|
+
* @return {Boolean}
|
|
1576
|
+
* @memberof Draw */
|
|
896
1577
|
const isFullscreen =()=> document.fullscreenElement;
|
|
1578
|
+
|
|
1579
|
+
/** Toggle fullsceen mode
|
|
1580
|
+
* @memberof Draw */
|
|
897
1581
|
function toggleFullscreen()
|
|
898
1582
|
{
|
|
899
1583
|
if (isFullscreen())
|
|
@@ -911,39 +1595,107 @@ function toggleFullscreen()
|
|
|
911
1595
|
document.body.mozRequestFullScreen();
|
|
912
1596
|
}
|
|
913
1597
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1598
|
+
/**
|
|
1599
|
+
* LittleJS Input System
|
|
1600
|
+
* <br> - Tracks key down, pressed, and released
|
|
1601
|
+
* <br> - Also tracks mouse buttons, position, and wheel
|
|
1602
|
+
* <br> - Supports multiple gamepads
|
|
1603
|
+
* @namespace Input
|
|
1604
|
+
*/
|
|
920
1605
|
|
|
921
1606
|
'use strict';
|
|
922
1607
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1608
|
+
/** Returns true if device key is down
|
|
1609
|
+
* @param {Number} key
|
|
1610
|
+
* @param {Number} [device=0]
|
|
1611
|
+
* @return {Boolean}
|
|
1612
|
+
* @memberof Input */
|
|
1613
|
+
const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1 ? 1 : 0;
|
|
1614
|
+
|
|
1615
|
+
/** Returns true if device key was pressed this frame
|
|
1616
|
+
* @param {Number} key
|
|
1617
|
+
* @param {Number} [device=0]
|
|
1618
|
+
* @return {Boolean}
|
|
1619
|
+
* @memberof Input */
|
|
1620
|
+
const keyWasPressed = (key, device=0)=> inputData[device] && inputData[device][key] & 2 ? 1 : 0;
|
|
1621
|
+
|
|
1622
|
+
/** Returns true if device key was released this frame
|
|
1623
|
+
* @param {Number} key
|
|
1624
|
+
* @param {Number} [device=0]
|
|
1625
|
+
* @return {Boolean}
|
|
1626
|
+
* @memberof Input */
|
|
927
1627
|
const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][key] & 4 ? 1 : 0;
|
|
928
|
-
const clearInput = ()=> inputData[0] = [];
|
|
929
1628
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
const
|
|
1629
|
+
/** Clears all input
|
|
1630
|
+
* @memberof Input */
|
|
1631
|
+
const clearInput = ()=> inputData[0] = [];
|
|
1632
|
+
|
|
1633
|
+
/** Returns true if mouse button is down
|
|
1634
|
+
* @param {Number} button
|
|
1635
|
+
* @return {Boolean}
|
|
1636
|
+
* @memberof Input */
|
|
1637
|
+
const mouseIsDown = keyIsDown;
|
|
1638
|
+
|
|
1639
|
+
/** Returns true if mouse button was pressed
|
|
1640
|
+
* @param {Number} button
|
|
1641
|
+
* @return {Boolean}
|
|
1642
|
+
* @memberof Input */
|
|
1643
|
+
const mouseWasPressed = keyWasPressed;
|
|
1644
|
+
|
|
1645
|
+
/** Returns true if mouse button was released
|
|
1646
|
+
* @param {Number} button
|
|
1647
|
+
* @return {Boolean}
|
|
1648
|
+
* @memberof Input */
|
|
933
1649
|
const mouseWasReleased = keyWasReleased;
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1650
|
+
|
|
1651
|
+
/** Mouse pos in world space
|
|
1652
|
+
* @type {Vector2}
|
|
1653
|
+
* @memberof Input */
|
|
1654
|
+
let mousePos = vec2();
|
|
1655
|
+
|
|
1656
|
+
/** Mouse pos in screen space
|
|
1657
|
+
* @type {Vector2}
|
|
1658
|
+
* @memberof Input */
|
|
1659
|
+
let mousePosScreen = vec2();
|
|
1660
|
+
|
|
1661
|
+
/** Mouse wheel delta this frame
|
|
1662
|
+
* @memberof Input */
|
|
1663
|
+
let mouseWheel = 0;
|
|
1664
|
+
|
|
1665
|
+
/** Returns true if user is using gamepad (has more recently pressed a gamepad button)
|
|
1666
|
+
* @memberof Input */
|
|
1667
|
+
let isUsingGamepad = 0;
|
|
1668
|
+
|
|
1669
|
+
/** Returns true if gamepad button is down
|
|
1670
|
+
* @param {Number} button
|
|
1671
|
+
* @param {Number} [gamepad=0]
|
|
1672
|
+
* @return {Boolean}
|
|
1673
|
+
* @memberof Input */
|
|
1674
|
+
const gamepadIsDown = (button, gamepad=0)=> keyIsDown(button, gamepad+1);
|
|
1675
|
+
|
|
1676
|
+
/** Returns true if gamepad button was pressed
|
|
1677
|
+
* @param {Number} button
|
|
1678
|
+
* @param {Number} [gamepad=0]
|
|
1679
|
+
* @return {Boolean}
|
|
1680
|
+
* @memberof Input */
|
|
1681
|
+
const gamepadWasPressed = (button, gamepad=0)=> keyWasPressed(button, gamepad+1);
|
|
1682
|
+
|
|
1683
|
+
/** Returns true if gamepad button was released
|
|
1684
|
+
* @param {Number} button
|
|
1685
|
+
* @param {Number} [gamepad=0]
|
|
1686
|
+
* @return {Boolean}
|
|
1687
|
+
* @memberof Input */
|
|
942
1688
|
const gamepadWasReleased = (button, gamepad=0)=> keyWasReleased(button, gamepad+1);
|
|
943
|
-
|
|
1689
|
+
|
|
1690
|
+
/** Returns gamepad stick value
|
|
1691
|
+
* @param {Number} stick
|
|
1692
|
+
* @param {Number} [gamepad=0]
|
|
1693
|
+
* @return {Vector2}
|
|
1694
|
+
* @memberof Input */
|
|
1695
|
+
const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2();
|
|
944
1696
|
|
|
945
1697
|
///////////////////////////////////////////////////////////////////////////////
|
|
946
|
-
//
|
|
1698
|
+
// Input update called by engine
|
|
947
1699
|
|
|
948
1700
|
const inputData = [[]];
|
|
949
1701
|
|
|
@@ -969,23 +1721,25 @@ function inputUpdatePost()
|
|
|
969
1721
|
}
|
|
970
1722
|
|
|
971
1723
|
///////////////////////////////////////////////////////////////////////////////
|
|
972
|
-
//
|
|
1724
|
+
// Keyboard event handlers
|
|
1725
|
+
|
|
973
1726
|
onkeydown = e=>
|
|
974
1727
|
{
|
|
975
1728
|
if (debug && e.target != document.body) return;
|
|
976
|
-
e.repeat || (inputData[
|
|
977
|
-
|
|
1729
|
+
e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
|
|
1730
|
+
debug || e.preventDefault();
|
|
978
1731
|
}
|
|
979
1732
|
onkeyup = e=>
|
|
980
1733
|
{
|
|
981
1734
|
if (debug && e.target != document.body) return;
|
|
982
1735
|
inputData[0][remapKeyCode(e.keyCode)] = 4;
|
|
983
1736
|
}
|
|
984
|
-
const remapKeyCode = c=>
|
|
1737
|
+
const remapKeyCode = c=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
985
1738
|
|
|
986
1739
|
///////////////////////////////////////////////////////////////////////////////
|
|
987
|
-
//
|
|
988
|
-
|
|
1740
|
+
// Mouse event handlers
|
|
1741
|
+
|
|
1742
|
+
onmousedown = e=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
989
1743
|
onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
990
1744
|
onmousemove = e=>
|
|
991
1745
|
{
|
|
@@ -999,7 +1753,7 @@ onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
|
999
1753
|
oncontextmenu = e=> !1; // prevent right click menu
|
|
1000
1754
|
|
|
1001
1755
|
///////////////////////////////////////////////////////////////////////////////
|
|
1002
|
-
//
|
|
1756
|
+
// Gamepad input
|
|
1003
1757
|
|
|
1004
1758
|
const stickData = [];
|
|
1005
1759
|
function gamepadsUpdate()
|
|
@@ -1033,10 +1787,10 @@ function gamepadsUpdate()
|
|
|
1033
1787
|
{
|
|
1034
1788
|
const button = gamepad.buttons[j];
|
|
1035
1789
|
data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
|
|
1036
|
-
|
|
1790
|
+
isUsingGamepad |= !i && button.pressed;
|
|
1037
1791
|
}
|
|
1038
1792
|
|
|
1039
|
-
if (
|
|
1793
|
+
if (gamepadDirectionEmulateStick)
|
|
1040
1794
|
{
|
|
1041
1795
|
// copy dpad to left analog stick when pressed
|
|
1042
1796
|
const dpad = vec2(gamepadIsDown(15,i) - gamepadIsDown(14,i), gamepadIsDown(12,i) - gamepadIsDown(13,i));
|
|
@@ -1048,12 +1802,16 @@ function gamepadsUpdate()
|
|
|
1048
1802
|
}
|
|
1049
1803
|
|
|
1050
1804
|
///////////////////////////////////////////////////////////////////////////////
|
|
1051
|
-
//
|
|
1052
|
-
|
|
1805
|
+
// Touch input
|
|
1806
|
+
|
|
1807
|
+
/** True if a touch device has been detected
|
|
1808
|
+
* @const {boolean}
|
|
1809
|
+
* @memberof Input */
|
|
1810
|
+
const isTouchDevice = inputTouchEnable && window.ontouchstart !== undefined;
|
|
1053
1811
|
if (isTouchDevice)
|
|
1054
1812
|
{
|
|
1055
1813
|
// handle all touch events the same way
|
|
1056
|
-
let wasTouching;
|
|
1814
|
+
let wasTouching, hadTouchInput;
|
|
1057
1815
|
ontouchstart = ontouchmove = ontouchend = e=>
|
|
1058
1816
|
{
|
|
1059
1817
|
e.button = 0; // all touches are left click
|
|
@@ -1062,7 +1820,7 @@ if (isTouchDevice)
|
|
|
1062
1820
|
const touching = e.touches.length;
|
|
1063
1821
|
if (touching)
|
|
1064
1822
|
{
|
|
1065
|
-
|
|
1823
|
+
hadTouchInput || zzfx(0, hadTouchInput=1) ; // fix mobile audio, force it to play a sound the first time
|
|
1066
1824
|
|
|
1067
1825
|
// set event pos and pass it along
|
|
1068
1826
|
e.x = e.touches[0].clientX;
|
|
@@ -1079,27 +1837,45 @@ if (isTouchDevice)
|
|
|
1079
1837
|
return !e.cancelable;
|
|
1080
1838
|
}
|
|
1081
1839
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1840
|
+
/**
|
|
1841
|
+
* LittleJS Audio System
|
|
1842
|
+
* <br> - ZzFX Sound Effects and ZzFXM Music
|
|
1843
|
+
* <br> - Caches sounds and music for fast playback
|
|
1844
|
+
* <br> - Can attenuate and apply stereo panning to sounds
|
|
1845
|
+
* <br> - Ability to play mp3, ogg, and wave files
|
|
1846
|
+
* <br> - Speech synthesis wrapper functions
|
|
1847
|
+
* @namespace Audio
|
|
1848
|
+
*/
|
|
1090
1849
|
|
|
1091
1850
|
'use strict';
|
|
1092
1851
|
|
|
1852
|
+
/**
|
|
1853
|
+
* Sound Object - Stores a zzfx sound for later use and can be played positionally
|
|
1854
|
+
* @example
|
|
1855
|
+
* // create a sound
|
|
1856
|
+
* const sound_example = new Sound([.5,.5]);
|
|
1857
|
+
*
|
|
1858
|
+
* // play the sound
|
|
1859
|
+
* sound_example.play();
|
|
1860
|
+
*/
|
|
1093
1861
|
class Sound
|
|
1094
1862
|
{
|
|
1095
|
-
|
|
1863
|
+
/** Create a sound object and cache the zzfx samples for later use
|
|
1864
|
+
* @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
|
|
1865
|
+
* @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
|
|
1866
|
+
* @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
|
|
1867
|
+
*/
|
|
1868
|
+
constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
|
|
1096
1869
|
{
|
|
1097
1870
|
if (!soundEnable) return;
|
|
1098
1871
|
|
|
1872
|
+
/** @property {Number} - World space max range of sound, will not play if camera is farther away */
|
|
1099
1873
|
this.range = range;
|
|
1874
|
+
|
|
1875
|
+
/** @property {Number} - At what percentage of range should it start tapering off */
|
|
1100
1876
|
this.taper = taper;
|
|
1101
1877
|
|
|
1102
|
-
// get randomness from sound
|
|
1878
|
+
// get randomness from sound parameters
|
|
1103
1879
|
this.randomness = zzfxSound[1] || 0;
|
|
1104
1880
|
zzfxSound[1] = 0;
|
|
1105
1881
|
|
|
@@ -1107,7 +1883,14 @@ class Sound
|
|
|
1107
1883
|
this.cachedSamples = zzfxG(...zzfxSound);
|
|
1108
1884
|
}
|
|
1109
1885
|
|
|
1110
|
-
|
|
1886
|
+
/** Play the sound
|
|
1887
|
+
* @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
|
|
1888
|
+
* @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
|
|
1889
|
+
* @param {Number} [pitch=1] - How much to scale pitch by (also adjusted by this.randomness)
|
|
1890
|
+
* @param {Number} [randomnessScale=1] - How much to scale randomness
|
|
1891
|
+
* @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
|
|
1892
|
+
*/
|
|
1893
|
+
play(pos, volume=1, pitch=1, randomnessScale=1)
|
|
1111
1894
|
{
|
|
1112
1895
|
if (!soundEnable) return;
|
|
1113
1896
|
|
|
@@ -1123,7 +1906,7 @@ class Sound
|
|
|
1123
1906
|
return; // out of range
|
|
1124
1907
|
|
|
1125
1908
|
// attenuate volume by distance
|
|
1126
|
-
|
|
1909
|
+
volume *= percent(lengthSquared**.5, range*this.taper, range);
|
|
1127
1910
|
}
|
|
1128
1911
|
|
|
1129
1912
|
// get pan from screen space coords
|
|
@@ -1131,13 +1914,57 @@ class Sound
|
|
|
1131
1914
|
}
|
|
1132
1915
|
|
|
1133
1916
|
// play the sound
|
|
1134
|
-
const playbackRate =
|
|
1135
|
-
return playSamples([this.cachedSamples],
|
|
1917
|
+
const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
|
|
1918
|
+
return playSamples([this.cachedSamples], volume, playbackRate, pan);
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
/** Play the sound as a note with a semitone offset
|
|
1922
|
+
* @param {Number} semitoneOffset - How many semitones to offset pitch
|
|
1923
|
+
* @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
|
|
1924
|
+
* @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
|
|
1925
|
+
* @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
|
|
1926
|
+
*/
|
|
1927
|
+
playNote(semitoneOffset, pos, volume=1)
|
|
1928
|
+
{
|
|
1929
|
+
if (!soundEnable) return;
|
|
1930
|
+
|
|
1931
|
+
return this.play(pos, volume, 2**(semitoneOffset/12), 0);
|
|
1136
1932
|
}
|
|
1137
1933
|
}
|
|
1138
1934
|
|
|
1935
|
+
/**
|
|
1936
|
+
* Music Object - Stores a zzfx music track for later use
|
|
1937
|
+
* @example
|
|
1938
|
+
* // create some music
|
|
1939
|
+
* const music_example = new Music(
|
|
1940
|
+
* [
|
|
1941
|
+
* [ // instruments
|
|
1942
|
+
* [,0,400] // simple note
|
|
1943
|
+
* ],
|
|
1944
|
+
* [ // patterns
|
|
1945
|
+
* [ // pattern 1
|
|
1946
|
+
* [ // channel 0
|
|
1947
|
+
* 0, -1, // instrument 0, left speaker
|
|
1948
|
+
* 1, 0, 9, 1 // channel notes
|
|
1949
|
+
* ],
|
|
1950
|
+
* [ // channel 1
|
|
1951
|
+
* 0, 1, // instrument 1, right speaker
|
|
1952
|
+
* 0, 12, 17, -1 // channel notes
|
|
1953
|
+
* ]
|
|
1954
|
+
* ],
|
|
1955
|
+
* ],
|
|
1956
|
+
* [0, 0, 0, 0], // sequence, play pattern 0 four times
|
|
1957
|
+
* 90 // BPM
|
|
1958
|
+
* ]);
|
|
1959
|
+
*
|
|
1960
|
+
* // play the music
|
|
1961
|
+
* music_example.play();
|
|
1962
|
+
*/
|
|
1139
1963
|
class Music
|
|
1140
1964
|
{
|
|
1965
|
+
/** Create a music object and cache the zzfx music samples for later use
|
|
1966
|
+
* @param {Array} zzfxMusic - Array of zzfx music parameters
|
|
1967
|
+
*/
|
|
1141
1968
|
constructor(zzfxMusic)
|
|
1142
1969
|
{
|
|
1143
1970
|
if (!soundEnable) return;
|
|
@@ -1145,29 +1972,44 @@ class Music
|
|
|
1145
1972
|
this.cachedSamples = zzfxM(...zzfxMusic);
|
|
1146
1973
|
}
|
|
1147
1974
|
|
|
1148
|
-
|
|
1975
|
+
/** Play the music
|
|
1976
|
+
* @param {Number} [volume=1] - How much to scale volume by
|
|
1977
|
+
* @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
|
|
1978
|
+
* @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
|
|
1979
|
+
*/
|
|
1980
|
+
play(volume = 1, loop = 1)
|
|
1149
1981
|
{
|
|
1150
1982
|
if (!soundEnable) return;
|
|
1151
1983
|
|
|
1152
|
-
return playSamples(this.cachedSamples,
|
|
1984
|
+
return playSamples(this.cachedSamples, volume, 1, 0, loop);
|
|
1153
1985
|
}
|
|
1154
1986
|
}
|
|
1155
1987
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1988
|
+
/** Play an mp3 or wav audio from a local file or url
|
|
1989
|
+
* @param {String} url - Location of sound file to play
|
|
1990
|
+
* @param {Number} [volume=1] - How much to scale volume by
|
|
1991
|
+
* @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
|
|
1992
|
+
* @return {HTMLAudioElement} - The audio element for this sound
|
|
1993
|
+
* @memberof Audio */
|
|
1994
|
+
function playAudioFile(url, volume=1, loop=1)
|
|
1160
1995
|
{
|
|
1161
1996
|
if (!soundEnable) return;
|
|
1162
1997
|
|
|
1163
1998
|
const audio = new Audio(url);
|
|
1164
|
-
audio.volume =
|
|
1999
|
+
audio.volume = soundVolume * volume;
|
|
1165
2000
|
audio.loop = loop;
|
|
1166
2001
|
audio.play();
|
|
1167
2002
|
return audio;
|
|
1168
2003
|
}
|
|
1169
2004
|
|
|
1170
|
-
|
|
2005
|
+
/** Speak text with passed in settings
|
|
2006
|
+
* @param {String} text - The text to speak
|
|
2007
|
+
* @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
2008
|
+
* @param {Number} [volume=1] - How much to scale volume by
|
|
2009
|
+
* @param {Number} [rate=1] - How quickly to speak
|
|
2010
|
+
* @param {Number} [pitch=1] - How much to change the pitch by
|
|
2011
|
+
* @return {SpeechSynthesisUtterance} - The utterance that was spoken
|
|
2012
|
+
* @memberof Audio */
|
|
1171
2013
|
function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
1172
2014
|
{
|
|
1173
2015
|
if (!soundEnable || !speechSynthesis) return;
|
|
@@ -1179,22 +2021,39 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
1179
2021
|
// build utterance and speak
|
|
1180
2022
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
1181
2023
|
utterance.lang = language;
|
|
1182
|
-
utterance.volume = volume*
|
|
2024
|
+
utterance.volume = 2*volume*soundVolume;
|
|
1183
2025
|
utterance.rate = rate;
|
|
1184
2026
|
utterance.pitch = pitch;
|
|
1185
2027
|
speechSynthesis.speak(utterance);
|
|
1186
2028
|
return utterance;
|
|
1187
2029
|
}
|
|
1188
2030
|
|
|
1189
|
-
|
|
2031
|
+
/** Stop all queued speech
|
|
2032
|
+
* @memberof Audio */
|
|
1190
2033
|
const stopSpeech = ()=> speechSynthesis && speechSynthesis.cancel();
|
|
1191
2034
|
|
|
1192
|
-
|
|
2035
|
+
/** Get frequency of a note on a musical scale
|
|
2036
|
+
* @param {Number} semitoneOffset - How many semitones away from the root note
|
|
2037
|
+
* @param {Number} [rootNoteFrequency=220] - Frequency at semitone offset 0
|
|
2038
|
+
* @return {Number} - The frequency of the note
|
|
2039
|
+
* @memberof Audio */
|
|
2040
|
+
const getNoteFrequency = (semitoneOffset, rootFrequency=220)=> rootFrequency * 2**(semitoneOffset/12);
|
|
1193
2041
|
|
|
1194
|
-
|
|
2042
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1195
2043
|
|
|
1196
|
-
|
|
1197
|
-
|
|
2044
|
+
/** Audio context used by the engine
|
|
2045
|
+
* @memberof Audio */
|
|
2046
|
+
let audioContext;
|
|
2047
|
+
|
|
2048
|
+
/** Play cached audio samples with given settings
|
|
2049
|
+
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
|
|
2050
|
+
* @param {Number} [volume=1] - How much to scale volume by
|
|
2051
|
+
* @param {Number} [rate=1] - The playback rate to use
|
|
2052
|
+
* @param {Number} [pan=0] - How much to apply stereo panning
|
|
2053
|
+
* @param {Boolean} [loop=0] - True if the sound should loop when it reaches the end
|
|
2054
|
+
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
2055
|
+
* @memberof Audio */
|
|
2056
|
+
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
1198
2057
|
{
|
|
1199
2058
|
if (!soundEnable) return;
|
|
1200
2059
|
|
|
@@ -1209,13 +2068,13 @@ function playSamples(sampleChannels, volume=1, playbackRate=1, pan=0, loop=0)
|
|
|
1209
2068
|
// copy samples to buffer and setup source
|
|
1210
2069
|
sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
|
|
1211
2070
|
source.buffer = buffer;
|
|
1212
|
-
source.playbackRate.value =
|
|
2071
|
+
source.playbackRate.value = rate;
|
|
1213
2072
|
source.loop = loop;
|
|
1214
2073
|
|
|
1215
2074
|
// create pan and gain nodes
|
|
1216
2075
|
source
|
|
1217
2076
|
.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, 1, -1)}))
|
|
1218
|
-
.connect(new GainNode(audioContext, {'gain':
|
|
2077
|
+
.connect(new GainNode(audioContext, {'gain':soundVolume*volume}))
|
|
1219
2078
|
.connect(audioContext.destination);
|
|
1220
2079
|
|
|
1221
2080
|
// play and return sound
|
|
@@ -1226,10 +2085,20 @@ function playSamples(sampleChannels, volume=1, playbackRate=1, pan=0, loop=0)
|
|
|
1226
2085
|
///////////////////////////////////////////////////////////////////////////////
|
|
1227
2086
|
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.1.8 by Frank Force
|
|
1228
2087
|
|
|
1229
|
-
|
|
1230
|
-
|
|
2088
|
+
/** Generate and play a ZzFX sound
|
|
2089
|
+
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
2090
|
+
* @return {Array} - Array of audio samples
|
|
2091
|
+
* @memberof Audio */
|
|
2092
|
+
const zzfx = (...zzfxSound) => playSamples([zzfxG(...zzfxSound)]);
|
|
2093
|
+
|
|
2094
|
+
/** Sample rate used for all ZzFX sounds
|
|
2095
|
+
* @default 44100
|
|
2096
|
+
* @memberof Audio */
|
|
2097
|
+
const zzfxR = 44100;
|
|
1231
2098
|
|
|
1232
|
-
|
|
2099
|
+
/** Generate samples for a ZzFX sound
|
|
2100
|
+
* @memberof Audio */
|
|
2101
|
+
function zzfxG
|
|
1233
2102
|
(
|
|
1234
2103
|
// parameters
|
|
1235
2104
|
volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
|
|
@@ -1273,7 +2142,7 @@ function zzfxG // generate samples
|
|
|
1273
2142
|
1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
|
|
1274
2143
|
: 1) *
|
|
1275
2144
|
sign(s)*(abs(s)**shapeCurve) * // curve 0=square, 2=pointy
|
|
1276
|
-
volume *
|
|
2145
|
+
volume * soundVolume * ( // envelope
|
|
1277
2146
|
i < attack ? i/attack : // attack
|
|
1278
2147
|
i < attack + decay ? // decay
|
|
1279
2148
|
1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
|
|
@@ -1314,6 +2183,13 @@ function zzfxG // generate samples
|
|
|
1314
2183
|
///////////////////////////////////////////////////////////////////////////////
|
|
1315
2184
|
// ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
|
|
1316
2185
|
|
|
2186
|
+
/** Generate samples for a ZzFM song with given parameters
|
|
2187
|
+
* @param {Array} instruments - Array of ZzFX sound paramaters
|
|
2188
|
+
* @param {Array} patterns - Array of pattern data
|
|
2189
|
+
* @param {Array} sequence - Array of pattern indexes
|
|
2190
|
+
* @param {Number} [BPM=125] - Playback speed of the song in BPM
|
|
2191
|
+
* @returns {Array} - Left and right channel sample data
|
|
2192
|
+
* @memberof Audio */
|
|
1317
2193
|
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
1318
2194
|
{
|
|
1319
2195
|
let instrumentParameters;
|
|
@@ -1411,25 +2287,31 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
1411
2287
|
|
|
1412
2288
|
return [leftChannelBuffer, rightChannelBuffer];
|
|
1413
2289
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
2290
|
+
/**
|
|
2291
|
+
* LittleJS Tile Layer System
|
|
2292
|
+
* <br> - Caches arrays of tiles to offscreen canvas for fast rendering
|
|
2293
|
+
* <br> - Unlimted numbers of layers, allocates canvases as needed
|
|
2294
|
+
* <br> - Interfaces with EngineObject for collision
|
|
2295
|
+
* <br> - Collision layer is separate from visible layers
|
|
2296
|
+
* <br> - Tile layers can be drawn to using their context with canvas2d
|
|
2297
|
+
* <br> - It is recommended to have a visible layer that matches the collision
|
|
2298
|
+
* @namespace TileCollision
|
|
2299
|
+
*/
|
|
1423
2300
|
|
|
1424
2301
|
'use strict';
|
|
1425
2302
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
2303
|
+
/** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access
|
|
2304
|
+
* @memberof TileCollision */
|
|
1429
2305
|
let tileCollision = [];
|
|
2306
|
+
|
|
2307
|
+
/** Size of the tile collision layer
|
|
2308
|
+
* @type {Vector2}
|
|
2309
|
+
* @memberof TileCollision */
|
|
1430
2310
|
let tileCollisionSize = vec2();
|
|
1431
|
-
const tileLayerCanvasCache = [];
|
|
1432
2311
|
|
|
2312
|
+
/** Clear and initialize tile collision
|
|
2313
|
+
* @param {Vector2} size
|
|
2314
|
+
* @memberof TileCollision */
|
|
1433
2315
|
function initTileCollision(size)
|
|
1434
2316
|
{
|
|
1435
2317
|
tileCollisionSize = size;
|
|
@@ -1438,21 +2320,34 @@ function initTileCollision(size)
|
|
|
1438
2320
|
tileCollision[i] = 0;
|
|
1439
2321
|
}
|
|
1440
2322
|
|
|
1441
|
-
|
|
2323
|
+
/** Set tile collision data
|
|
2324
|
+
* @param {Vector2} pos
|
|
2325
|
+
* @param {Number} [data=0]
|
|
2326
|
+
* @memberof TileCollision */
|
|
1442
2327
|
const setTileCollisionData = (pos, data=0)=>
|
|
1443
2328
|
pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
|
|
2329
|
+
|
|
2330
|
+
/** Get tile collision data
|
|
2331
|
+
* @param {Vector2} pos
|
|
2332
|
+
* @return {Number}
|
|
2333
|
+
* @memberof TileCollision */
|
|
1444
2334
|
const getTileCollisionData = (pos)=>
|
|
1445
2335
|
pos.arrayCheck(tileCollisionSize) ? tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] : 0;
|
|
1446
2336
|
|
|
1447
|
-
|
|
2337
|
+
/** Check if collision with another object should occur
|
|
2338
|
+
* @param {Vector2} pos
|
|
2339
|
+
* @param {Vector2} [size=new Vector2(1,1)]
|
|
2340
|
+
* @param {EngineObject} [object]
|
|
2341
|
+
* @return {Boolean}
|
|
2342
|
+
* @memberof TileCollision */
|
|
1448
2343
|
function tileCollisionTest(pos, size=vec2(), object)
|
|
1449
2344
|
{
|
|
1450
2345
|
const minX = max(Math.floor(pos.x - size.x/2), 0);
|
|
1451
2346
|
const minY = max(Math.floor(pos.y - size.y/2), 0);
|
|
1452
|
-
const maxX = min(
|
|
1453
|
-
const maxY = min(
|
|
1454
|
-
for (let y = minY; y
|
|
1455
|
-
for (let x = minX; x
|
|
2347
|
+
const maxX = min(pos.x + size.x/2, tileCollisionSize.x-1);
|
|
2348
|
+
const maxY = min(pos.y + size.y/2, tileCollisionSize.y-1);
|
|
2349
|
+
for (let y = minY; y < maxY; ++y)
|
|
2350
|
+
for (let x = minX; x < maxX; ++x)
|
|
1456
2351
|
{
|
|
1457
2352
|
const tileData = tileCollision[y*tileCollisionSize.x+x];
|
|
1458
2353
|
if (tileData && (!object || object.collideWithTile(tileData, new Vector2(x, y))))
|
|
@@ -1460,13 +2355,18 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
1460
2355
|
}
|
|
1461
2356
|
}
|
|
1462
2357
|
|
|
1463
|
-
|
|
1464
|
-
|
|
2358
|
+
/** Return the center of tile if any that is hit (this does not return the exact hit point)
|
|
2359
|
+
* @param {Vector2} posStart
|
|
2360
|
+
* @param {Vector2} posEnd
|
|
2361
|
+
* @param {EngineObject} [object]
|
|
2362
|
+
* @return {Vector2}
|
|
2363
|
+
* @memberof TileCollision */
|
|
1465
2364
|
function tileCollisionRaycast(posStart, posEnd, object)
|
|
1466
2365
|
{
|
|
1467
2366
|
// test if a ray collides with tiles from start to end
|
|
1468
|
-
|
|
1469
|
-
|
|
2367
|
+
// todo: a way to get the exact hit point, it must still register as inside the hit tile
|
|
2368
|
+
posStart = posStart.floor();
|
|
2369
|
+
posEnd = posEnd.floor();
|
|
1470
2370
|
const posDelta = posEnd.subtract(posStart);
|
|
1471
2371
|
const dx = abs(posDelta.x), dy = -abs(posDelta.y);
|
|
1472
2372
|
const sx = sign(posDelta.x), sy = sign(posDelta.y);
|
|
@@ -1494,32 +2394,74 @@ function tileCollisionRaycast(posStart, posEnd, object)
|
|
|
1494
2394
|
///////////////////////////////////////////////////////////////////////////////
|
|
1495
2395
|
// Tile Layer Rendering System
|
|
1496
2396
|
|
|
2397
|
+
// Reuse canvas autmatically when destroyed
|
|
2398
|
+
const tileLayerCanvasCache = [];
|
|
2399
|
+
|
|
2400
|
+
/**
|
|
2401
|
+
* Tile layer data object stores info about how to render a tile
|
|
2402
|
+
* @example
|
|
2403
|
+
* // create tile layer data with tile index 0 and random orientation and color
|
|
2404
|
+
* const tileIndex = 0;
|
|
2405
|
+
* const direction = randInt(4)
|
|
2406
|
+
* const mirror = randInt(2);
|
|
2407
|
+
* const color = randColor();
|
|
2408
|
+
* const data = new TileLayerData(tileIndex, direction, mirror, color);
|
|
2409
|
+
*/
|
|
1497
2410
|
class TileLayerData
|
|
1498
2411
|
{
|
|
1499
|
-
|
|
2412
|
+
/** Create a tile layer data object, one for each tile in a TileLayer
|
|
2413
|
+
* @param {Number} [tile] - The tile to use, untextured if undefined
|
|
2414
|
+
* @param {Number} [direction=0] - Integer direction of tile, in 90 degree increments
|
|
2415
|
+
* @param {Boolean} [mirror=0] - If the tile should be mirrored along the x axis
|
|
2416
|
+
* @param {Color} [color=new Color(1,1,1)] - Color of the tile */
|
|
2417
|
+
constructor(tile, direction=0, mirror=0, color=new Color)
|
|
1500
2418
|
{
|
|
2419
|
+
/** @property {Number} - The tile to use, untextured if undefined */
|
|
1501
2420
|
this.tile = tile;
|
|
2421
|
+
/** @property {Number} - Integer direction of tile, in 90 degree increments */
|
|
1502
2422
|
this.direction = direction;
|
|
2423
|
+
/** @property {Boolean} - If the tile should be mirrored along the x axis */
|
|
1503
2424
|
this.mirror = mirror;
|
|
2425
|
+
/** @property {Color} - Color of the tile */
|
|
1504
2426
|
this.color = color;
|
|
1505
2427
|
}
|
|
2428
|
+
|
|
2429
|
+
/** Set this tile to clear, it will not be rendered */
|
|
1506
2430
|
clear() { this.tile = this.direction = this.mirror = 0; color = new Color; }
|
|
1507
2431
|
}
|
|
1508
2432
|
|
|
2433
|
+
/**
|
|
2434
|
+
* Tile layer object - cached rendering system for tile layers
|
|
2435
|
+
* <br> - Each Tile layer is rendered to an off screen canvas
|
|
2436
|
+
* <br> - Tile layers are not rendered using WebGL to allow modifications at run time
|
|
2437
|
+
* <br> - Tile layers are sorted
|
|
2438
|
+
* @extends EngineObject
|
|
2439
|
+
* @example
|
|
2440
|
+
* // create tile collision and visible tile layer
|
|
2441
|
+
* initTileCollision(vec2(200,100));
|
|
2442
|
+
* const tileLayer = new TileLayer();
|
|
2443
|
+
*/
|
|
1509
2444
|
class TileLayer extends EngineObject
|
|
1510
2445
|
{
|
|
1511
|
-
|
|
2446
|
+
/** Create a tile layer object
|
|
2447
|
+
* @param {Vector2} [position=new Vector2(0,0)] - World space position
|
|
2448
|
+
* @param {Vector2} [size=objectDefaultSize] - World space size
|
|
2449
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
|
|
2450
|
+
* @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
|
|
2451
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
2452
|
+
*/
|
|
2453
|
+
constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
|
|
1512
2454
|
{
|
|
1513
|
-
super(pos, size);
|
|
2455
|
+
super(pos, size, -1, tileSize, 0, undefined, renderOrder);
|
|
1514
2456
|
|
|
1515
|
-
|
|
2457
|
+
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
1516
2458
|
this.canvas = tileLayerCanvasCache.length ? tileLayerCanvasCache.pop() : document.createElement('canvas');
|
|
2459
|
+
/** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
1517
2460
|
this.context = this.canvas.getContext('2d');
|
|
2461
|
+
/** @property {Vector2} - How much to scale this layer when rendered */
|
|
1518
2462
|
this.scale = scale;
|
|
1519
|
-
|
|
1520
|
-
this.
|
|
1521
|
-
this.renderOrder = layer;
|
|
1522
|
-
this.flushGLBeforeRender = 1;
|
|
2463
|
+
/** @property {Boolean} [isOverlay=0] - If true this layer will render to overlay canvas and appear above all objects */
|
|
2464
|
+
this.isOverlay;
|
|
1523
2465
|
|
|
1524
2466
|
// init tile data
|
|
1525
2467
|
this.data = [];
|
|
@@ -1527,6 +2469,7 @@ class TileLayer extends EngineObject
|
|
|
1527
2469
|
this.data.push(new TileLayerData());
|
|
1528
2470
|
}
|
|
1529
2471
|
|
|
2472
|
+
/** Destroy this tile layer */
|
|
1530
2473
|
destroy()
|
|
1531
2474
|
{
|
|
1532
2475
|
// add canvas back to the cache
|
|
@@ -1534,6 +2477,10 @@ class TileLayer extends EngineObject
|
|
|
1534
2477
|
super.destroy();
|
|
1535
2478
|
}
|
|
1536
2479
|
|
|
2480
|
+
/** Set data at a given position in the array
|
|
2481
|
+
* @param {Vector2} position - Local position in array
|
|
2482
|
+
* @param {TileLayerData} data - Data to set
|
|
2483
|
+
* @param {Boolean} [redraw=0] - Force the tile to redraw if true */
|
|
1537
2484
|
setData(layerPos, data, redraw)
|
|
1538
2485
|
{
|
|
1539
2486
|
if (layerPos.arrayCheck(this.size))
|
|
@@ -1543,38 +2490,46 @@ class TileLayer extends EngineObject
|
|
|
1543
2490
|
}
|
|
1544
2491
|
}
|
|
1545
2492
|
|
|
2493
|
+
/** Get data at a given position in the array
|
|
2494
|
+
* @param {Vector2} layerPos - Local position in array
|
|
2495
|
+
* @return {TileLayerData} */
|
|
1546
2496
|
getData(layerPos)
|
|
1547
2497
|
{ return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]; }
|
|
1548
2498
|
|
|
1549
|
-
|
|
2499
|
+
// Tile layers are not updated
|
|
2500
|
+
update() {}
|
|
2501
|
+
|
|
2502
|
+
// Render the tile layer, called automatically by the engine
|
|
1550
2503
|
render()
|
|
1551
2504
|
{
|
|
1552
2505
|
ASSERT(mainContext != this.context); // must call redrawEnd() after drawing tiles
|
|
1553
2506
|
|
|
1554
|
-
// flush and copy gl canvas because tile canvas does not use
|
|
1555
|
-
|
|
2507
|
+
// flush and copy gl canvas because tile canvas does not use webgl
|
|
2508
|
+
glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
1556
2509
|
|
|
1557
2510
|
// draw the entire cached level onto the main canvas
|
|
1558
2511
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
1559
|
-
mainContext.drawImage
|
|
2512
|
+
(this.isOverlay ? overlayContext : mainContext).drawImage
|
|
1560
2513
|
(
|
|
1561
2514
|
this.canvas, pos.x, pos.y,
|
|
1562
2515
|
cameraScale*this.size.x*this.scale.x, cameraScale*this.size.y*this.scale.y
|
|
1563
2516
|
);
|
|
1564
2517
|
}
|
|
1565
2518
|
|
|
2519
|
+
/** Draw all the tile data to an offscreen canvas using webgl if possible */
|
|
1566
2520
|
redraw()
|
|
1567
2521
|
{
|
|
1568
|
-
// draw all the tile data to an offscreen canvas using webgl if possible
|
|
1569
2522
|
this.redrawStart();
|
|
1570
2523
|
this.drawAllTileData();
|
|
1571
2524
|
this.redrawEnd();
|
|
1572
2525
|
}
|
|
1573
2526
|
|
|
2527
|
+
/** Call to start the redraw process
|
|
2528
|
+
* @param {Boolean} [clear=1] - Should it clear the canvas before drawing */
|
|
1574
2529
|
redrawStart(clear = 1)
|
|
1575
2530
|
{
|
|
1576
2531
|
// clear and set size
|
|
1577
|
-
const width
|
|
2532
|
+
const width = this.size.x * this.tileSize.x;
|
|
1578
2533
|
const height = this.size.y * this.tileSize.y;
|
|
1579
2534
|
if (clear)
|
|
1580
2535
|
{
|
|
@@ -1595,6 +2550,7 @@ class TileLayer extends EngineObject
|
|
|
1595
2550
|
glPreRender(width, height);
|
|
1596
2551
|
}
|
|
1597
2552
|
|
|
2553
|
+
/** Call to end the redraw process */
|
|
1598
2554
|
redrawEnd()
|
|
1599
2555
|
{
|
|
1600
2556
|
ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
|
|
@@ -1605,18 +2561,24 @@ class TileLayer extends EngineObject
|
|
|
1605
2561
|
[mainCanvasSize, mainCanvas, mainContext, cameraScale, cameraPos] = this.savedRenderSettings;
|
|
1606
2562
|
}
|
|
1607
2563
|
|
|
2564
|
+
/** Draw the tile at a given position
|
|
2565
|
+
* @param {Vector2} layerPos */
|
|
1608
2566
|
drawTileData(layerPos)
|
|
1609
2567
|
{
|
|
1610
2568
|
// first clear out where the tile was
|
|
1611
|
-
const pos = layerPos.
|
|
2569
|
+
const pos = layerPos.floor().add(this.pos).add(vec2(.5));
|
|
1612
2570
|
this.drawCanvas2D(pos, vec2(1), 0, 0, (context)=>context.clearRect(-.5, -.5, 1, 1));
|
|
1613
2571
|
|
|
1614
|
-
// draw the tile
|
|
2572
|
+
// draw the tile if not undefined
|
|
1615
2573
|
const d = this.getData(layerPos);
|
|
1616
|
-
|
|
1617
|
-
|
|
2574
|
+
if (d.tile != undefined)
|
|
2575
|
+
{
|
|
2576
|
+
ASSERT(mainContext == this.context); // must call redrawStart() before drawing tiles
|
|
2577
|
+
drawTile(pos, vec2(1), d.tile, this.tileSize, d.color, d.direction*PI/2, d.mirror);
|
|
2578
|
+
}
|
|
1618
2579
|
}
|
|
1619
2580
|
|
|
2581
|
+
/** Draw all the tiles in this layer */
|
|
1620
2582
|
drawAllTileData()
|
|
1621
2583
|
{
|
|
1622
2584
|
for (let x = this.size.x; x--;)
|
|
@@ -1624,7 +2586,12 @@ class TileLayer extends EngineObject
|
|
|
1624
2586
|
this.drawTileData(vec2(x,y));
|
|
1625
2587
|
}
|
|
1626
2588
|
|
|
1627
|
-
|
|
2589
|
+
/** Draw directly to the 2d canvas in world space (bipass webgl)
|
|
2590
|
+
* @param {Vector2} pos
|
|
2591
|
+
* @param {Vector2} size
|
|
2592
|
+
* @param {Number} angle
|
|
2593
|
+
* @param {Boolean} mirror
|
|
2594
|
+
* @param {Function} drawFunction */
|
|
1628
2595
|
drawCanvas2D(pos, size, angle, mirror, drawFunction)
|
|
1629
2596
|
{
|
|
1630
2597
|
const context = this.context;
|
|
@@ -1638,8 +2605,15 @@ class TileLayer extends EngineObject
|
|
|
1638
2605
|
context.restore();
|
|
1639
2606
|
}
|
|
1640
2607
|
|
|
1641
|
-
|
|
1642
|
-
|
|
2608
|
+
/** Draw a tile directly onto the layer canvas
|
|
2609
|
+
* @param {Vector2} pos
|
|
2610
|
+
* @param {Vector2} [size=new Vector2(1,1)]
|
|
2611
|
+
* @param {Number} [tileIndex=-1]
|
|
2612
|
+
* @param {Vector2} [tileSize=tileSizeDefault]
|
|
2613
|
+
* @param {Color} [color=new Color(1,1,1)]
|
|
2614
|
+
* @param {Number} [angle=0]
|
|
2615
|
+
* @param {Boolean} [mirror=0] */
|
|
2616
|
+
drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, color=new Color, angle=0, mirror)
|
|
1643
2617
|
{
|
|
1644
2618
|
this.drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
1645
2619
|
{
|
|
@@ -1660,6 +2634,11 @@ class TileLayer extends EngineObject
|
|
|
1660
2634
|
});
|
|
1661
2635
|
}
|
|
1662
2636
|
|
|
2637
|
+
/** Draw a rectangle directly onto the layer canvas
|
|
2638
|
+
* @param {Vector2} pos
|
|
2639
|
+
* @param {Vector2} [size=new Vector2(1,1)]
|
|
2640
|
+
* @param {Color} [color=new Color(1,1,1)]
|
|
2641
|
+
* @param {Number} [angle=0] */
|
|
1663
2642
|
drawRect(pos, size, color, angle) { this.drawTile(pos, size, -1, 0, color, angle, 0); }
|
|
1664
2643
|
}
|
|
1665
2644
|
/*
|
|
@@ -1671,72 +2650,142 @@ class TileLayer extends EngineObject
|
|
|
1671
2650
|
|
|
1672
2651
|
'use strict';
|
|
1673
2652
|
|
|
2653
|
+
/**
|
|
2654
|
+
* Particle Emitter - Spawns particles with the given settings
|
|
2655
|
+
* @extends EngineObject
|
|
2656
|
+
* @example
|
|
2657
|
+
* // create a particle emitter
|
|
2658
|
+
* let pos = vec2(2,3);
|
|
2659
|
+
* let particleEmiter = new ParticleEmitter
|
|
2660
|
+
* (
|
|
2661
|
+
* pos, 1, 0, 500, PI, // pos, emitSize, emitTime, emitRate, emiteCone
|
|
2662
|
+
* 0, vec2(16), // tileIndex, tileSize
|
|
2663
|
+
* new Color, new Color(0,0,0), // colorStartA, colorStartB
|
|
2664
|
+
* new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
|
|
2665
|
+
* 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
2666
|
+
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
2667
|
+
* .5, 1 // randomness, collide, additive, randomColorLinear, renderOrder
|
|
2668
|
+
* );
|
|
2669
|
+
*/
|
|
1674
2670
|
class ParticleEmitter extends EngineObject
|
|
1675
2671
|
{
|
|
2672
|
+
/** Create a particle system with the given settings
|
|
2673
|
+
* @param {Vector2} position - World space position of the emitter
|
|
2674
|
+
* @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
2675
|
+
* @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
|
|
2676
|
+
* @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
|
|
2677
|
+
* @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
|
|
2678
|
+
* @param {Number} [tileIndex=-1] - Index into tile sheet, if <0 no texture is applied
|
|
2679
|
+
* @param {Number} [tileSize=tileSizeDefault] - Tile size for particles
|
|
2680
|
+
* @param {Color} [colorStartA=new Color(1,1,1)] - Color at start of life 1, randomized between start colors
|
|
2681
|
+
* @param {Color} [colorStartB=new Color(1,1,1)] - Color at start of life 2, randomized between start colors
|
|
2682
|
+
* @param {Color} [colorEndA=new Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
|
|
2683
|
+
* @param {Color} [colorEndB=new Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
|
|
2684
|
+
* @param {Number} [particleTime=.5] - How long particles live
|
|
2685
|
+
* @param {Number} [sizeStart=.1] - How big are particles at start
|
|
2686
|
+
* @param {Number} [sizeEnd=1] - How big are particles at end
|
|
2687
|
+
* @param {Number} [speed=.1] - How fast are particles when spawned
|
|
2688
|
+
* @param {Number} [angleSpeed=.05] - How fast are particles rotating
|
|
2689
|
+
* @param {Number} [damping=1] - How much to dampen particle speed
|
|
2690
|
+
* @param {Number} [angleDamping=1] - How much to dampen particle angular speed
|
|
2691
|
+
* @param {Number} [gravityScale=0] - How much does gravity effect particles
|
|
2692
|
+
* @param {Number} [particleConeAngle=PI] - Cone for start particle angle
|
|
2693
|
+
* @param {Number} [fadeRate=.1] - How quick to fade in particles at start/end in percent of life
|
|
2694
|
+
* @param {Number} [randomness=.2] - Apply extra randomness percent
|
|
2695
|
+
* @param {Boolean} [collideTiles=0] - Do particles collide against tiles
|
|
2696
|
+
* @param {Boolean} [additive=0] - Should particles use addtive blend
|
|
2697
|
+
* @param {Boolean} [randomColorLinear=0] - Should color be randomized linearly or across each component
|
|
2698
|
+
* @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
|
|
2699
|
+
*/
|
|
1676
2700
|
constructor
|
|
1677
2701
|
(
|
|
1678
|
-
pos,
|
|
1679
|
-
emitSize = 0,
|
|
1680
|
-
emitTime = 0,
|
|
1681
|
-
emitRate = 100,
|
|
1682
|
-
emitConeAngle = PI,
|
|
1683
|
-
tileIndex = -1,
|
|
1684
|
-
tileSize =
|
|
1685
|
-
colorStartA = new Color,
|
|
1686
|
-
colorStartB = new Color,
|
|
1687
|
-
colorEndA = new Color(1,1,1,0),
|
|
1688
|
-
colorEndB = new Color(1,1,1,0),
|
|
1689
|
-
particleTime = .5,
|
|
1690
|
-
sizeStart = .1,
|
|
1691
|
-
sizeEnd = 1,
|
|
1692
|
-
speed = .1,
|
|
1693
|
-
angleSpeed = .05,
|
|
1694
|
-
damping = 1,
|
|
1695
|
-
angleDamping = 1,
|
|
1696
|
-
gravityScale = 0,
|
|
1697
|
-
particleConeAngle = PI,
|
|
1698
|
-
fadeRate = .1,
|
|
1699
|
-
randomness = .2,
|
|
1700
|
-
collideTiles,
|
|
1701
|
-
additive,
|
|
1702
|
-
randomColorLinear = 1,
|
|
1703
|
-
renderOrder = additive ? 1e9 : 0
|
|
2702
|
+
pos,
|
|
2703
|
+
emitSize = 0,
|
|
2704
|
+
emitTime = 0,
|
|
2705
|
+
emitRate = 100,
|
|
2706
|
+
emitConeAngle = PI,
|
|
2707
|
+
tileIndex = -1,
|
|
2708
|
+
tileSize = tileSizeDefault,
|
|
2709
|
+
colorStartA = new Color,
|
|
2710
|
+
colorStartB = new Color,
|
|
2711
|
+
colorEndA = new Color(1,1,1,0),
|
|
2712
|
+
colorEndB = new Color(1,1,1,0),
|
|
2713
|
+
particleTime = .5,
|
|
2714
|
+
sizeStart = .1,
|
|
2715
|
+
sizeEnd = 1,
|
|
2716
|
+
speed = .1,
|
|
2717
|
+
angleSpeed = .05,
|
|
2718
|
+
damping = 1,
|
|
2719
|
+
angleDamping = 1,
|
|
2720
|
+
gravityScale = 0,
|
|
2721
|
+
particleConeAngle = PI,
|
|
2722
|
+
fadeRate = .1,
|
|
2723
|
+
randomness = .2,
|
|
2724
|
+
collideTiles,
|
|
2725
|
+
additive,
|
|
2726
|
+
randomColorLinear = 1,
|
|
2727
|
+
renderOrder = additive ? 1e9 : 0
|
|
1704
2728
|
)
|
|
1705
2729
|
{
|
|
1706
|
-
super(pos, new Vector2, tileIndex, tileSize);
|
|
2730
|
+
super(pos, new Vector2, tileIndex, tileSize, 0, undefined, renderOrder);
|
|
1707
2731
|
|
|
1708
2732
|
// emitter settings
|
|
2733
|
+
/** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
1709
2734
|
this.emitSize = emitSize
|
|
2735
|
+
/** @property {Number} - How long to stay alive (0 is forever) */
|
|
1710
2736
|
this.emitTime = emitTime;
|
|
2737
|
+
/** @property {Number} - How many particles per second to spawn, does not emit if 0 */
|
|
1711
2738
|
this.emitRate = emitRate;
|
|
2739
|
+
/** @property {Number} - Local angle to apply velocity to particles from emitter */
|
|
1712
2740
|
this.emitConeAngle = emitConeAngle;
|
|
1713
2741
|
|
|
1714
2742
|
// color settings
|
|
2743
|
+
/** @property {Color} - Color at start of life 1, randomized between start colors */
|
|
1715
2744
|
this.colorStartA = colorStartA;
|
|
2745
|
+
/** @property {Color} - Color at start of life 2, randomized between start colors */
|
|
1716
2746
|
this.colorStartB = colorStartB;
|
|
2747
|
+
/** @property {Color} - Color at end of life 1, randomized between end colors */
|
|
1717
2748
|
this.colorEndA = colorEndA;
|
|
2749
|
+
/** @property {Color} - Color at end of life 2, randomized between end colors */
|
|
1718
2750
|
this.colorEndB = colorEndB;
|
|
2751
|
+
/** @property {Boolean} - Should color be randomized linearly or across each component */
|
|
1719
2752
|
this.randomColorLinear = randomColorLinear;
|
|
1720
2753
|
|
|
1721
2754
|
// particle settings
|
|
2755
|
+
/** @property {Number} - How long particles live */
|
|
1722
2756
|
this.particleTime = particleTime;
|
|
2757
|
+
/** @property {Number} - How big are particles at start */
|
|
1723
2758
|
this.sizeStart = sizeStart;
|
|
2759
|
+
/** @property {Number} - How big are particles at end */
|
|
1724
2760
|
this.sizeEnd = sizeEnd;
|
|
2761
|
+
/** @property {Number} - How fast are particles when spawned */
|
|
1725
2762
|
this.speed = speed;
|
|
2763
|
+
/** @property {Number} - How fast are particles rotating */
|
|
1726
2764
|
this.angleSpeed = angleSpeed;
|
|
2765
|
+
/** @property {Number} - How much to dampen particle speed */
|
|
1727
2766
|
this.damping = damping;
|
|
2767
|
+
/** @property {Number} - How much to dampen particle angular speed */
|
|
1728
2768
|
this.angleDamping = angleDamping;
|
|
2769
|
+
/** @property {Number} - How much does gravity effect particles */
|
|
1729
2770
|
this.gravityScale = gravityScale;
|
|
2771
|
+
/** @property {Number} - Cone for start particle angle */
|
|
1730
2772
|
this.particleConeAngle = particleConeAngle;
|
|
2773
|
+
/** @property {Number} - How quick to fade in particles at start/end in percent of life */
|
|
1731
2774
|
this.fadeRate = fadeRate;
|
|
2775
|
+
/** @property {Number} - Apply extra randomness percent */
|
|
1732
2776
|
this.randomness = randomness;
|
|
2777
|
+
/** @property {Number} - Do particles collide against tiles */
|
|
1733
2778
|
this.collideTiles = collideTiles;
|
|
2779
|
+
/** @property {Number} - Should particles use addtive blend */
|
|
1734
2780
|
this.additive = additive;
|
|
1735
|
-
|
|
1736
|
-
this.trailScale =
|
|
2781
|
+
/** @property {Number} - If set the partile is drawn as a trail, stretched in the drection of velocity */
|
|
2782
|
+
this.trailScale = 0;
|
|
2783
|
+
|
|
2784
|
+
// internal variables
|
|
1737
2785
|
this.emitTimeBuffer = 0;
|
|
1738
2786
|
}
|
|
1739
2787
|
|
|
2788
|
+
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
1740
2789
|
update()
|
|
1741
2790
|
{
|
|
1742
2791
|
// only do default update to apply parent transforms
|
|
@@ -1759,6 +2808,8 @@ class ParticleEmitter extends EngineObject
|
|
|
1759
2808
|
debugParticles && debugRect(this.pos, vec2(this.emitSize), '#0f0', 0, this.angle);
|
|
1760
2809
|
}
|
|
1761
2810
|
|
|
2811
|
+
/** Spawn one particle
|
|
2812
|
+
* @return {Particle} */
|
|
1762
2813
|
emitParticle()
|
|
1763
2814
|
{
|
|
1764
2815
|
// spawn a particle
|
|
@@ -1810,16 +2861,27 @@ class ParticleEmitter extends EngineObject
|
|
|
1810
2861
|
return particle;
|
|
1811
2862
|
}
|
|
1812
2863
|
|
|
1813
|
-
|
|
2864
|
+
// Particle emitters are not rendered, only the particles are
|
|
2865
|
+
render() {}
|
|
1814
2866
|
}
|
|
1815
2867
|
|
|
1816
2868
|
///////////////////////////////////////////////////////////////////////////////
|
|
1817
|
-
|
|
1818
|
-
|
|
2869
|
+
/**
|
|
2870
|
+
* Particle Object - Created automatically by Particle Emitters
|
|
2871
|
+
* @extends EngineObject
|
|
2872
|
+
*/
|
|
1819
2873
|
class Particle extends EngineObject
|
|
1820
2874
|
{
|
|
2875
|
+
/**
|
|
2876
|
+
* Create a particle with the given settings
|
|
2877
|
+
* @param {Vector2} position - World space position of the particle
|
|
2878
|
+
* @param {Number} [tileIndex=-1] - Tile to use to render, untextured if -1
|
|
2879
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
2880
|
+
* @param {Number} [angle=0] - Angle to rotate the particle
|
|
2881
|
+
*/
|
|
1821
2882
|
constructor(pos, tileIndex, tileSize, angle) { super(pos, new Vector2, tileIndex, tileSize, angle); }
|
|
1822
2883
|
|
|
2884
|
+
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
1823
2885
|
render()
|
|
1824
2886
|
{
|
|
1825
2887
|
// modulate size and color
|
|
@@ -1861,27 +2923,64 @@ class Particle extends EngineObject
|
|
|
1861
2923
|
}
|
|
1862
2924
|
}
|
|
1863
2925
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
2926
|
+
/**
|
|
2927
|
+
* LittleJS Medal System
|
|
2928
|
+
* <br> - Tracks and displays medals
|
|
2929
|
+
* <br> - Saves medals to local storage
|
|
2930
|
+
* <br> - Newgrounds and OS13k integration
|
|
2931
|
+
* @namespace Medals
|
|
2932
|
+
*/
|
|
1870
2933
|
|
|
1871
2934
|
'use strict';
|
|
1872
2935
|
|
|
1873
|
-
|
|
1874
|
-
|
|
2936
|
+
/** List of all medals
|
|
2937
|
+
* @memberof Medals */
|
|
2938
|
+
const medals = [];
|
|
2939
|
+
|
|
2940
|
+
/** Set to stop medals from being unlockable (like if cheats are enabled)
|
|
2941
|
+
* @memberof Medals */
|
|
2942
|
+
let medalsPreventUnlock;
|
|
2943
|
+
|
|
2944
|
+
/** This can used to enable Newgrounds functionality
|
|
2945
|
+
* @type {Newgrounds}
|
|
2946
|
+
* @memberof Medals */
|
|
2947
|
+
let newgrounds;
|
|
1875
2948
|
|
|
1876
|
-
|
|
2949
|
+
// Engine internal variables not exposed to documentation
|
|
2950
|
+
let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimer;
|
|
2951
|
+
|
|
2952
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2953
|
+
|
|
2954
|
+
/** Initialize medals with a save name used for storage
|
|
2955
|
+
* <br> - Checks if medals are unlocked
|
|
2956
|
+
* <br> - Call this after creating all medals
|
|
2957
|
+
* @param {String} saveName
|
|
2958
|
+
* @memberof Medals */
|
|
2959
|
+
function medalsInit(saveName)
|
|
1877
2960
|
{
|
|
1878
2961
|
// check if medals are unlocked
|
|
1879
|
-
|
|
2962
|
+
medalsSaveName = saveName;
|
|
1880
2963
|
debugMedals || medals.forEach(medal=> medal.unlocked = localStorage[medal.storageKey()]);
|
|
1881
2964
|
}
|
|
1882
2965
|
|
|
2966
|
+
/**
|
|
2967
|
+
* Medal Object - Tracks an unlockable medal
|
|
2968
|
+
* @example
|
|
2969
|
+
* // create a medal
|
|
2970
|
+
* const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️');
|
|
2971
|
+
*
|
|
2972
|
+
* // unlock the medal
|
|
2973
|
+
* medal_example.unlock();
|
|
2974
|
+
*/
|
|
1883
2975
|
class Medal
|
|
1884
2976
|
{
|
|
2977
|
+
/** Create an medal object and adds it to the list of medals
|
|
2978
|
+
* @param {Number} id - The unique identifier of the medal
|
|
2979
|
+
* @param {String} name - Name of the medal
|
|
2980
|
+
* @param {String} [description] - Description of the medal
|
|
2981
|
+
* @param {String} [icon='🏆'] - Icon for the medal
|
|
2982
|
+
* @param {String} [src] - Image location for the medal
|
|
2983
|
+
*/
|
|
1885
2984
|
constructor(id, name, description='', icon='🏆', src)
|
|
1886
2985
|
{
|
|
1887
2986
|
ASSERT(id >= 0 && !medals[id]);
|
|
@@ -1900,26 +2999,25 @@ class Medal
|
|
|
1900
2999
|
}
|
|
1901
3000
|
}
|
|
1902
3001
|
|
|
3002
|
+
/** Unlocks a medal if not already unlocked */
|
|
1903
3003
|
unlock()
|
|
1904
3004
|
{
|
|
1905
3005
|
if (medalsPreventUnlock || this.unlocked)
|
|
1906
3006
|
return;
|
|
1907
3007
|
|
|
1908
3008
|
// save the medal
|
|
1909
|
-
ASSERT(
|
|
3009
|
+
ASSERT(medalsSaveName); // game name must be set
|
|
1910
3010
|
localStorage[this.storageKey()] = this.unlocked = 1;
|
|
1911
3011
|
medalsDisplayQueue.push(this);
|
|
1912
3012
|
|
|
1913
3013
|
// save for newgrounds and OS13K
|
|
1914
3014
|
newgrounds && newgrounds.unlockMedal(this.id);
|
|
1915
|
-
localStorage['OS13kTrophy,' + this.icon + ',' +
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
storageKey()
|
|
1919
|
-
{
|
|
1920
|
-
return medalsGameName + '_medal_' + this.id;
|
|
3015
|
+
localStorage['OS13kTrophy,' + this.icon + ',' + medalsSaveName + ',' + this.name] = this.description;
|
|
1921
3016
|
}
|
|
1922
3017
|
|
|
3018
|
+
/** Render a medal
|
|
3019
|
+
* @param {Number} [hidePercent=0] - How much to slide the medal off screen
|
|
3020
|
+
*/
|
|
1923
3021
|
render(hidePercent=0)
|
|
1924
3022
|
{
|
|
1925
3023
|
const context = overlayContext;
|
|
@@ -1940,25 +3038,36 @@ class Medal
|
|
|
1940
3038
|
|
|
1941
3039
|
// draw the text
|
|
1942
3040
|
context.textAlign = 'left';
|
|
1943
|
-
context.font = '3em '+
|
|
3041
|
+
context.font = '3em '+ fontDefault;
|
|
1944
3042
|
context.fillText(this.name, x+medalDisplayIconSize+25, y+35);
|
|
1945
|
-
context.font = '1.5em '+
|
|
3043
|
+
context.font = '1.5em '+ fontDefault;
|
|
1946
3044
|
context.restore(context.fillText(this.description, x+medalDisplayIconSize+25, y+70));
|
|
1947
3045
|
}
|
|
1948
3046
|
|
|
3047
|
+
/** Render the icon for a medal
|
|
3048
|
+
* @param {Number} x - Screen space X position
|
|
3049
|
+
* @param {Number} y - Screen space Y position
|
|
3050
|
+
* @param {Number} [size=medalDisplayIconSize] - Screen space size
|
|
3051
|
+
*/
|
|
1949
3052
|
renderIcon(x, y, size=medalDisplayIconSize)
|
|
1950
3053
|
{
|
|
1951
3054
|
// draw the image or icon
|
|
1952
3055
|
const context = overlayContext;
|
|
1953
3056
|
context.textAlign = 'center';
|
|
1954
3057
|
context.textBaseline = 'middle';
|
|
1955
|
-
context.font = size*.6 + 'px '+
|
|
3058
|
+
context.font = size*.6 + 'px '+ fontDefault;
|
|
1956
3059
|
context.fillStyle = '#000';
|
|
1957
3060
|
if (this.image)
|
|
1958
3061
|
context.drawImage(this.image, x-size/2, y-size/2, size, size);
|
|
1959
3062
|
else
|
|
1960
3063
|
context.fillText(this.icon, x, y); // show icon if there is no image
|
|
1961
3064
|
}
|
|
3065
|
+
|
|
3066
|
+
// Get local storage key used by the medal
|
|
3067
|
+
storageKey()
|
|
3068
|
+
{
|
|
3069
|
+
return medalsSaveName + '_medal_' + this.id;
|
|
3070
|
+
}
|
|
1962
3071
|
}
|
|
1963
3072
|
|
|
1964
3073
|
// engine automatically renders medals
|
|
@@ -1969,9 +3078,9 @@ function medalsRender()
|
|
|
1969
3078
|
|
|
1970
3079
|
// update first medal in queue
|
|
1971
3080
|
const medal = medalsDisplayQueue[0];
|
|
1972
|
-
const time =
|
|
3081
|
+
const time = timeReal - medalsDisplayTimer;
|
|
1973
3082
|
if (!medalsDisplayTimer)
|
|
1974
|
-
medalsDisplayTimer =
|
|
3083
|
+
medalsDisplayTimer = timeReal;
|
|
1975
3084
|
else if (time > medalDisplayTime)
|
|
1976
3085
|
medalsDisplayQueue.shift(medalsDisplayTimer = 0);
|
|
1977
3086
|
else
|
|
@@ -1986,15 +3095,26 @@ function medalsRender()
|
|
|
1986
3095
|
}
|
|
1987
3096
|
|
|
1988
3097
|
///////////////////////////////////////////////////////////////////////////////
|
|
1989
|
-
// Newgrounds API wrapper
|
|
1990
3098
|
|
|
3099
|
+
/**
|
|
3100
|
+
* Newgrounds API wrapper object
|
|
3101
|
+
* @example
|
|
3102
|
+
* // create a newgrounds object, replace the app id and cipher with your own
|
|
3103
|
+
* const app_id = '53123:1ZuSTQ9l';
|
|
3104
|
+
* const cipher = 'enF0vGH@Mj/FRASKL23Q==';
|
|
3105
|
+
* newgrounds = new Newgrounds(app_id, cipher);
|
|
3106
|
+
*/
|
|
1991
3107
|
class Newgrounds
|
|
1992
3108
|
{
|
|
3109
|
+
/** Create a newgrounds object
|
|
3110
|
+
* @param {Number} app_id - The newgrounds App ID
|
|
3111
|
+
* @param {String} [cipher] - The encryption Key (AES-128/Base64) */
|
|
1993
3112
|
constructor(app_id, cipher)
|
|
1994
3113
|
{
|
|
1995
3114
|
ASSERT(!newgrounds && app_id);
|
|
1996
3115
|
this.app_id = app_id;
|
|
1997
3116
|
this.cipher = cipher;
|
|
3117
|
+
this.host = location ? location.hostname : '';
|
|
1998
3118
|
|
|
1999
3119
|
// create an instance of CryptoJS for encrypted calls
|
|
2000
3120
|
cipher && (this.cryptoJS = CryptoJS());
|
|
@@ -2016,11 +3136,16 @@ class Newgrounds
|
|
|
2016
3136
|
if (medal)
|
|
2017
3137
|
{
|
|
2018
3138
|
// copy newgrounds medal data
|
|
2019
|
-
medal.
|
|
3139
|
+
medal.image = new Image();
|
|
3140
|
+
medal.image.src = newgroundsMedal['icon'];
|
|
3141
|
+
medal.name = newgroundsMedal['name'];
|
|
2020
3142
|
medal.description = newgroundsMedal['description'];
|
|
2021
|
-
medal.unlocked =
|
|
2022
|
-
medal.
|
|
2023
|
-
medal.
|
|
3143
|
+
medal.unlocked = newgroundsMedal['unlocked'];
|
|
3144
|
+
medal.difficulty = newgroundsMedal['difficulty'];
|
|
3145
|
+
medal.value = newgroundsMedal['value'];
|
|
3146
|
+
|
|
3147
|
+
if (medal.value)
|
|
3148
|
+
medal.description = medal.description + ' (' + medal.value + ')';
|
|
2024
3149
|
}
|
|
2025
3150
|
}
|
|
2026
3151
|
|
|
@@ -2030,30 +3155,55 @@ class Newgrounds
|
|
|
2030
3155
|
debugMedals && console.log(this.scoreboards);
|
|
2031
3156
|
}
|
|
2032
3157
|
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
3158
|
+
/** Send message to unlock a medal by id
|
|
3159
|
+
* @param {Number} id - The medal id */
|
|
3160
|
+
unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, 1); }
|
|
3161
|
+
|
|
3162
|
+
/** Send message to post score
|
|
3163
|
+
* @param {Number} id - The scoreboard id
|
|
3164
|
+
* @param {Number} value - The score value */
|
|
3165
|
+
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
|
|
3166
|
+
|
|
3167
|
+
/** Send message to log a view */
|
|
3168
|
+
logView() { return this.call('App.logView', {'host':this.host}, 1); }
|
|
3169
|
+
|
|
3170
|
+
/** Get scores from a scoreboard
|
|
3171
|
+
* @param {Number} id - The scoreboard id
|
|
3172
|
+
* @param {String} [user=0] - A user's id or name
|
|
3173
|
+
* @param {Number} [social=0] - If true, only social scores will be loaded
|
|
3174
|
+
* @param {Number} [skip=0] - Number of scores to skip before start
|
|
3175
|
+
* @param {Number} [limit=10] - Number of scores to include in the list
|
|
3176
|
+
* @return {Object} - The response JSON object
|
|
3177
|
+
*/
|
|
2043
3178
|
getScores(id, user=0, social=0, skip=0, limit=10)
|
|
2044
|
-
{
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
3179
|
+
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
3180
|
+
|
|
3181
|
+
/** Send a message to call a component of the Newgrounds API
|
|
3182
|
+
* @param {String} component - Name of the component
|
|
3183
|
+
* @param {Object} [parameters=0] - Parameters to use for call
|
|
3184
|
+
* @param {Boolean} [async=0] - If true, wait for response before continuing (will cause stall)
|
|
3185
|
+
* @return {Object} - The response JSON object
|
|
3186
|
+
*/
|
|
2049
3187
|
call(component, parameters=0, async=0)
|
|
2050
3188
|
{
|
|
3189
|
+
const call = {'component':component, 'parameters':parameters};
|
|
3190
|
+
if (this.cipher)
|
|
3191
|
+
{
|
|
3192
|
+
// encrypt using AES-128 Base64 with cryptoJS
|
|
3193
|
+
const cryptoJS = this.cryptoJS;
|
|
3194
|
+
const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
|
|
3195
|
+
const iv = cryptoJS['lib']['WordArray']['random'](16);
|
|
3196
|
+
const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
|
|
3197
|
+
call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
|
|
3198
|
+
call['parameters'] = 0;
|
|
3199
|
+
}
|
|
3200
|
+
|
|
2051
3201
|
// build the input object
|
|
2052
3202
|
const input =
|
|
2053
3203
|
{
|
|
2054
3204
|
'app_id': this.app_id,
|
|
2055
3205
|
'session_id': this.session_id,
|
|
2056
|
-
'call':
|
|
3206
|
+
'call': call
|
|
2057
3207
|
};
|
|
2058
3208
|
|
|
2059
3209
|
// build post data
|
|
@@ -2068,21 +3218,6 @@ class Newgrounds
|
|
|
2068
3218
|
debugMedals && console.log(xmlHttp.responseText);
|
|
2069
3219
|
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
2070
3220
|
}
|
|
2071
|
-
|
|
2072
|
-
encryptCall(call)
|
|
2073
|
-
{
|
|
2074
|
-
if (!this.cipher)
|
|
2075
|
-
return call;
|
|
2076
|
-
|
|
2077
|
-
// encrypt using AES-128 Base64 with cryptoJS
|
|
2078
|
-
const cryptoJS = this.cryptoJS;
|
|
2079
|
-
const aesKey = cryptoJS['enc']['Base64']['parse'](this.cipher);
|
|
2080
|
-
const iv = cryptoJS['lib']['WordArray']['random'](16);
|
|
2081
|
-
const encrypted = cryptoJS['AES']['encrypt'](JSON.stringify(call), aesKey, {'iv':iv});
|
|
2082
|
-
call['secure'] = cryptoJS['enc']['Base64']['stringify'](iv.concat(encrypted['ciphertext']));
|
|
2083
|
-
call['parameters'] = 0;
|
|
2084
|
-
return call;
|
|
2085
|
-
}
|
|
2086
3221
|
}
|
|
2087
3222
|
|
|
2088
3223
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -2090,21 +3225,40 @@ class Newgrounds
|
|
|
2090
3225
|
// Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
|
|
2091
3226
|
|
|
2092
3227
|
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));
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
3228
|
+
/**
|
|
3229
|
+
* LittleJS WebGL Interface
|
|
3230
|
+
* <br> - All webgl used by the engine is wrapped up here
|
|
3231
|
+
* <br> - Can be disabled with glEnable to revert to 2D canvas rendering
|
|
3232
|
+
* <br> - Batches sprite rendering on GPU for incredibly fast performance
|
|
3233
|
+
* <br> - Sprite transform math is done in the shader where possible
|
|
3234
|
+
* <br> - For normal stuff you won't need to call any functions in this file
|
|
3235
|
+
* <br> - For advanced stuff there are helper functions to create shaders, textures, etc
|
|
3236
|
+
* @namespace WebGL
|
|
3237
|
+
*/
|
|
2102
3238
|
|
|
2103
3239
|
'use strict';
|
|
2104
3240
|
|
|
2105
|
-
|
|
2106
|
-
|
|
3241
|
+
/** The WebGL canvas which appears above the main canvas and below the overlay canvas
|
|
3242
|
+
* @type {HTMLCanvasElement}
|
|
3243
|
+
* @memberof WebGL */
|
|
3244
|
+
let glCanvas;
|
|
3245
|
+
|
|
3246
|
+
/** 2d context for glCanvas
|
|
3247
|
+
* @type {WebGLRenderingContext}
|
|
3248
|
+
* @memberof WebGL */
|
|
3249
|
+
let glContext;
|
|
3250
|
+
|
|
3251
|
+
/** Main tile sheet texture automatically loaded by engine
|
|
3252
|
+
* @type {WebGLTexture}
|
|
3253
|
+
* @memberof WebGL */
|
|
3254
|
+
let glTileTexture;
|
|
2107
3255
|
|
|
3256
|
+
// WebGL internal variables not exposed to documentation
|
|
3257
|
+
let glActiveTexture, glShader, glPositionData, glColorData, glBatchCount, glBatchAdditive, glDirty, glAdditive;
|
|
3258
|
+
|
|
3259
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3260
|
+
|
|
3261
|
+
// Init WebGL, called automatically by the engine
|
|
2108
3262
|
function glInit()
|
|
2109
3263
|
{
|
|
2110
3264
|
if (!glEnable) return;
|
|
@@ -2116,7 +3270,7 @@ function glInit()
|
|
|
2116
3270
|
|
|
2117
3271
|
if (glOverlay)
|
|
2118
3272
|
{
|
|
2119
|
-
//
|
|
3273
|
+
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
2120
3274
|
document.body.appendChild(glCanvas);
|
|
2121
3275
|
glCanvas.style = mainCanvas.style.cssText;
|
|
2122
3276
|
}
|
|
@@ -2167,20 +3321,21 @@ function glInit()
|
|
|
2167
3321
|
initVertexAttribArray('b', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
2168
3322
|
}
|
|
2169
3323
|
|
|
3324
|
+
/** Set the WebGl blend mode, normally you should call setBlendMode instead
|
|
3325
|
+
* @param {Boolean} [additive=0]
|
|
3326
|
+
* @memberof WebGL */
|
|
2170
3327
|
function glSetBlendMode(additive)
|
|
2171
3328
|
{
|
|
2172
3329
|
if (!glEnable) return;
|
|
2173
3330
|
|
|
2174
|
-
if (additive != glAdditive)
|
|
2175
|
-
glFlush();
|
|
2176
|
-
|
|
2177
3331
|
// setup blending
|
|
2178
3332
|
glAdditive = additive;
|
|
2179
|
-
const destBlend = additive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
|
|
2180
|
-
glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
|
|
2181
|
-
glContext.enable(gl_BLEND);
|
|
2182
3333
|
}
|
|
2183
3334
|
|
|
3335
|
+
/** Set the WebGl texture, not normally necessary unless multiple tile sheets are used
|
|
3336
|
+
* <br> - This may also flush the gl buffer resulting in more draw calls and worse performance
|
|
3337
|
+
* @param {WebGLTexture} [texture=glTileTexture]
|
|
3338
|
+
* @memberof WebGL */
|
|
2184
3339
|
function glSetTexture(texture=glTileTexture)
|
|
2185
3340
|
{
|
|
2186
3341
|
if (!glEnable) return;
|
|
@@ -2191,6 +3346,11 @@ function glSetTexture(texture=glTileTexture)
|
|
|
2191
3346
|
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = texture);
|
|
2192
3347
|
}
|
|
2193
3348
|
|
|
3349
|
+
/** Compile WebGL shader of the given type, will throw errors if in debug mode
|
|
3350
|
+
* @param {String} source
|
|
3351
|
+
* @param type
|
|
3352
|
+
* @return {WebGLShader}
|
|
3353
|
+
* @memberof WebGL */
|
|
2194
3354
|
function glCompileShader(source, type)
|
|
2195
3355
|
{
|
|
2196
3356
|
if (!glEnable) return;
|
|
@@ -2206,6 +3366,11 @@ function glCompileShader(source, type)
|
|
|
2206
3366
|
return shader;
|
|
2207
3367
|
}
|
|
2208
3368
|
|
|
3369
|
+
/** Create WebGL program with given shaders
|
|
3370
|
+
* @param {WebGLShader} vsSource
|
|
3371
|
+
* @param {WebGLShader} fsSource
|
|
3372
|
+
* @return {WebGLProgram}
|
|
3373
|
+
* @memberof WebGL */
|
|
2209
3374
|
function glCreateProgram(vsSource, fsSource)
|
|
2210
3375
|
{
|
|
2211
3376
|
if (!glEnable) return;
|
|
@@ -2222,6 +3387,12 @@ function glCreateProgram(vsSource, fsSource)
|
|
|
2222
3387
|
return program;
|
|
2223
3388
|
}
|
|
2224
3389
|
|
|
3390
|
+
/** Create WebGL buffer
|
|
3391
|
+
* @param bufferType
|
|
3392
|
+
* @param size
|
|
3393
|
+
* @param usage
|
|
3394
|
+
* @return {WebGLBuffer}
|
|
3395
|
+
* @memberof WebGL */
|
|
2225
3396
|
function glCreateBuffer(bufferType, size, usage)
|
|
2226
3397
|
{
|
|
2227
3398
|
if (!glEnable) return;
|
|
@@ -2233,15 +3404,19 @@ function glCreateBuffer(bufferType, size, usage)
|
|
|
2233
3404
|
return buffer;
|
|
2234
3405
|
}
|
|
2235
3406
|
|
|
3407
|
+
/** Create WebGL texture from an image and set the texture settings
|
|
3408
|
+
* @param {Image} image
|
|
3409
|
+
* @return {WebGLTexture}
|
|
3410
|
+
* @memberof WebGL */
|
|
2236
3411
|
function glCreateTexture(image)
|
|
2237
3412
|
{
|
|
2238
|
-
if (!glEnable) return;
|
|
3413
|
+
if (!glEnable || !image || !image.width) return;
|
|
2239
3414
|
|
|
2240
3415
|
// build the texture
|
|
2241
3416
|
const texture = glContext.createTexture();
|
|
2242
3417
|
glContext.bindTexture(gl_TEXTURE_2D, texture);
|
|
2243
3418
|
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
2244
|
-
|
|
3419
|
+
|
|
2245
3420
|
// use point filtering for pixelated rendering
|
|
2246
3421
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, pixelated ? gl_NEAREST : gl_LINEAR);
|
|
2247
3422
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, pixelated ? gl_NEAREST : gl_LINEAR);
|
|
@@ -2250,6 +3425,7 @@ function glCreateTexture(image)
|
|
|
2250
3425
|
return texture;
|
|
2251
3426
|
}
|
|
2252
3427
|
|
|
3428
|
+
// called automatically by engine before render
|
|
2253
3429
|
function glPreRender(width, height)
|
|
2254
3430
|
{
|
|
2255
3431
|
if (!glEnable) return;
|
|
@@ -2277,24 +3453,33 @@ function glPreRender(width, height)
|
|
|
2277
3453
|
);
|
|
2278
3454
|
}
|
|
2279
3455
|
|
|
3456
|
+
/** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
|
|
3457
|
+
* @memberof WebGL */
|
|
2280
3458
|
function glFlush()
|
|
2281
3459
|
{
|
|
2282
3460
|
if (!glEnable || !glBatchCount) return;
|
|
2283
3461
|
|
|
3462
|
+
const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
|
|
3463
|
+
glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
|
|
3464
|
+
glContext.enable(gl_BLEND);
|
|
3465
|
+
|
|
2284
3466
|
// draw all the sprites in the batch and reset the buffer
|
|
2285
3467
|
glContext.bufferSubData(gl_ARRAY_BUFFER, 0,
|
|
2286
3468
|
glPositionData.subarray(0, glBatchCount * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT));
|
|
2287
3469
|
glContext.drawArrays(gl_TRIANGLES, 0, glBatchCount * gl_VERTICES_PER_QUAD);
|
|
2288
3470
|
glBatchCount = 0;
|
|
3471
|
+
glBatchAdditive = glAdditive;
|
|
2289
3472
|
}
|
|
2290
3473
|
|
|
3474
|
+
/** Draw any sprites still in the buffer, copy to main canvas and clear
|
|
3475
|
+
* @param {CanvasRenderingContext2D} context
|
|
3476
|
+
* @param {Boolean} [forceDraw=0]
|
|
3477
|
+
* @memberof WebGL */
|
|
2291
3478
|
function glCopyToContext(context, forceDraw)
|
|
2292
3479
|
{
|
|
2293
3480
|
if (!glEnable || !glDirty) return;
|
|
2294
3481
|
|
|
2295
|
-
// draw any sprites still in the buffer, copy to main canvas and clear
|
|
2296
3482
|
glFlush();
|
|
2297
|
-
|
|
2298
3483
|
if (!glOverlay || forceDraw)
|
|
2299
3484
|
{
|
|
2300
3485
|
// do not draw/clear in overlay mode because the canvas is visible
|
|
@@ -2303,12 +3488,25 @@ function glCopyToContext(context, forceDraw)
|
|
|
2303
3488
|
}
|
|
2304
3489
|
}
|
|
2305
3490
|
|
|
2306
|
-
|
|
3491
|
+
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
3492
|
+
* @param x
|
|
3493
|
+
* @param y
|
|
3494
|
+
* @param sizeX
|
|
3495
|
+
* @param sizeY
|
|
3496
|
+
* @param [angle=0]
|
|
3497
|
+
* @param [uv0X=0]
|
|
3498
|
+
* @param [uv0Y=0]
|
|
3499
|
+
* @param [uv1X=1]
|
|
3500
|
+
* @param [uv1Y=1]
|
|
3501
|
+
* @param [rgba=0xffffffff]
|
|
3502
|
+
* @param [rgbaAdditive=0]
|
|
3503
|
+
* @memberof WebGL */
|
|
3504
|
+
function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgba=0xffffffff, rgbaAdditive=0)
|
|
2307
3505
|
{
|
|
2308
3506
|
if (!glEnable) return;
|
|
2309
|
-
|
|
2310
|
-
// flush if there is no room for more verts
|
|
2311
|
-
if (glBatchCount == gl_MAX_BATCH)
|
|
3507
|
+
|
|
3508
|
+
// flush if there is no room for more verts or if different blend mode
|
|
3509
|
+
if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
|
|
2312
3510
|
glFlush();
|
|
2313
3511
|
|
|
2314
3512
|
// setup 2 triangles to form a quad
|
|
@@ -2317,69 +3515,45 @@ function glDraw(x, y, sizeX, sizeY, angle=0, uv0X=0, uv0Y=0, uv1X=1, uv1Y=1, rgb
|
|
|
2317
3515
|
|
|
2318
3516
|
// vertex 0
|
|
2319
3517
|
glPositionData[offset++] = angle;
|
|
2320
|
-
glPositionData[offset++] = x;
|
|
2321
|
-
glPositionData[offset++] =
|
|
2322
|
-
glPositionData[offset++] =
|
|
2323
|
-
|
|
2324
|
-
glPositionData[offset++] = uv0X;
|
|
2325
|
-
glPositionData[offset++] = uv1Y;
|
|
2326
|
-
glColorData[offset++] = rgba;
|
|
2327
|
-
glColorData[offset++] = rgbaAdditive;
|
|
3518
|
+
glPositionData[offset++] = x; glPositionData[offset++] = y;
|
|
3519
|
+
glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
|
|
3520
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3521
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
2328
3522
|
|
|
2329
3523
|
// vertex 1
|
|
2330
3524
|
glPositionData[offset++] = angle;
|
|
2331
|
-
glPositionData[offset++] = x;
|
|
2332
|
-
glPositionData[offset++] =
|
|
2333
|
-
glPositionData[offset++] =
|
|
2334
|
-
|
|
2335
|
-
glPositionData[offset++] = uv1X;
|
|
2336
|
-
glPositionData[offset++] = uv0Y;
|
|
2337
|
-
glColorData[offset++] = rgba;
|
|
2338
|
-
glColorData[offset++] = rgbaAdditive;
|
|
3525
|
+
glPositionData[offset++] = x; glPositionData[offset++] = y;
|
|
3526
|
+
glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
|
|
3527
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3528
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
2339
3529
|
|
|
2340
3530
|
// vertex 2
|
|
2341
3531
|
glPositionData[offset++] = angle;
|
|
2342
|
-
glPositionData[offset++] = x;
|
|
2343
|
-
glPositionData[offset++] =
|
|
2344
|
-
glPositionData[offset++] =
|
|
2345
|
-
|
|
2346
|
-
glPositionData[offset++] = uv0X;
|
|
2347
|
-
glPositionData[offset++] = uv0Y;
|
|
2348
|
-
glColorData[offset++] = rgba;
|
|
2349
|
-
glColorData[offset++] = rgbaAdditive;
|
|
3532
|
+
glPositionData[offset++] = x; glPositionData[offset++] = y;
|
|
3533
|
+
glPositionData[offset++] = -sizeX; glPositionData[offset++] = sizeY;
|
|
3534
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
|
|
3535
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
2350
3536
|
|
|
2351
3537
|
// vertex 0
|
|
2352
3538
|
glPositionData[offset++] = angle;
|
|
2353
|
-
glPositionData[offset++] = x;
|
|
2354
|
-
glPositionData[offset++] =
|
|
2355
|
-
glPositionData[offset++] =
|
|
2356
|
-
|
|
2357
|
-
glPositionData[offset++] = uv0X;
|
|
2358
|
-
glPositionData[offset++] = uv1Y;
|
|
2359
|
-
glColorData[offset++] = rgba;
|
|
2360
|
-
glColorData[offset++] = rgbaAdditive;
|
|
3539
|
+
glPositionData[offset++] = x; glPositionData[offset++] = y;
|
|
3540
|
+
glPositionData[offset++] = -sizeX; glPositionData[offset++] = -sizeY;
|
|
3541
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3542
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
2361
3543
|
|
|
2362
3544
|
// vertex 3
|
|
2363
3545
|
glPositionData[offset++] = angle;
|
|
2364
|
-
glPositionData[offset++] = x;
|
|
2365
|
-
glPositionData[offset++] =
|
|
2366
|
-
glPositionData[offset++] =
|
|
2367
|
-
|
|
2368
|
-
glPositionData[offset++] = uv1X;
|
|
2369
|
-
glPositionData[offset++] = uv1Y;
|
|
2370
|
-
glColorData[offset++] = rgba;
|
|
2371
|
-
glColorData[offset++] = rgbaAdditive;
|
|
3546
|
+
glPositionData[offset++] = x; glPositionData[offset++] = y;
|
|
3547
|
+
glPositionData[offset++] = sizeX; glPositionData[offset++] = -sizeY;
|
|
3548
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
|
|
3549
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
2372
3550
|
|
|
2373
3551
|
// vertex 1
|
|
2374
3552
|
glPositionData[offset++] = angle;
|
|
2375
|
-
glPositionData[offset++] = x;
|
|
2376
|
-
glPositionData[offset++] =
|
|
2377
|
-
glPositionData[offset++] =
|
|
2378
|
-
|
|
2379
|
-
glPositionData[offset++] = uv1X;
|
|
2380
|
-
glPositionData[offset++] = uv0Y;
|
|
2381
|
-
glColorData[offset++] = rgba;
|
|
2382
|
-
glColorData[offset++] = rgbaAdditive;
|
|
3553
|
+
glPositionData[offset++] = x; glPositionData[offset++] = y;
|
|
3554
|
+
glPositionData[offset++] = sizeX; glPositionData[offset++] = sizeY;
|
|
3555
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3556
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
2383
3557
|
}
|
|
2384
3558
|
|
|
2385
3559
|
///////////////////////////////////////////////////////////////////////////////
|