littlejsengine 1.8.8 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -17
- package/build/littlejs.d.ts +316 -254
- package/build/littlejs.esm.js +727 -676
- package/build/littlejs.esm.min.js +1 -1
- package/build/littlejs.js +726 -676
- package/build/littlejs.min.js +1 -1
- package/build/littlejs.release.js +661 -612
- package/examples/breakout/game.js +4 -4
- package/examples/breakout/index.html +3 -3
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/electron/game.js +1 -1
- package/examples/electron/index.html +2 -2
- package/examples/favicon.png +0 -0
- package/examples/js13k/index.html +13 -13
- package/examples/logo.png +0 -0
- package/examples/module/game.js +1 -1
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +1 -1
- package/examples/platformer/game.js +6 -6
- package/examples/platformer/gameCharacter.js +293 -0
- package/examples/platformer/gameEffects.js +8 -5
- package/examples/platformer/gameObjects.js +13 -13
- package/examples/platformer/gamePlayer.js +9 -293
- package/examples/platformer/index.html +7 -6
- package/examples/puzzle/game.js +3 -2
- package/examples/puzzle/index.html +2 -2
- package/examples/starter/game.js +3 -3
- package/examples/starter/index.html +13 -13
- package/examples/starter/tiles.png +0 -0
- package/examples/stress/index.html +34 -26
- package/examples/typescript/index.html +1 -1
- package/package.json +1 -1
- package/src/engine.js +50 -47
- package/src/engineAudio.js +126 -112
- package/src/engineDebug.js +66 -65
- package/src/engineDraw.js +47 -56
- package/src/engineExport.js +1 -0
- package/src/engineInput.js +57 -40
- package/src/engineMedals.js +32 -29
- package/src/engineObject.js +41 -26
- package/src/engineParticles.js +101 -75
- package/src/engineRelease.js +1 -1
- package/src/engineSettings.js +22 -22
- package/src/engineTileLayer.js +34 -33
- package/src/engineUtilities.js +44 -44
- package/src/engineWebGL.js +105 -126
- package/src/jsconfig.json +10 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
let showWatermark = 0;
|
|
14
|
-
let debugKey =
|
|
14
|
+
let debugKey = '';
|
|
15
15
|
const debug = 0;
|
|
16
16
|
const debugOverlay = 0;
|
|
17
17
|
const debugPhysics = 0;
|
|
@@ -79,15 +79,15 @@ function sign(value) { return Math.sign(value); }
|
|
|
79
79
|
|
|
80
80
|
/** Returns first parm modulo the second param, but adjusted so negative numbers work as expected
|
|
81
81
|
* @param {Number} dividend
|
|
82
|
-
* @param {Number} [divisor
|
|
82
|
+
* @param {Number} [divisor]
|
|
83
83
|
* @return {Number}
|
|
84
84
|
* @memberof Utilities */
|
|
85
85
|
function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
|
|
86
86
|
|
|
87
87
|
/** Clamps the value beween max and min
|
|
88
88
|
* @param {Number} value
|
|
89
|
-
* @param {Number} [min
|
|
90
|
-
* @param {Number} [max
|
|
89
|
+
* @param {Number} [min]
|
|
90
|
+
* @param {Number} [max]
|
|
91
91
|
* @return {Number}
|
|
92
92
|
* @memberof Utilities */
|
|
93
93
|
function clamp(value, min=0, max=1) { return value < min ? min : value > max ? max : value; }
|
|
@@ -112,7 +112,7 @@ function lerp(percent, valueA, valueB) { return valueA + clamp(percent) * (value
|
|
|
112
112
|
/** Returns signed wrapped distance between the two values passed in
|
|
113
113
|
* @param {Number} valueA
|
|
114
114
|
* @param {Number} valueB
|
|
115
|
-
* @param {Number} [wrapSize
|
|
115
|
+
* @param {Number} [wrapSize]
|
|
116
116
|
* @returns {Number}
|
|
117
117
|
* @memberof Utilities */
|
|
118
118
|
function distanceWrap(valueA, valueB, wrapSize=1)
|
|
@@ -122,7 +122,7 @@ function distanceWrap(valueA, valueB, wrapSize=1)
|
|
|
122
122
|
* @param {Number} percent
|
|
123
123
|
* @param {Number} valueA
|
|
124
124
|
* @param {Number} valueB
|
|
125
|
-
* @param {Number} [wrapSize
|
|
125
|
+
* @param {Number} [wrapSize]
|
|
126
126
|
* @returns {Number}
|
|
127
127
|
* @memberof Utilities */
|
|
128
128
|
function lerpWrap(percent, valueA, valueB, wrapSize=1)
|
|
@@ -133,7 +133,7 @@ function lerpWrap(percent, valueA, valueB, wrapSize=1)
|
|
|
133
133
|
* @param {Number} angleB
|
|
134
134
|
* @returns {Number}
|
|
135
135
|
* @memberof Utilities */
|
|
136
|
-
function distanceAngle(angleA, angleB) { distanceWrap(angleA, angleB, 2*PI); }
|
|
136
|
+
function distanceAngle(angleA, angleB) { return distanceWrap(angleA, angleB, 2*PI); }
|
|
137
137
|
|
|
138
138
|
/** Linearly interpolates between the angles passed in with wrappping
|
|
139
139
|
* @param {Number} percent
|
|
@@ -169,10 +169,10 @@ function isOverlapping(pointA, sizeA, pointB, sizeB)
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
|
|
172
|
-
* @param {Number} [frequency
|
|
173
|
-
* @param {Number} [amplitude
|
|
174
|
-
* @param {Number} [t=time]
|
|
175
|
-
* @return {Number}
|
|
172
|
+
* @param {Number} [frequency] - Frequency of the wave in Hz
|
|
173
|
+
* @param {Number} [amplitude] - Amplitude (max height) of the wave
|
|
174
|
+
* @param {Number} [t=time] - Value to use for time of the wave
|
|
175
|
+
* @return {Number} - Value waving between 0 and amplitude
|
|
176
176
|
* @memberof Utilities */
|
|
177
177
|
function wave(frequency=1, amplitude=1, t=time)
|
|
178
178
|
{ return amplitude/2 * (1 - Math.cos(t*frequency*2*PI)); }
|
|
@@ -189,15 +189,15 @@ function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
|
|
|
189
189
|
* @namespace Random */
|
|
190
190
|
|
|
191
191
|
/** Returns a random value between the two values passed in
|
|
192
|
-
* @param {Number} [valueA
|
|
193
|
-
* @param {Number} [valueB
|
|
192
|
+
* @param {Number} [valueA]
|
|
193
|
+
* @param {Number} [valueB]
|
|
194
194
|
* @return {Number}
|
|
195
195
|
* @memberof Random */
|
|
196
196
|
function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valueB); }
|
|
197
197
|
|
|
198
198
|
/** Returns a floored random value the two values passed in
|
|
199
199
|
* @param {Number} valueA
|
|
200
|
-
* @param {Number} [valueB
|
|
200
|
+
* @param {Number} [valueB]
|
|
201
201
|
* @return {Number}
|
|
202
202
|
* @memberof Random */
|
|
203
203
|
function randInt(valueA, valueB=0) { return Math.floor(rand(valueA,valueB)); }
|
|
@@ -208,14 +208,14 @@ function randInt(valueA, valueB=0) { return Math.floor(rand(valueA,valueB)); }
|
|
|
208
208
|
function randSign() { return randInt(2) * 2 - 1; }
|
|
209
209
|
|
|
210
210
|
/** Returns a random Vector2 with the passed in length
|
|
211
|
-
* @param {Number} [length
|
|
211
|
+
* @param {Number} [length]
|
|
212
212
|
* @return {Vector2}
|
|
213
213
|
* @memberof Random */
|
|
214
214
|
function randVector(length=1) { return new Vector2().setAngle(rand(2*PI), length); }
|
|
215
215
|
|
|
216
216
|
/** Returns a random Vector2 within a circular shape
|
|
217
|
-
* @param {Number} [radius
|
|
218
|
-
* @param {Number} [minRadius
|
|
217
|
+
* @param {Number} [radius]
|
|
218
|
+
* @param {Number} [minRadius]
|
|
219
219
|
* @return {Vector2}
|
|
220
220
|
* @memberof Random */
|
|
221
221
|
function randInCircle(radius=1, minRadius=0)
|
|
@@ -227,7 +227,7 @@ function randInCircle(radius=1, minRadius=0)
|
|
|
227
227
|
* @param {Boolean} [linear]
|
|
228
228
|
* @return {Color}
|
|
229
229
|
* @memberof Random */
|
|
230
|
-
function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear)
|
|
230
|
+
function randColor(colorA=new Color, colorB=new Color(0,0,0,1), linear=false)
|
|
231
231
|
{
|
|
232
232
|
return linear ? colorA.lerp(colorB, rand()) :
|
|
233
233
|
new Color(rand(colorA.r,colorB.r), rand(colorA.g,colorB.g), rand(colorA.b,colorB.b), rand(colorA.a,colorB.a));
|
|
@@ -256,8 +256,8 @@ class RandomGenerator
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
/** Returns a seeded random value between the two values passed in
|
|
259
|
-
* @param {Number} [valueA
|
|
260
|
-
* @param {Number} [valueB
|
|
259
|
+
* @param {Number} [valueA]
|
|
260
|
+
* @param {Number} [valueB]
|
|
261
261
|
* @return {Number} */
|
|
262
262
|
float(valueA=1, valueB=0)
|
|
263
263
|
{
|
|
@@ -270,7 +270,7 @@ class RandomGenerator
|
|
|
270
270
|
|
|
271
271
|
/** Returns a floored seeded random value the two values passed in
|
|
272
272
|
* @param {Number} valueA
|
|
273
|
-
* @param {Number} [valueB
|
|
273
|
+
* @param {Number} [valueB]
|
|
274
274
|
* @return {Number} */
|
|
275
275
|
int(valueA, valueB=0) { return Math.floor(this.float(valueA, valueB)); }
|
|
276
276
|
|
|
@@ -283,8 +283,8 @@ class RandomGenerator
|
|
|
283
283
|
|
|
284
284
|
/**
|
|
285
285
|
* Create a 2d vector, can take another Vector2 to copy, 2 scalars, or 1 scalar
|
|
286
|
-
* @param {(Number|Vector2)} [x
|
|
287
|
-
* @param {Number} [y
|
|
286
|
+
* @param {(Number|Vector2)} [x]
|
|
287
|
+
* @param {Number} [y]
|
|
288
288
|
* @return {Vector2}
|
|
289
289
|
* @example
|
|
290
290
|
* let a = vec2(0, 1); // vector with coordinates (0, 1)
|
|
@@ -294,15 +294,15 @@ class RandomGenerator
|
|
|
294
294
|
* @memberof Utilities
|
|
295
295
|
*/
|
|
296
296
|
function vec2(x=0, y)
|
|
297
|
-
{ return x
|
|
297
|
+
{ return typeof x === 'number'? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y); }
|
|
298
298
|
|
|
299
299
|
/**
|
|
300
300
|
* Check if object is a valid Vector2
|
|
301
|
-
* @param {
|
|
301
|
+
* @param {any} v
|
|
302
302
|
* @return {Boolean}
|
|
303
303
|
* @memberof Utilities
|
|
304
304
|
*/
|
|
305
|
-
function isVector2(v) { return
|
|
305
|
+
function isVector2(v) { return typeof v === 'object' && typeof v.x === 'number' && typeof v.y === 'number'; }
|
|
306
306
|
|
|
307
307
|
/**
|
|
308
308
|
* 2D Vector object with vector math library
|
|
@@ -316,8 +316,8 @@ function isVector2(v) { return !isNaN(v.x) && !isNaN(v.y); }
|
|
|
316
316
|
class Vector2
|
|
317
317
|
{
|
|
318
318
|
/** Create a 2D vector with the x and y passed in, can also be created with vec2()
|
|
319
|
-
* @param {Number} [x
|
|
320
|
-
* @param {Number} [y
|
|
319
|
+
* @param {Number} [x] - X axis location
|
|
320
|
+
* @param {Number} [y] - Y axis location */
|
|
321
321
|
constructor(x=0, y=0)
|
|
322
322
|
{
|
|
323
323
|
/** @property {Number} - X axis location */
|
|
@@ -374,12 +374,12 @@ class Vector2
|
|
|
374
374
|
distanceSquared(v) { return (this.x - v.x)**2 + (this.y - v.y)**2; }
|
|
375
375
|
|
|
376
376
|
/** Returns a new vector in same direction as this one with the length passed in
|
|
377
|
-
* @param {Number} [length
|
|
377
|
+
* @param {Number} [length]
|
|
378
378
|
* @return {Vector2} */
|
|
379
379
|
normalize(length=1) { const l = this.length(); return l ? this.scale(length/l) : new Vector2(0, length); }
|
|
380
380
|
|
|
381
381
|
/** Returns a new vector clamped to length passed in
|
|
382
|
-
* @param {Number} [length
|
|
382
|
+
* @param {Number} [length]
|
|
383
383
|
* @return {Vector2} */
|
|
384
384
|
clampLength(length=1) { const l = this.length(); return l > length ? this.scale(length/l) : this; }
|
|
385
385
|
|
|
@@ -398,8 +398,8 @@ class Vector2
|
|
|
398
398
|
angle() { return Math.atan2(this.x, this.y); }
|
|
399
399
|
|
|
400
400
|
/** Sets this vector with angle and length passed in
|
|
401
|
-
* @param {Number} [angle
|
|
402
|
-
* @param {Number} [length
|
|
401
|
+
* @param {Number} [angle]
|
|
402
|
+
* @param {Number} [length]
|
|
403
403
|
* @return {Vector2} */
|
|
404
404
|
setAngle(angle=0, length=1)
|
|
405
405
|
{ this.x = length*Math.sin(angle); this.y = length*Math.cos(angle); return this; }
|
|
@@ -484,10 +484,10 @@ function hsl(h, s, l, a) { return new Color().setHSLA(h, s, l, a); }
|
|
|
484
484
|
class Color
|
|
485
485
|
{
|
|
486
486
|
/** Create a color with the rgba components passed in, white by default
|
|
487
|
-
* @param {Number} [r
|
|
488
|
-
* @param {Number} [g
|
|
489
|
-
* @param {Number} [b
|
|
490
|
-
* @param {Number} [a
|
|
487
|
+
* @param {Number} [r] - red
|
|
488
|
+
* @param {Number} [g] - green
|
|
489
|
+
* @param {Number} [b] - blue
|
|
490
|
+
* @param {Number} [a] - alpha*/
|
|
491
491
|
constructor(r=1, g=1, b=1, a=1)
|
|
492
492
|
{
|
|
493
493
|
/** @property {Number} - Red */
|
|
@@ -542,10 +542,10 @@ class Color
|
|
|
542
542
|
lerp(c, percent) { return this.add(c.subtract(this).scale(clamp(percent))); }
|
|
543
543
|
|
|
544
544
|
/** Sets this color given a hue, saturation, lightness, and alpha
|
|
545
|
-
* @param {Number} [h
|
|
546
|
-
* @param {Number} [s
|
|
547
|
-
* @param {Number} [l
|
|
548
|
-
* @param {Number} [a
|
|
545
|
+
* @param {Number} [h] - hue
|
|
546
|
+
* @param {Number} [s] - saturation
|
|
547
|
+
* @param {Number} [l] - lightness
|
|
548
|
+
* @param {Number} [a] - alpha
|
|
549
549
|
* @return {Color} */
|
|
550
550
|
setHSLA(h=0, s=0, l=1, a=1)
|
|
551
551
|
{
|
|
@@ -591,8 +591,8 @@ class Color
|
|
|
591
591
|
}
|
|
592
592
|
|
|
593
593
|
/** Returns a new color that has each component randomly adjusted
|
|
594
|
-
* @param {Number} [amount
|
|
595
|
-
* @param {Number} [alphaAmount
|
|
594
|
+
* @param {Number} [amount]
|
|
595
|
+
* @param {Number} [alphaAmount]
|
|
596
596
|
* @return {Color} */
|
|
597
597
|
mutate(amount=.05, alphaAmount=0)
|
|
598
598
|
{
|
|
@@ -606,9 +606,9 @@ class Color
|
|
|
606
606
|
}
|
|
607
607
|
|
|
608
608
|
/** Returns this color expressed as a hex color code
|
|
609
|
-
* @param {Boolean} [useAlpha
|
|
609
|
+
* @param {Boolean} [useAlpha] - if alpha should be included in result
|
|
610
610
|
* @return {String} */
|
|
611
|
-
toString(useAlpha =
|
|
611
|
+
toString(useAlpha = true)
|
|
612
612
|
{
|
|
613
613
|
const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
|
|
614
614
|
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
@@ -657,7 +657,7 @@ class Timer
|
|
|
657
657
|
constructor(timeLeft) { this.time = timeLeft == undefined ? undefined : time + timeLeft; this.setTime = timeLeft; }
|
|
658
658
|
|
|
659
659
|
/** Set the timer with seconds passed in
|
|
660
|
-
* @param {Number} [timeLeft
|
|
660
|
+
* @param {Number} [timeLeft] - How much time left before the timer is elapsed in seconds */
|
|
661
661
|
set(timeLeft=0) { this.time = time + timeLeft; this.setTime = timeLeft; }
|
|
662
662
|
|
|
663
663
|
/** Unset the timer */
|
|
@@ -734,7 +734,7 @@ let canvasFixedSize = vec2();
|
|
|
734
734
|
* @type {Boolean}
|
|
735
735
|
* @default
|
|
736
736
|
* @memberof Settings */
|
|
737
|
-
let canvasPixelated =
|
|
737
|
+
let canvasPixelated = true;
|
|
738
738
|
|
|
739
739
|
/** Default font used for text rendering
|
|
740
740
|
* @type {String}
|
|
@@ -746,7 +746,7 @@ let fontDefault = 'arial';
|
|
|
746
746
|
* @type {Boolean}
|
|
747
747
|
* @default
|
|
748
748
|
* @memberof Settings */
|
|
749
|
-
let showSplashScreen =
|
|
749
|
+
let showSplashScreen = false;
|
|
750
750
|
|
|
751
751
|
///////////////////////////////////////////////////////////////////////////////
|
|
752
752
|
// WebGL settings
|
|
@@ -755,13 +755,13 @@ let showSplashScreen = 0;
|
|
|
755
755
|
* @type {Boolean}
|
|
756
756
|
* @default
|
|
757
757
|
* @memberof Settings */
|
|
758
|
-
let glEnable =
|
|
758
|
+
let glEnable = true;
|
|
759
759
|
|
|
760
760
|
/** Fixes slow rendering in some browsers by not compositing the WebGL canvas
|
|
761
761
|
* @type {Boolean}
|
|
762
762
|
* @default
|
|
763
763
|
* @memberof Settings */
|
|
764
|
-
let glOverlay =
|
|
764
|
+
let glOverlay = true;
|
|
765
765
|
|
|
766
766
|
///////////////////////////////////////////////////////////////////////////////
|
|
767
767
|
// Tile sheet settings
|
|
@@ -785,7 +785,7 @@ let tileFixBleedScale = .3;
|
|
|
785
785
|
* @type {Boolean}
|
|
786
786
|
* @default
|
|
787
787
|
* @memberof Settings */
|
|
788
|
-
let enablePhysicsSolver =
|
|
788
|
+
let enablePhysicsSolver = true;
|
|
789
789
|
|
|
790
790
|
/** Default object mass for collison calcuations (how heavy objects are)
|
|
791
791
|
* @type {Number}
|
|
@@ -807,7 +807,7 @@ let objectDefaultAngleDamping = 1;
|
|
|
807
807
|
|
|
808
808
|
/** How much to bounce when a collision occurs (0-1)
|
|
809
809
|
* @type {Number}
|
|
810
|
-
* @default
|
|
810
|
+
* @default
|
|
811
811
|
* @memberof Settings */
|
|
812
812
|
let objectDefaultElasticity = 0;
|
|
813
813
|
|
|
@@ -825,7 +825,7 @@ let objectMaxSpeed = 1;
|
|
|
825
825
|
|
|
826
826
|
/** How much gravity to apply to objects along the Y axis, negative is down
|
|
827
827
|
* @type {Number}
|
|
828
|
-
* @default
|
|
828
|
+
* @default
|
|
829
829
|
* @memberof Settings */
|
|
830
830
|
let gravity = 0;
|
|
831
831
|
|
|
@@ -842,33 +842,33 @@ let particleEmitRateScale = 1;
|
|
|
842
842
|
* @type {Boolean}
|
|
843
843
|
* @default
|
|
844
844
|
* @memberof Settings */
|
|
845
|
-
let gamepadsEnable =
|
|
845
|
+
let gamepadsEnable = true;
|
|
846
846
|
|
|
847
847
|
/** If true, the dpad input is also routed to the left analog stick (for better accessability)
|
|
848
848
|
* @type {Boolean}
|
|
849
849
|
* @default
|
|
850
850
|
* @memberof Settings */
|
|
851
|
-
let gamepadDirectionEmulateStick =
|
|
851
|
+
let gamepadDirectionEmulateStick = true;
|
|
852
852
|
|
|
853
853
|
/** If true the WASD keys are also routed to the direction keys (for better accessability)
|
|
854
854
|
* @type {Boolean}
|
|
855
855
|
* @default
|
|
856
856
|
* @memberof Settings */
|
|
857
|
-
let inputWASDEmulateDirection =
|
|
857
|
+
let inputWASDEmulateDirection = true;
|
|
858
858
|
|
|
859
859
|
/** True if touch gamepad should appear on mobile devices
|
|
860
860
|
* - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
861
861
|
* - Must be set by end of gameInit to be activated
|
|
862
862
|
* @type {Boolean}
|
|
863
|
-
* @default
|
|
863
|
+
* @default
|
|
864
864
|
* @memberof Settings */
|
|
865
|
-
let touchGamepadEnable =
|
|
865
|
+
let touchGamepadEnable = false;
|
|
866
866
|
|
|
867
867
|
/** True if touch gamepad should be analog stick or false to use if 8 way dpad
|
|
868
868
|
* @type {Boolean}
|
|
869
869
|
* @default
|
|
870
870
|
* @memberof Settings */
|
|
871
|
-
let touchGamepadAnalog =
|
|
871
|
+
let touchGamepadAnalog = true;
|
|
872
872
|
|
|
873
873
|
/** Size of virutal gamepad for touch devices in pixels
|
|
874
874
|
* @type {Number}
|
|
@@ -886,7 +886,7 @@ let touchGamepadAlpha = .3;
|
|
|
886
886
|
* @type {Boolean}
|
|
887
887
|
* @default
|
|
888
888
|
* @memberof Settings */
|
|
889
|
-
let vibrateEnable =
|
|
889
|
+
let vibrateEnable = true;
|
|
890
890
|
|
|
891
891
|
///////////////////////////////////////////////////////////////////////////////
|
|
892
892
|
// Audio settings
|
|
@@ -895,7 +895,7 @@ let vibrateEnable = 1;
|
|
|
895
895
|
* @type {Boolean}
|
|
896
896
|
* @default
|
|
897
897
|
* @memberof Settings */
|
|
898
|
-
let soundEnable =
|
|
898
|
+
let soundEnable = true;
|
|
899
899
|
|
|
900
900
|
/** Volume scale to apply to all sound, music and speech
|
|
901
901
|
* @type {Number}
|
|
@@ -944,9 +944,9 @@ let medalDisplayIconSize = 50;
|
|
|
944
944
|
|
|
945
945
|
/** Set to stop medals from being unlockable (like if cheats are enabled)
|
|
946
946
|
* @type {Boolean}
|
|
947
|
-
* @default
|
|
947
|
+
* @default
|
|
948
948
|
* @memberof Settings */
|
|
949
|
-
let medalsPreventUnlock;
|
|
949
|
+
let medalsPreventUnlock = false;
|
|
950
950
|
|
|
951
951
|
///////////////////////////////////////////////////////////////////////////////
|
|
952
952
|
// Setters for global variables
|
|
@@ -1017,12 +1017,12 @@ function setEnablePhysicsSolver(enable) { enablePhysicsSolver = enable; }
|
|
|
1017
1017
|
function setObjectDefaultMass(mass) { objectDefaultMass = mass; }
|
|
1018
1018
|
|
|
1019
1019
|
/** Set how much to slow velocity by each frame
|
|
1020
|
-
* @param {Number}
|
|
1020
|
+
* @param {Number} damp
|
|
1021
1021
|
* @memberof Settings */
|
|
1022
1022
|
function setObjectDefaultDamping(damp) { objectDefaultDamping = damp; }
|
|
1023
1023
|
|
|
1024
1024
|
/** Set how much to slow angular velocity each frame
|
|
1025
|
-
* @param {Number}
|
|
1025
|
+
* @param {Number} damp
|
|
1026
1026
|
* @memberof Settings */
|
|
1027
1027
|
function setObjectDefaultAngleDamping(damp) { objectDefaultAngleDamping = damp; }
|
|
1028
1028
|
|
|
@@ -1042,9 +1042,9 @@ function setObjectDefaultFriction(friction) { objectDefaultFriction = friction;
|
|
|
1042
1042
|
function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
|
|
1043
1043
|
|
|
1044
1044
|
/** Set how much gravity to apply to objects along the Y axis
|
|
1045
|
-
* @param {Number}
|
|
1045
|
+
* @param {Number} newGravity
|
|
1046
1046
|
* @memberof Settings */
|
|
1047
|
-
function setGravity(
|
|
1047
|
+
function setGravity(newGravity) { gravity = newGravity; }
|
|
1048
1048
|
|
|
1049
1049
|
/** Set to scales emit rate of particles
|
|
1050
1050
|
* @param {Number} scale
|
|
@@ -1142,7 +1142,7 @@ function setMedalsPreventUnlock(preventUnlock) { medalsPreventUnlock = preventUn
|
|
|
1142
1142
|
function setShowWatermark(show) { showWatermark = show; }
|
|
1143
1143
|
|
|
1144
1144
|
/** Set key code used to toggle debug mode, Esc by default
|
|
1145
|
-
* @param {
|
|
1145
|
+
* @param {String} key
|
|
1146
1146
|
* @memberof Debug */
|
|
1147
1147
|
function setDebugKey(key) { debugKey = key; }
|
|
1148
1148
|
/**
|
|
@@ -1179,27 +1179,25 @@ function setDebugKey(key) { debugKey = key; }
|
|
|
1179
1179
|
class EngineObject
|
|
1180
1180
|
{
|
|
1181
1181
|
/** Create an engine object and adds it to the list of objects
|
|
1182
|
-
* @param {Vector2}
|
|
1183
|
-
* @param {Vector2}
|
|
1184
|
-
* @param {TileInfo} [tileInfo]
|
|
1185
|
-
* @param {Number}
|
|
1186
|
-
* @param {Color}
|
|
1187
|
-
* @param {Number}
|
|
1182
|
+
* @param {Vector2} [pos=Vector2()] - World space position of the object
|
|
1183
|
+
* @param {Vector2} [size=Vector2(1,1)] - World space size of the object
|
|
1184
|
+
* @param {TileInfo} [tileInfo] - Tile info to render object (undefined is untextured)
|
|
1185
|
+
* @param {Number} [angle] - Angle the object is rotated by
|
|
1186
|
+
* @param {Color} [color=Color()] - Color to apply to tile when rendered
|
|
1187
|
+
* @param {Number} [renderOrder] - Objects sorted by renderOrder before being rendered
|
|
1188
1188
|
*/
|
|
1189
1189
|
constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color, renderOrder=0)
|
|
1190
1190
|
{
|
|
1191
1191
|
// set passed in params
|
|
1192
|
-
ASSERT(isVector2(pos) && isVector2(size)
|
|
1193
|
-
ASSERT(typeof tileInfo !== 'number' || !tileInfo
|
|
1194
|
-
ASSERT(!(renderOrder instanceof Color)); // prevent old style calls
|
|
1195
|
-
// to fix old calls, replace with tile(tileIndex, tileSize)
|
|
1192
|
+
ASSERT(isVector2(pos) && isVector2(size), 'ensure pos and size are vec2s');
|
|
1193
|
+
ASSERT(typeof tileInfo !== 'number' || !tileInfo, 'old style tile setup');
|
|
1196
1194
|
|
|
1197
1195
|
/** @property {Vector2} - World space position of the object */
|
|
1198
1196
|
this.pos = pos.copy();
|
|
1199
1197
|
/** @property {Vector2} - World space width and height of the object */
|
|
1200
1198
|
this.size = size;
|
|
1201
1199
|
/** @property {Vector2} - Size of object used for drawing, uses size if not set */
|
|
1202
|
-
this.drawSize;
|
|
1200
|
+
this.drawSize = undefined;
|
|
1203
1201
|
/** @property {TileInfo} - Tile info to render object (undefined is untextured) */
|
|
1204
1202
|
this.tileInfo = tileInfo;
|
|
1205
1203
|
/** @property {Number} - Angle to rotate the object */
|
|
@@ -1207,9 +1205,11 @@ class EngineObject
|
|
|
1207
1205
|
/** @property {Color} - Color to apply when rendered */
|
|
1208
1206
|
this.color = color;
|
|
1209
1207
|
/** @property {Color} - Additive color to apply when rendered */
|
|
1210
|
-
this.additiveColor;
|
|
1208
|
+
this.additiveColor = undefined;
|
|
1209
|
+
/** @property {Boolean} - Should it flip along y axis when rendered */
|
|
1210
|
+
this.mirror = false;
|
|
1211
1211
|
|
|
1212
|
-
//
|
|
1212
|
+
// physical properties
|
|
1213
1213
|
/** @property {Number} [mass=objectDefaultMass] - How heavy the object is, static if 0 */
|
|
1214
1214
|
this.mass = objectDefaultMass;
|
|
1215
1215
|
/** @property {Number} [damping=objectDefaultDamping] - How much to slow down velocity each frame (0-1) */
|
|
@@ -1220,19 +1220,34 @@ class EngineObject
|
|
|
1220
1220
|
this.elasticity = objectDefaultElasticity;
|
|
1221
1221
|
/** @property {Number} [friction=objectDefaultFriction] - How much friction to apply when sliding (0-1) */
|
|
1222
1222
|
this.friction = objectDefaultFriction;
|
|
1223
|
-
/** @property {Number}
|
|
1223
|
+
/** @property {Number} - How much to scale gravity by for this object */
|
|
1224
1224
|
this.gravityScale = 1;
|
|
1225
|
-
/** @property {Number}
|
|
1225
|
+
/** @property {Number} - Objects are sorted by render order */
|
|
1226
1226
|
this.renderOrder = renderOrder;
|
|
1227
|
-
/** @property {Vector2}
|
|
1227
|
+
/** @property {Vector2} - Velocity of the object */
|
|
1228
1228
|
this.velocity = vec2();
|
|
1229
|
-
/** @property {Number}
|
|
1229
|
+
/** @property {Number} - Angular velocity of the object */
|
|
1230
1230
|
this.angleVelocity = 0;
|
|
1231
|
-
|
|
1232
|
-
// init other internal object stuff
|
|
1231
|
+
/** @property {Number} - Track when object was created */
|
|
1233
1232
|
this.spawnTime = time;
|
|
1233
|
+
/** @property {Array} - List of children of this object */
|
|
1234
1234
|
this.children = [];
|
|
1235
|
+
|
|
1236
|
+
// parent child system
|
|
1237
|
+
/** @property {EngineObject} - Parent of object if in local space */
|
|
1238
|
+
this.parent = undefined;
|
|
1239
|
+
/** @property {Vector2} - Local position if child */
|
|
1240
|
+
this.localPos = vec2();
|
|
1241
|
+
/** @property {Number} - Local angle if child */
|
|
1242
|
+
this.localAngle = 0;
|
|
1243
|
+
|
|
1244
|
+
// collision flags
|
|
1245
|
+
/** @property {Boolean} - Object collides with the tile collision */
|
|
1235
1246
|
this.collideTiles = false;
|
|
1247
|
+
/** @property {Boolean} - Object collides with solid objects */
|
|
1248
|
+
this.collideSolidObjects = false;
|
|
1249
|
+
/** @property {Boolean} - Object collides with and blocks other objects */
|
|
1250
|
+
this.isSolid = false;
|
|
1236
1251
|
|
|
1237
1252
|
// add to list of objects
|
|
1238
1253
|
engineObjects.push(this);
|
|
@@ -1449,7 +1464,7 @@ class EngineObject
|
|
|
1449
1464
|
* @param {EngineObject} object - the object to test against
|
|
1450
1465
|
* @return {Boolean} - true if the collision should be resolved
|
|
1451
1466
|
*/
|
|
1452
|
-
collideWithObject(object) { return
|
|
1467
|
+
collideWithObject(object) { return true; }
|
|
1453
1468
|
|
|
1454
1469
|
/** How long since the object was created
|
|
1455
1470
|
* @return {Number} */
|
|
@@ -1470,7 +1485,7 @@ class EngineObject
|
|
|
1470
1485
|
/** Attaches a child to this with a given local transform
|
|
1471
1486
|
* @param {EngineObject} child
|
|
1472
1487
|
* @param {Vector2} [localPos=Vector2()]
|
|
1473
|
-
* @param {Number} [localAngle
|
|
1488
|
+
* @param {Number} [localAngle] */
|
|
1474
1489
|
addChild(child, localPos=vec2(), localAngle=0)
|
|
1475
1490
|
{
|
|
1476
1491
|
ASSERT(!child.parent && !this.children.includes(child));
|
|
@@ -1490,12 +1505,12 @@ class EngineObject
|
|
|
1490
1505
|
}
|
|
1491
1506
|
|
|
1492
1507
|
/** Set how this object collides
|
|
1493
|
-
* @param {Boolean} [collideSolidObjects
|
|
1494
|
-
* @param {Boolean} [isSolid
|
|
1495
|
-
* @param {Boolean} [collideTiles
|
|
1496
|
-
setCollision(collideSolidObjects=
|
|
1508
|
+
* @param {Boolean} [collideSolidObjects] - Does it collide with solid objects
|
|
1509
|
+
* @param {Boolean} [isSolid] - Does it collide with and block other objects (expensive in large numbers)
|
|
1510
|
+
* @param {Boolean} [collideTiles] - Does it collide with the tile collision */
|
|
1511
|
+
setCollision(collideSolidObjects=true, isSolid=true, collideTiles=true)
|
|
1497
1512
|
{
|
|
1498
|
-
ASSERT(collideSolidObjects || !isSolid
|
|
1513
|
+
ASSERT(collideSolidObjects || !isSolid, 'solid objects must be set to collide');
|
|
1499
1514
|
|
|
1500
1515
|
this.collideSolidObjects = collideSolidObjects;
|
|
1501
1516
|
this.isSolid = isSolid;
|
|
@@ -1586,9 +1601,9 @@ let drawCount;
|
|
|
1586
1601
|
* Create a tile info object
|
|
1587
1602
|
* - This can take vecs or floats for easier use and conversion
|
|
1588
1603
|
* - If an index is passed in, the tile size and index will determine the position
|
|
1589
|
-
* @param {(Number|Vector2)} [pos=Vector2()]
|
|
1590
|
-
* @param {(Number|Vector2)} [size=tileSizeDefault]
|
|
1591
|
-
* @param {Number} [textureIndex
|
|
1604
|
+
* @param {(Number|Vector2)} [pos=Vector2()] - Top left corner of tile in pixels or index
|
|
1605
|
+
* @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
|
|
1606
|
+
* @param {Number} [textureIndex] - Texture index to use
|
|
1592
1607
|
* @return {TileInfo}
|
|
1593
1608
|
* @example
|
|
1594
1609
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
@@ -1600,14 +1615,14 @@ let drawCount;
|
|
|
1600
1615
|
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
1601
1616
|
{
|
|
1602
1617
|
// if size is a number, make it a vector
|
|
1603
|
-
if (size
|
|
1618
|
+
if (typeof size === 'number')
|
|
1604
1619
|
{
|
|
1605
1620
|
ASSERT(size > 0);
|
|
1606
1621
|
size = vec2(size);
|
|
1607
1622
|
}
|
|
1608
1623
|
|
|
1609
1624
|
// if pos is a number, use it as a tile index
|
|
1610
|
-
if (pos
|
|
1625
|
+
if (typeof pos === 'number')
|
|
1611
1626
|
{
|
|
1612
1627
|
const textureInfo = textureInfos[textureIndex];
|
|
1613
1628
|
if (textureInfo)
|
|
@@ -1631,7 +1646,7 @@ class TileInfo
|
|
|
1631
1646
|
/** Create a tile info object
|
|
1632
1647
|
* @param {Vector2} [pos=Vector2()] - Top left corner of tile in pixels
|
|
1633
1648
|
* @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
|
|
1634
|
-
* @param {Number} [textureIndex
|
|
1649
|
+
* @param {Number} [textureIndex] - Texture index to use
|
|
1635
1650
|
*/
|
|
1636
1651
|
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
1637
1652
|
{
|
|
@@ -1660,10 +1675,13 @@ class TileInfo
|
|
|
1660
1675
|
/** Texture Info - Stores info about each texture */
|
|
1661
1676
|
class TextureInfo
|
|
1662
1677
|
{
|
|
1663
|
-
|
|
1678
|
+
/**
|
|
1679
|
+
* Create a TextureInfo, called automatically by the engine
|
|
1680
|
+
* @param {HTMLImageElement} image
|
|
1681
|
+
*/
|
|
1664
1682
|
constructor(image)
|
|
1665
1683
|
{
|
|
1666
|
-
/** @property {
|
|
1684
|
+
/** @property {HTMLImageElement} - image source */
|
|
1667
1685
|
this.image = image;
|
|
1668
1686
|
/** @property {Vector2} - size of the image */
|
|
1669
1687
|
this.size = vec2(image.width, image.height);
|
|
@@ -1706,23 +1724,21 @@ function worldToScreen(worldPos)
|
|
|
1706
1724
|
* @param {Vector2} pos - Center of the tile in world space
|
|
1707
1725
|
* @param {Vector2} [size=Vector2(1,1)] - Size of the tile in world space
|
|
1708
1726
|
* @param {TileInfo}[tileInfo] - Tile info to use, untextured if undefined
|
|
1709
|
-
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
|
|
1710
1727
|
* @param {Color} [color=Color()] - Color to modulate with
|
|
1711
|
-
* @param {Number} [angle
|
|
1712
|
-
* @param {Boolean} [mirror
|
|
1728
|
+
* @param {Number} [angle] - Angle to rotate by
|
|
1729
|
+
* @param {Boolean} [mirror] - If true image is flipped along the Y axis
|
|
1713
1730
|
* @param {Color} [additiveColor=Color(0,0,0,0)] - Additive color to be applied
|
|
1714
1731
|
* @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
|
|
1715
|
-
* @param {Boolean} [screenSpace
|
|
1732
|
+
* @param {Boolean} [screenSpace] - If true the pos and size are in screen space
|
|
1716
1733
|
* @param {CanvasRenderingContext2D} [context] - Canvas 2D context to draw to
|
|
1717
1734
|
* @memberof Draw */
|
|
1718
1735
|
function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
1719
1736
|
angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace, context)
|
|
1720
1737
|
{
|
|
1721
|
-
ASSERT(!context || !useWebGL
|
|
1722
|
-
ASSERT(typeof tileInfo !== 'number' || !tileInfo
|
|
1723
|
-
|
|
1738
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
1739
|
+
ASSERT(typeof tileInfo !== 'number' || !tileInfo,
|
|
1740
|
+
'this is an old style calls, to fix replace it with tile(tileIndex, tileSize)');
|
|
1724
1741
|
|
|
1725
|
-
showWatermark && ++drawCount;
|
|
1726
1742
|
const textureInfo = tileInfo && tileInfo.getTextureInfo();
|
|
1727
1743
|
if (useWebGL)
|
|
1728
1744
|
{
|
|
@@ -1756,6 +1772,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
1756
1772
|
else
|
|
1757
1773
|
{
|
|
1758
1774
|
// normal canvas 2D rendering method (slower)
|
|
1775
|
+
showWatermark && ++drawCount;
|
|
1759
1776
|
drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
1760
1777
|
{
|
|
1761
1778
|
if (textureInfo)
|
|
@@ -1783,49 +1800,38 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
1783
1800
|
* @param {Vector2} pos
|
|
1784
1801
|
* @param {Vector2} [size=Vector2(1,1)]
|
|
1785
1802
|
* @param {Color} [color=Color()]
|
|
1786
|
-
* @param {Number} [angle
|
|
1803
|
+
* @param {Number} [angle]
|
|
1787
1804
|
* @param {Boolean} [useWebGL=glEnable]
|
|
1788
|
-
* @param {Boolean} [screenSpace
|
|
1805
|
+
* @param {Boolean} [screenSpace]
|
|
1789
1806
|
* @param {CanvasRenderingContext2D} [context]
|
|
1790
1807
|
* @memberof Draw */
|
|
1791
1808
|
function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
1792
1809
|
{
|
|
1793
|
-
drawTile(pos, size, undefined, color, angle,
|
|
1810
|
+
drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
|
|
1794
1811
|
}
|
|
1795
1812
|
|
|
1796
1813
|
/** Draw colored polygon using passed in points
|
|
1797
1814
|
* @param {Array} points - Array of Vector2 points
|
|
1798
1815
|
* @param {Color} [color=Color()]
|
|
1799
|
-
* @param {Boolean} [
|
|
1800
|
-
* @param {
|
|
1801
|
-
* @param {CanvasRenderingContext2D} [context]
|
|
1816
|
+
* @param {Boolean} [screenSpace]
|
|
1817
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
1802
1818
|
* @memberof Draw */
|
|
1803
|
-
function drawPoly(points, color=new Color,
|
|
1819
|
+
function drawPoly(points, color=new Color, screenSpace, context=mainContext)
|
|
1804
1820
|
{
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
{
|
|
1811
|
-
// draw using canvas
|
|
1812
|
-
if (!context)
|
|
1813
|
-
context = mainContext;
|
|
1814
|
-
context.fillStyle = color;
|
|
1815
|
-
context.beginPath();
|
|
1816
|
-
for (const point of screenSpace ? points : points.map(worldToScreen))
|
|
1817
|
-
context.lineTo(point.x, point.y);
|
|
1818
|
-
context.fill();
|
|
1819
|
-
}
|
|
1821
|
+
context.fillStyle = color.toString();
|
|
1822
|
+
context.beginPath();
|
|
1823
|
+
for (const point of screenSpace ? points : points.map(worldToScreen))
|
|
1824
|
+
context.lineTo(point.x, point.y);
|
|
1825
|
+
context.fill();
|
|
1820
1826
|
}
|
|
1821
1827
|
|
|
1822
1828
|
/** Draw colored line between two points
|
|
1823
1829
|
* @param {Vector2} posA
|
|
1824
1830
|
* @param {Vector2} posB
|
|
1825
|
-
* @param {Number} [thickness
|
|
1831
|
+
* @param {Number} [thickness]
|
|
1826
1832
|
* @param {Color} [color=Color()]
|
|
1827
1833
|
* @param {Boolean} [useWebGL=glEnable]
|
|
1828
|
-
* @param {Boolean} [screenSpace
|
|
1834
|
+
* @param {Boolean} [screenSpace]
|
|
1829
1835
|
* @param {CanvasRenderingContext2D} [context]
|
|
1830
1836
|
* @memberof Draw */
|
|
1831
1837
|
function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
|
|
@@ -1841,7 +1847,7 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, contex
|
|
|
1841
1847
|
* @param {Number} angle
|
|
1842
1848
|
* @param {Boolean} mirror
|
|
1843
1849
|
* @param {Function} drawFunction
|
|
1844
|
-
* @param {Boolean} [screenSpace
|
|
1850
|
+
* @param {Boolean} [screenSpace]
|
|
1845
1851
|
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
1846
1852
|
* @memberof Draw */
|
|
1847
1853
|
function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, context=mainContext)
|
|
@@ -1861,13 +1867,13 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
|
|
|
1861
1867
|
}
|
|
1862
1868
|
|
|
1863
1869
|
/** Enable normal or additive blend mode
|
|
1864
|
-
* @param {Boolean} [additive
|
|
1870
|
+
* @param {Boolean} [additive]
|
|
1865
1871
|
* @param {Boolean} [useWebGL=glEnable]
|
|
1866
1872
|
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
1867
1873
|
* @memberof Draw */
|
|
1868
1874
|
function setBlendMode(additive, useWebGL=glEnable, context)
|
|
1869
1875
|
{
|
|
1870
|
-
ASSERT(!context || !useWebGL
|
|
1876
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
1871
1877
|
if (useWebGL)
|
|
1872
1878
|
glAdditive = additive;
|
|
1873
1879
|
else
|
|
@@ -1882,11 +1888,11 @@ function setBlendMode(additive, useWebGL=glEnable, context)
|
|
|
1882
1888
|
* Automatically splits new lines into rows
|
|
1883
1889
|
* @param {String} text
|
|
1884
1890
|
* @param {Vector2} pos
|
|
1885
|
-
* @param {Number} [size
|
|
1891
|
+
* @param {Number} [size]
|
|
1886
1892
|
* @param {Color} [color=Color()]
|
|
1887
|
-
* @param {Number} [lineWidth
|
|
1893
|
+
* @param {Number} [lineWidth]
|
|
1888
1894
|
* @param {Color} [lineColor=Color(0,0,0)]
|
|
1889
|
-
* @param {
|
|
1895
|
+
* @param {CanvasTextAlign} [textAlign='center']
|
|
1890
1896
|
* @param {String} [font=fontDefault]
|
|
1891
1897
|
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
1892
1898
|
* @memberof Draw */
|
|
@@ -1899,19 +1905,19 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
1899
1905
|
* Automatically splits new lines into rows
|
|
1900
1906
|
* @param {String} text
|
|
1901
1907
|
* @param {Vector2} pos
|
|
1902
|
-
* @param {Number} [size
|
|
1908
|
+
* @param {Number} [size]
|
|
1903
1909
|
* @param {Color} [color=Color()]
|
|
1904
|
-
* @param {Number} [lineWidth
|
|
1910
|
+
* @param {Number} [lineWidth]
|
|
1905
1911
|
* @param {Color} [lineColor=Color(0,0,0)]
|
|
1906
|
-
* @param {
|
|
1912
|
+
* @param {CanvasTextAlign} [textAlign]
|
|
1907
1913
|
* @param {String} [font=fontDefault]
|
|
1908
1914
|
* @param {CanvasRenderingContext2D} [context=overlayContext]
|
|
1909
1915
|
* @memberof Draw */
|
|
1910
1916
|
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
|
|
1911
1917
|
{
|
|
1912
|
-
context.fillStyle = color;
|
|
1918
|
+
context.fillStyle = color.toString();
|
|
1913
1919
|
context.lineWidth = lineWidth;
|
|
1914
|
-
context.strokeStyle = lineColor;
|
|
1920
|
+
context.strokeStyle = lineColor.toString();
|
|
1915
1921
|
context.textAlign = textAlign;
|
|
1916
1922
|
context.font = size + 'px '+ font;
|
|
1917
1923
|
context.textBaseline = 'middle';
|
|
@@ -1946,8 +1952,8 @@ class FontImage
|
|
|
1946
1952
|
{
|
|
1947
1953
|
/** Create an image font
|
|
1948
1954
|
* @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
|
|
1949
|
-
* @param {Vector2} [tileSize=
|
|
1950
|
-
* @param {Vector2} [paddingSize=
|
|
1955
|
+
* @param {Vector2} [tileSize=Vector2(8)] - Size of the font source tiles
|
|
1956
|
+
* @param {Vector2} [paddingSize=Vector2(0,1)] - How much extra space to add between characters
|
|
1951
1957
|
* @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
1952
1958
|
*/
|
|
1953
1959
|
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
|
|
@@ -1976,7 +1982,7 @@ class FontImage
|
|
|
1976
1982
|
/** Draw text in screen space using the image font
|
|
1977
1983
|
* @param {String} text
|
|
1978
1984
|
* @param {Vector2} pos
|
|
1979
|
-
* @param {Number} [scale
|
|
1985
|
+
* @param {Number} [scale]
|
|
1980
1986
|
* @param {Boolean} [center]
|
|
1981
1987
|
*/
|
|
1982
1988
|
drawTextScreen(text, pos, scale=4, center)
|
|
@@ -1994,7 +2000,7 @@ class FontImage
|
|
|
1994
2000
|
for(let j=line.length; j--;)
|
|
1995
2001
|
{
|
|
1996
2002
|
// draw each character
|
|
1997
|
-
let charCode = line[j].charCodeAt();
|
|
2003
|
+
let charCode = line[j].charCodeAt(0);
|
|
1998
2004
|
if (charCode < 32 || charCode > 127)
|
|
1999
2005
|
charCode = 127; // unknown character
|
|
2000
2006
|
|
|
@@ -2018,7 +2024,7 @@ class FontImage
|
|
|
2018
2024
|
/** Returns true if fullscreen mode is active
|
|
2019
2025
|
* @return {Boolean}
|
|
2020
2026
|
* @memberof Draw */
|
|
2021
|
-
function isFullscreen() { return document.fullscreenElement; }
|
|
2027
|
+
function isFullscreen() { return !!document.fullscreenElement; }
|
|
2022
2028
|
|
|
2023
2029
|
/** Toggle fullsceen mode
|
|
2024
2030
|
* @memberof Draw */
|
|
@@ -2044,28 +2050,37 @@ function toggleFullscreen()
|
|
|
2044
2050
|
|
|
2045
2051
|
|
|
2046
2052
|
/** Returns true if device key is down
|
|
2047
|
-
* @param {Number} key
|
|
2048
|
-
* @param {Number} [device
|
|
2053
|
+
* @param {String|Number} key
|
|
2054
|
+
* @param {Number} [device]
|
|
2049
2055
|
* @return {Boolean}
|
|
2050
2056
|
* @memberof Input */
|
|
2051
2057
|
function keyIsDown(key, device=0)
|
|
2052
|
-
{
|
|
2058
|
+
{
|
|
2059
|
+
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
2060
|
+
return inputData[device] && !!(inputData[device][key] & 1);
|
|
2061
|
+
}
|
|
2053
2062
|
|
|
2054
2063
|
/** Returns true if device key was pressed this frame
|
|
2055
|
-
* @param {Number} key
|
|
2056
|
-
* @param {Number} [device
|
|
2064
|
+
* @param {String|Number} key
|
|
2065
|
+
* @param {Number} [device]
|
|
2057
2066
|
* @return {Boolean}
|
|
2058
2067
|
* @memberof Input */
|
|
2059
2068
|
function keyWasPressed(key, device=0)
|
|
2060
|
-
{
|
|
2069
|
+
{
|
|
2070
|
+
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
2071
|
+
return inputData[device] && !!(inputData[device][key] & 2);
|
|
2072
|
+
}
|
|
2061
2073
|
|
|
2062
2074
|
/** Returns true if device key was released this frame
|
|
2063
|
-
* @param {Number} key
|
|
2064
|
-
* @param {Number} [device
|
|
2075
|
+
* @param {String|Number} key
|
|
2076
|
+
* @param {Number} [device]
|
|
2065
2077
|
* @return {Boolean}
|
|
2066
2078
|
* @memberof Input */
|
|
2067
2079
|
function keyWasReleased(key, device=0)
|
|
2068
|
-
{
|
|
2080
|
+
{
|
|
2081
|
+
ASSERT(device > 0 || typeof key !== 'number' || key < 3, 'use code string for keyboard');
|
|
2082
|
+
return inputData[device] && !!(inputData[device][key] & 4);
|
|
2083
|
+
}
|
|
2069
2084
|
|
|
2070
2085
|
/** Clears all input
|
|
2071
2086
|
* @memberof Input */
|
|
@@ -2110,16 +2125,16 @@ let mouseWheel = 0;
|
|
|
2110
2125
|
/** Returns true if user is using gamepad (has more recently pressed a gamepad button)
|
|
2111
2126
|
* @type {Boolean}
|
|
2112
2127
|
* @memberof Input */
|
|
2113
|
-
let isUsingGamepad =
|
|
2128
|
+
let isUsingGamepad = false;
|
|
2114
2129
|
|
|
2115
2130
|
/** Prevents input continuing to the default browser handling (false by default)
|
|
2116
2131
|
* @type {Boolean}
|
|
2117
2132
|
* @memberof Input */
|
|
2118
|
-
let preventDefaultInput =
|
|
2133
|
+
let preventDefaultInput = false;
|
|
2119
2134
|
|
|
2120
2135
|
/** Returns true if gamepad button is down
|
|
2121
2136
|
* @param {Number} button
|
|
2122
|
-
* @param {Number} [gamepad
|
|
2137
|
+
* @param {Number} [gamepad]
|
|
2123
2138
|
* @return {Boolean}
|
|
2124
2139
|
* @memberof Input */
|
|
2125
2140
|
function gamepadIsDown(button, gamepad=0)
|
|
@@ -2127,7 +2142,7 @@ function gamepadIsDown(button, gamepad=0)
|
|
|
2127
2142
|
|
|
2128
2143
|
/** Returns true if gamepad button was pressed
|
|
2129
2144
|
* @param {Number} button
|
|
2130
|
-
* @param {Number} [gamepad
|
|
2145
|
+
* @param {Number} [gamepad]
|
|
2131
2146
|
* @return {Boolean}
|
|
2132
2147
|
* @memberof Input */
|
|
2133
2148
|
function gamepadWasPressed(button, gamepad=0)
|
|
@@ -2135,7 +2150,7 @@ function gamepadWasPressed(button, gamepad=0)
|
|
|
2135
2150
|
|
|
2136
2151
|
/** Returns true if gamepad button was released
|
|
2137
2152
|
* @param {Number} button
|
|
2138
|
-
* @param {Number} [gamepad
|
|
2153
|
+
* @param {Number} [gamepad]
|
|
2139
2154
|
* @return {Boolean}
|
|
2140
2155
|
* @memberof Input */
|
|
2141
2156
|
function gamepadWasReleased(button, gamepad=0)
|
|
@@ -2143,7 +2158,7 @@ function gamepadWasReleased(button, gamepad=0)
|
|
|
2143
2158
|
|
|
2144
2159
|
/** Returns gamepad stick value
|
|
2145
2160
|
* @param {Number} stick
|
|
2146
|
-
* @param {Number} [gamepad
|
|
2161
|
+
* @param {Number} [gamepad]
|
|
2147
2162
|
* @return {Vector2}
|
|
2148
2163
|
* @memberof Input */
|
|
2149
2164
|
function gamepadStick(stick, gamepad=0)
|
|
@@ -2186,9 +2201,10 @@ function inputUpdatePost()
|
|
|
2186
2201
|
if (debug && e.target != document.body) return;
|
|
2187
2202
|
if (!e.repeat)
|
|
2188
2203
|
{
|
|
2189
|
-
|
|
2204
|
+
isUsingGamepad = false;
|
|
2205
|
+
inputData[0][e.code] = 3;
|
|
2190
2206
|
if (inputWASDEmulateDirection)
|
|
2191
|
-
inputData[0][remapKey(e.
|
|
2207
|
+
inputData[0][remapKey(e.code)] = 3;
|
|
2192
2208
|
}
|
|
2193
2209
|
preventDefaultInput && e.preventDefault();
|
|
2194
2210
|
}
|
|
@@ -2196,26 +2212,29 @@ function inputUpdatePost()
|
|
|
2196
2212
|
onkeyup = (e)=>
|
|
2197
2213
|
{
|
|
2198
2214
|
if (debug && e.target != document.body) return;
|
|
2199
|
-
inputData[0][e.
|
|
2215
|
+
inputData[0][e.code] = 4;
|
|
2200
2216
|
if (inputWASDEmulateDirection)
|
|
2201
|
-
inputData[0][remapKey(e.
|
|
2217
|
+
inputData[0][remapKey(e.code)] = 4;
|
|
2202
2218
|
}
|
|
2203
2219
|
|
|
2204
2220
|
// handle remapping wasd keys to directions
|
|
2205
2221
|
function remapKey(c)
|
|
2206
|
-
{
|
|
2222
|
+
{
|
|
2207
2223
|
return inputWASDEmulateDirection ?
|
|
2208
|
-
c
|
|
2224
|
+
c == 'KeyW' ? 'ArrowUp' :
|
|
2225
|
+
c == 'KeyS' ? 'ArrowDown' :
|
|
2226
|
+
c == 'KeyA' ? 'ArrowLeft' :
|
|
2227
|
+
c == 'KeyD' ? 'ArrowRight' : c : c;
|
|
2209
2228
|
}
|
|
2210
2229
|
}
|
|
2211
2230
|
|
|
2212
2231
|
///////////////////////////////////////////////////////////////////////////////
|
|
2213
2232
|
// Mouse event handlers
|
|
2214
2233
|
|
|
2215
|
-
onmousedown = (e)=> {
|
|
2234
|
+
onmousedown = (e)=> {isUsingGamepad = false; inputData[0][e.button] = 3; window.onmousemove(e); e.button && e.preventDefault();}
|
|
2216
2235
|
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2217
2236
|
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
2218
|
-
onwheel
|
|
2237
|
+
onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
2219
2238
|
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2220
2239
|
|
|
2221
2240
|
// convert a mouse or touch event position to screen space
|
|
@@ -2253,7 +2272,7 @@ function gamepadsUpdate()
|
|
|
2253
2272
|
for (let i=10; i--;)
|
|
2254
2273
|
{
|
|
2255
2274
|
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
2256
|
-
data[j] = touchGamepadButtons[i] ?
|
|
2275
|
+
data[j] = touchGamepadButtons[i] ? gamepadIsDown(j,0) ? 1 : 3 : gamepadIsDown(j,0) ? 4 : 0;
|
|
2257
2276
|
}
|
|
2258
2277
|
}
|
|
2259
2278
|
}
|
|
@@ -2285,18 +2304,23 @@ function gamepadsUpdate()
|
|
|
2285
2304
|
for (let j = gamepad.buttons.length; j--;)
|
|
2286
2305
|
{
|
|
2287
2306
|
const button = gamepad.buttons[j];
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2307
|
+
const wasDown = gamepadIsDown(j,i);
|
|
2308
|
+
data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
2309
|
+
isUsingGamepad ||= !i && button.pressed;
|
|
2291
2310
|
}
|
|
2292
2311
|
|
|
2293
2312
|
if (gamepadDirectionEmulateStick)
|
|
2294
2313
|
{
|
|
2295
2314
|
// copy dpad to left analog stick when pressed
|
|
2296
|
-
const dpad = vec2(
|
|
2315
|
+
const dpad = vec2(
|
|
2316
|
+
(gamepadIsDown(15,i)&&1) - (gamepadIsDown(14,i)&&1),
|
|
2317
|
+
(gamepadIsDown(12,i)&&1) - (gamepadIsDown(13,i)&&1));
|
|
2297
2318
|
if (dpad.lengthSquared())
|
|
2298
2319
|
sticks[0] = dpad.clampLength();
|
|
2299
2320
|
}
|
|
2321
|
+
|
|
2322
|
+
// disable touch gamepad if using real gamepad
|
|
2323
|
+
touchGamepadEnable && isUsingGamepad && touchGamepadTimer.unset();
|
|
2300
2324
|
}
|
|
2301
2325
|
}
|
|
2302
2326
|
}
|
|
@@ -2304,9 +2328,9 @@ function gamepadsUpdate()
|
|
|
2304
2328
|
///////////////////////////////////////////////////////////////////////////////
|
|
2305
2329
|
|
|
2306
2330
|
/** Pulse the vibration hardware if it exists
|
|
2307
|
-
* @param {Number} [pattern
|
|
2331
|
+
* @param {Number} [pattern] - a single value in miliseconds or vibration interval array
|
|
2308
2332
|
* @memberof Input */
|
|
2309
|
-
function vibrate(pattern)
|
|
2333
|
+
function vibrate(pattern=100)
|
|
2310
2334
|
{ vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
2311
2335
|
|
|
2312
2336
|
/** Cancel any ongoing vibration
|
|
@@ -2324,29 +2348,28 @@ const isTouchDevice = window.ontouchstart !== undefined;
|
|
|
2324
2348
|
if (isTouchDevice)
|
|
2325
2349
|
{
|
|
2326
2350
|
// override mouse events
|
|
2327
|
-
let wasTouching
|
|
2351
|
+
let wasTouching;
|
|
2328
2352
|
onmousedown = onmouseup = ()=> 0;
|
|
2329
2353
|
|
|
2330
2354
|
// handle all touch events the same way
|
|
2331
2355
|
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2332
2356
|
{
|
|
2333
|
-
e.button = 0; // all touches are left click
|
|
2334
|
-
|
|
2335
2357
|
// fix stalled audio on mobile
|
|
2336
2358
|
if (soundEnable)
|
|
2337
2359
|
audioContext ? audioContext.resume() : zzfx(0);
|
|
2338
2360
|
|
|
2339
2361
|
// check if touching and pass to mouse events
|
|
2340
2362
|
const touching = e.touches.length;
|
|
2363
|
+
const button = 0; // all touches are left mouse button
|
|
2341
2364
|
if (touching)
|
|
2342
2365
|
{
|
|
2343
2366
|
// set event pos and pass it along
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
wasTouching ?
|
|
2367
|
+
const p = vec2(e.touches[0].clientX, e.touches[0].clientY);
|
|
2368
|
+
mousePosScreen = mouseToScreen(p);
|
|
2369
|
+
wasTouching ? isUsingGamepad = false : inputData[0][button] = 3;
|
|
2347
2370
|
}
|
|
2348
2371
|
else if (wasTouching)
|
|
2349
|
-
|
|
2372
|
+
inputData[0][button] = inputData[0][button] & 2 | 4;
|
|
2350
2373
|
|
|
2351
2374
|
// set was touching
|
|
2352
2375
|
wasTouching = touching;
|
|
@@ -2427,8 +2450,8 @@ function createTouchGamepad()
|
|
|
2427
2450
|
}
|
|
2428
2451
|
|
|
2429
2452
|
// call default touch handler and set to using gamepad
|
|
2430
|
-
touchHandler(e);
|
|
2431
|
-
isUsingGamepad =
|
|
2453
|
+
touchHandler.bind(window)(e);
|
|
2454
|
+
isUsingGamepad = true;
|
|
2432
2455
|
|
|
2433
2456
|
// must return true so the document will get focus
|
|
2434
2457
|
return true;
|
|
@@ -2442,7 +2465,7 @@ function touchGamepadRender()
|
|
|
2442
2465
|
return;
|
|
2443
2466
|
|
|
2444
2467
|
// fade off when not touching or paused
|
|
2445
|
-
const alpha = percent(touchGamepadTimer, 4, 3);
|
|
2468
|
+
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
2446
2469
|
if (!alpha || paused)
|
|
2447
2470
|
return;
|
|
2448
2471
|
|
|
@@ -2546,13 +2569,13 @@ class Sound
|
|
|
2546
2569
|
|
|
2547
2570
|
/** Play the sound
|
|
2548
2571
|
* @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
|
|
2549
|
-
* @param {Number} [volume
|
|
2550
|
-
* @param {Number} [pitch
|
|
2551
|
-
* @param {Number} [randomnessScale
|
|
2552
|
-
* @param {Boolean} [loop
|
|
2572
|
+
* @param {Number} [volume] - How much to scale volume by (in addition to range fade)
|
|
2573
|
+
* @param {Number} [pitch] - How much to scale pitch by (also adjusted by this.randomness)
|
|
2574
|
+
* @param {Number} [randomnessScale] - How much to scale randomness
|
|
2575
|
+
* @param {Boolean} [loop] - Should the sound loop
|
|
2553
2576
|
* @return {AudioBufferSourceNode} - The audio source node
|
|
2554
2577
|
*/
|
|
2555
|
-
play(pos, volume=1, pitch=1, randomnessScale=1, loop=
|
|
2578
|
+
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false)
|
|
2556
2579
|
{
|
|
2557
2580
|
if (!soundEnable || !this.sampleChannels) return;
|
|
2558
2581
|
|
|
@@ -2585,8 +2608,13 @@ class Sound
|
|
|
2585
2608
|
{
|
|
2586
2609
|
if (this.source)
|
|
2587
2610
|
this.source.stop();
|
|
2588
|
-
this.source =
|
|
2611
|
+
this.source = undefined;
|
|
2589
2612
|
}
|
|
2613
|
+
|
|
2614
|
+
/** Get source of most recent instance of this sound that was played
|
|
2615
|
+
* @return {AudioBufferSourceNode}
|
|
2616
|
+
*/
|
|
2617
|
+
getSource() { return this.source; }
|
|
2590
2618
|
|
|
2591
2619
|
/** Play the sound as a note with a semitone offset
|
|
2592
2620
|
* @param {Number} semitoneOffset - How many semitones to offset pitch
|
|
@@ -2602,12 +2630,7 @@ class Sound
|
|
|
2602
2630
|
*/
|
|
2603
2631
|
getDuration()
|
|
2604
2632
|
{ return this.sampleChannels && this.sampleChannels[0].length / this.sampleRate; }
|
|
2605
|
-
|
|
2606
|
-
/** Check if the last instance of this sound is playing
|
|
2607
|
-
* @return {Boolean} - True if the sound is playing
|
|
2608
|
-
*/
|
|
2609
|
-
isPlaying() { return this.source && !this.source.ended; }
|
|
2610
|
-
|
|
2633
|
+
|
|
2611
2634
|
/** Check if sound is loading, for sounds fetched from a url
|
|
2612
2635
|
* @return {Boolean} - True if sound is loading and not ready to play
|
|
2613
2636
|
*/
|
|
@@ -2628,13 +2651,13 @@ class SoundWave extends Sound
|
|
|
2628
2651
|
{
|
|
2629
2652
|
/** Create a sound object and cache the wave file for later use
|
|
2630
2653
|
* @param {String} filename - Filename of audio file to load
|
|
2631
|
-
* @param {Number} [randomness
|
|
2654
|
+
* @param {Number} [randomness] - How much to randomize frequency each time sound plays
|
|
2632
2655
|
* @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
|
|
2633
2656
|
* @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
|
|
2634
2657
|
*/
|
|
2635
2658
|
constructor(filename, randomness=0, range, taper)
|
|
2636
2659
|
{
|
|
2637
|
-
super(
|
|
2660
|
+
super(undefined, range, taper);
|
|
2638
2661
|
this.randomness = randomness;
|
|
2639
2662
|
|
|
2640
2663
|
if (!soundEnable) return;
|
|
@@ -2688,11 +2711,11 @@ let soundDecoderContext; // audio context used only to decode audio files
|
|
|
2688
2711
|
class Music extends Sound
|
|
2689
2712
|
{
|
|
2690
2713
|
/** Create a music object and cache the zzfx music samples for later use
|
|
2691
|
-
* @param {Array} zzfxMusic - Array of zzfx music parameters
|
|
2714
|
+
* @param {[Array, Array, Array, Number]} zzfxMusic - Array of zzfx music parameters
|
|
2692
2715
|
*/
|
|
2693
2716
|
constructor(zzfxMusic)
|
|
2694
2717
|
{
|
|
2695
|
-
super();
|
|
2718
|
+
super(undefined);
|
|
2696
2719
|
|
|
2697
2720
|
if (!soundEnable) return;
|
|
2698
2721
|
this.randomness = 0;
|
|
@@ -2705,17 +2728,17 @@ class Music extends Sound
|
|
|
2705
2728
|
* @param {Boolean} [loop=1] - True if the music should loop
|
|
2706
2729
|
* @return {AudioBufferSourceNode} - The audio source node
|
|
2707
2730
|
*/
|
|
2708
|
-
playMusic(volume, loop =
|
|
2709
|
-
{ return super.play(
|
|
2731
|
+
playMusic(volume, loop = false)
|
|
2732
|
+
{ return super.play(undefined, volume, 1, 1, loop); }
|
|
2710
2733
|
}
|
|
2711
2734
|
|
|
2712
2735
|
/** Play an mp3, ogg, or wav audio from a local file or url
|
|
2713
2736
|
* @param {String} url - Location of sound file to play
|
|
2714
|
-
* @param {Number} [volume
|
|
2715
|
-
* @param {Boolean} [loop
|
|
2737
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
2738
|
+
* @param {Boolean} [loop] - True if the music should loop
|
|
2716
2739
|
* @return {HTMLAudioElement} - The audio element for this sound
|
|
2717
2740
|
* @memberof Audio */
|
|
2718
|
-
function playAudioFile(url, volume=1, loop=
|
|
2741
|
+
function playAudioFile(url, volume=1, loop=false)
|
|
2719
2742
|
{
|
|
2720
2743
|
if (!soundEnable) return;
|
|
2721
2744
|
|
|
@@ -2729,9 +2752,9 @@ function playAudioFile(url, volume=1, loop=1)
|
|
|
2729
2752
|
/** Speak text with passed in settings
|
|
2730
2753
|
* @param {String} text - The text to speak
|
|
2731
2754
|
* @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
2732
|
-
* @param {Number} [volume
|
|
2733
|
-
* @param {Number} [rate
|
|
2734
|
-
* @param {Number} [pitch
|
|
2755
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
2756
|
+
* @param {Number} [rate] - How quickly to speak
|
|
2757
|
+
* @param {Number} [pitch] - How much to change the pitch by
|
|
2735
2758
|
* @return {SpeechSynthesisUtterance} - The utterance that was spoken
|
|
2736
2759
|
* @memberof Audio */
|
|
2737
2760
|
function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
@@ -2758,7 +2781,7 @@ function speakStop() {speechSynthesis && speechSynthesis.cancel();}
|
|
|
2758
2781
|
|
|
2759
2782
|
/** Get frequency of a note on a musical scale
|
|
2760
2783
|
* @param {Number} semitoneOffset - How many semitones away from the root note
|
|
2761
|
-
* @param {Number} [
|
|
2784
|
+
* @param {Number} [rootFrequency=220] - Frequency at semitone offset 0
|
|
2762
2785
|
* @return {Number} - The frequency of the note
|
|
2763
2786
|
* @memberof Audio */
|
|
2764
2787
|
function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
@@ -2772,14 +2795,14 @@ let audioContext;
|
|
|
2772
2795
|
|
|
2773
2796
|
/** Play cached audio samples with given settings
|
|
2774
2797
|
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
|
|
2775
|
-
* @param {Number} [volume
|
|
2776
|
-
* @param {Number} [rate
|
|
2777
|
-
* @param {Number} [pan
|
|
2778
|
-
* @param {Boolean} [loop
|
|
2798
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
2799
|
+
* @param {Number} [rate] - The playback rate to use
|
|
2800
|
+
* @param {Number} [pan] - How much to apply stereo panning
|
|
2801
|
+
* @param {Boolean} [loop] - True if the sound should loop when it reaches the end
|
|
2779
2802
|
* @param {Number} [sampleRate=44100] - Sample rate for the sound
|
|
2780
2803
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
2781
2804
|
* @memberof Audio */
|
|
2782
|
-
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=
|
|
2805
|
+
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
|
|
2783
2806
|
{
|
|
2784
2807
|
if (!soundEnable) return;
|
|
2785
2808
|
|
|
@@ -2819,7 +2842,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0, sampleRate
|
|
|
2819
2842
|
}
|
|
2820
2843
|
|
|
2821
2844
|
///////////////////////////////////////////////////////////////////////////////
|
|
2822
|
-
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.
|
|
2845
|
+
// ZzFXMicro - Zuper Zmall Zound Zynth - v1.3.1 by Frank Force
|
|
2823
2846
|
|
|
2824
2847
|
/** Generate and play a ZzFX sound
|
|
2825
2848
|
*
|
|
@@ -2835,26 +2858,27 @@ function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
|
|
|
2835
2858
|
const zzfxR = 44100;
|
|
2836
2859
|
|
|
2837
2860
|
/** Generate samples for a ZzFX sound
|
|
2838
|
-
* @param {Number} [volume
|
|
2839
|
-
* @param {Number} [randomness
|
|
2840
|
-
* @param {Number} [frequency
|
|
2841
|
-
* @param {Number} [attack
|
|
2842
|
-
* @param {Number} [sustain
|
|
2843
|
-
* @param {Number} [release
|
|
2844
|
-
* @param {Number} [shape
|
|
2845
|
-
* @param {Number} [shapeCurve
|
|
2846
|
-
* @param {Number} [slide
|
|
2847
|
-
* @param {Number} [deltaSlide
|
|
2848
|
-
* @param {Number} [pitchJump
|
|
2849
|
-
* @param {Number} [pitchJumpTime
|
|
2850
|
-
* @param {Number} [repeatTime
|
|
2851
|
-
* @param {Number} [noise
|
|
2852
|
-
* @param {Number} [modulation
|
|
2853
|
-
* @param {Number} [bitCrush
|
|
2854
|
-
* @param {Number} [delay
|
|
2855
|
-
* @param {Number} [sustainVolume
|
|
2856
|
-
* @param {Number} [decay
|
|
2857
|
-
* @param {Number} [tremolo
|
|
2861
|
+
* @param {Number} [volume] - Volume scale (percent)
|
|
2862
|
+
* @param {Number} [randomness] - How much to randomize frequency (percent Hz)
|
|
2863
|
+
* @param {Number} [frequency] - Frequency of sound (Hz)
|
|
2864
|
+
* @param {Number} [attack] - Attack time, how fast sound starts (seconds)
|
|
2865
|
+
* @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
|
|
2866
|
+
* @param {Number} [release] - Release time, how fast sound fades out (seconds)
|
|
2867
|
+
* @param {Number} [shape] - Shape of the sound wave
|
|
2868
|
+
* @param {Number} [shapeCurve] - Squarenes of wave (0=square, 1=normal, 2=pointy)
|
|
2869
|
+
* @param {Number} [slide] - How much to slide frequency (kHz/s)
|
|
2870
|
+
* @param {Number} [deltaSlide] - How much to change slide (kHz/s/s)
|
|
2871
|
+
* @param {Number} [pitchJump] - Frequency of pitch jump (Hz)
|
|
2872
|
+
* @param {Number} [pitchJumpTime] - Time of pitch jump (seconds)
|
|
2873
|
+
* @param {Number} [repeatTime] - Resets some parameters periodically (seconds)
|
|
2874
|
+
* @param {Number} [noise] - How much random noise to add (percent)
|
|
2875
|
+
* @param {Number} [modulation] - Frequency of modulation wave, negative flips phase (Hz)
|
|
2876
|
+
* @param {Number} [bitCrush] - Resamples at a lower frequency in (samples*100)
|
|
2877
|
+
* @param {Number} [delay] - Overlap sound with itself for reverb and flanger effects (seconds)
|
|
2878
|
+
* @param {Number} [sustainVolume] - Volume level for sustain (percent)
|
|
2879
|
+
* @param {Number} [decay] - Decay time, how long to reach sustain after attack (seconds)
|
|
2880
|
+
* @param {Number} [tremolo] - Trembling effect, rate controlled by repeat time (precent)
|
|
2881
|
+
* @param {Number} [filter] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
|
|
2858
2882
|
* @return {Array} - Array of audio samples
|
|
2859
2883
|
* @memberof Audio
|
|
2860
2884
|
*/
|
|
@@ -2864,78 +2888,91 @@ function zzfxG
|
|
|
2864
2888
|
volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
|
|
2865
2889
|
release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
|
|
2866
2890
|
pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
|
|
2867
|
-
bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0
|
|
2891
|
+
bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
|
|
2868
2892
|
)
|
|
2869
2893
|
{
|
|
2870
|
-
//
|
|
2871
|
-
let PI2 = PI*2,
|
|
2872
|
-
|
|
2873
|
-
|
|
2894
|
+
// init parameters
|
|
2895
|
+
let PI2 = PI*2, sampleRate = zzfxR,
|
|
2896
|
+
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
2897
|
+
startFrequency = frequency *=
|
|
2898
|
+
rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
|
|
2899
|
+
b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
|
|
2900
|
+
|
|
2901
|
+
// biquad LP/HP filter
|
|
2902
|
+
quality = 2, w = PI2 * abs(filter) * 2 / sampleRate,
|
|
2903
|
+
cos = Math.cos(w), alpha = Math.sin(w) / 2 / quality,
|
|
2904
|
+
a0 = 1 + alpha, a1 = -2*cos / a0, a2 = (1 - alpha) / a0,
|
|
2905
|
+
b0 = (1 + sign(filter) * cos) / 2 / a0,
|
|
2906
|
+
b1 = -(sign(filter) + cos) / a0, b2 = b0,
|
|
2907
|
+
x2 = 0, x1 = 0, y2 = 0, y1 = 0;
|
|
2874
2908
|
|
|
2875
2909
|
// scale by sample rate
|
|
2876
|
-
attack = attack *
|
|
2877
|
-
decay *=
|
|
2878
|
-
sustain *=
|
|
2879
|
-
release *=
|
|
2880
|
-
delay *=
|
|
2881
|
-
deltaSlide *= 500 * PI2 /
|
|
2882
|
-
modulation *= PI2 /
|
|
2883
|
-
pitchJump *= PI2 /
|
|
2884
|
-
pitchJumpTime *=
|
|
2885
|
-
repeatTime = repeatTime *
|
|
2910
|
+
attack = attack * sampleRate + 9; // minimum attack to prevent pop
|
|
2911
|
+
decay *= sampleRate;
|
|
2912
|
+
sustain *= sampleRate;
|
|
2913
|
+
release *= sampleRate;
|
|
2914
|
+
delay *= sampleRate;
|
|
2915
|
+
deltaSlide *= 500 * PI2 / sampleRate**3;
|
|
2916
|
+
modulation *= PI2 / sampleRate;
|
|
2917
|
+
pitchJump *= PI2 / sampleRate;
|
|
2918
|
+
pitchJumpTime *= sampleRate;
|
|
2919
|
+
repeatTime = repeatTime * sampleRate | 0;
|
|
2920
|
+
volume *= soundVolume;
|
|
2886
2921
|
|
|
2887
2922
|
// generate waveform
|
|
2888
|
-
for
|
|
2889
|
-
i < length; b[i++] = s)
|
|
2923
|
+
for(length = attack + decay + sustain + release + delay | 0;
|
|
2924
|
+
i < length; b[i++] = s * volume) // sample
|
|
2890
2925
|
{
|
|
2891
|
-
if (!(++c%(bitCrush*100|0)))
|
|
2926
|
+
if (!(++c%(bitCrush*100|0))) // bit crush
|
|
2892
2927
|
{
|
|
2893
|
-
s = shape? shape>1? shape>2? shape>3?
|
|
2894
|
-
Math.sin(
|
|
2895
|
-
|
|
2896
|
-
1-(2*t/PI2%2+2)%2:
|
|
2897
|
-
1-4*abs(Math.round(t/PI2)-t/PI2):
|
|
2898
|
-
Math.sin(t);
|
|
2899
|
-
|
|
2928
|
+
s = shape? shape>1? shape>2? shape>3? // wave shape
|
|
2929
|
+
Math.sin(t**3) : // 4 noise
|
|
2930
|
+
clamp(Math.tan(t),1,-1): // 3 tan
|
|
2931
|
+
1-(2*t/PI2%2+2)%2: // 2 saw
|
|
2932
|
+
1-4*abs(Math.round(t/PI2)-t/PI2): // 1 triangle
|
|
2933
|
+
Math.sin(t); // 0 sin
|
|
2934
|
+
|
|
2900
2935
|
s = (repeatTime ?
|
|
2901
2936
|
1 - tremolo + tremolo*Math.sin(PI2*i/repeatTime) // tremolo
|
|
2902
2937
|
: 1) *
|
|
2903
|
-
sign(s)*(abs(s)**shapeCurve) *
|
|
2904
|
-
|
|
2905
|
-
i < attack
|
|
2906
|
-
i
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
f = (frequency += slide += deltaSlide) * // frequency
|
|
2921
|
-
Math.cos(modulation*tm++); // modulation
|
|
2922
|
-
t += f - f*noise*(1 - (Math.sin(i)+1)*1e9%2); // noise
|
|
2923
|
-
|
|
2924
|
-
if (j && ++j > pitchJumpTime) // pitch jump
|
|
2925
|
-
{
|
|
2926
|
-
frequency += pitchJump; // apply pitch jump
|
|
2927
|
-
startFrequency += pitchJump; // also apply to start
|
|
2928
|
-
j = 0; // reset pitch jump time
|
|
2938
|
+
sign(s)*(abs(s)**shapeCurve) * // curve
|
|
2939
|
+
(i < attack ? i/attack : // attack
|
|
2940
|
+
i < attack + decay ? // decay
|
|
2941
|
+
1-((i-attack)/decay)*(1-sustainVolume) : // decay falloff
|
|
2942
|
+
i < attack + decay + sustain ? // sustain
|
|
2943
|
+
sustainVolume : // sustain volume
|
|
2944
|
+
i < length - delay ? // release
|
|
2945
|
+
(length - i - delay)/release * // release falloff
|
|
2946
|
+
sustainVolume : // release volume
|
|
2947
|
+
0); // post release
|
|
2948
|
+
|
|
2949
|
+
s = delay ? s/2 + (delay > i ? 0 : // delay
|
|
2950
|
+
(i<length-delay? 1 : (length-i)/delay) * // release delay
|
|
2951
|
+
b[i-delay|0]/2/volume) : s; // sample delay
|
|
2952
|
+
|
|
2953
|
+
if (filter) // apply filter
|
|
2954
|
+
s = y1 = b2*x2 + b1*(x2=x1) + b0*(x1=s) - a2*y2 - a1*(y2=y1);
|
|
2929
2955
|
}
|
|
2930
2956
|
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2957
|
+
f = (frequency += slide += deltaSlide) *// frequency
|
|
2958
|
+
Math.cos(modulation*tm++); // modulation
|
|
2959
|
+
t += f + f*noise*Math.sin(i**5); // noise
|
|
2960
|
+
|
|
2961
|
+
if (j && ++j > pitchJumpTime) // pitch jump
|
|
2962
|
+
{
|
|
2963
|
+
frequency += pitchJump; // apply pitch jump
|
|
2964
|
+
startFrequency += pitchJump; // also apply to start
|
|
2965
|
+
j = 0; // stop pitch jump time
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
if (repeatTime && !(++r % repeatTime)) // repeat
|
|
2969
|
+
{
|
|
2970
|
+
frequency = startFrequency; // reset frequency
|
|
2971
|
+
slide = startSlide; // reset slide
|
|
2972
|
+
j = j || 1; // reset pitch jump time
|
|
2936
2973
|
}
|
|
2937
2974
|
}
|
|
2938
|
-
|
|
2975
|
+
|
|
2939
2976
|
return b;
|
|
2940
2977
|
}
|
|
2941
2978
|
|
|
@@ -2946,7 +2983,7 @@ function zzfxG
|
|
|
2946
2983
|
* @param {Array} instruments - Array of ZzFX sound paramaters
|
|
2947
2984
|
* @param {Array} patterns - Array of pattern data
|
|
2948
2985
|
* @param {Array} sequence - Array of pattern indexes
|
|
2949
|
-
* @param {Number} [BPM
|
|
2986
|
+
* @param {Number} [BPM] - Playback speed of the song in BPM
|
|
2950
2987
|
* @return {Array} - Left and right channel sample data
|
|
2951
2988
|
* @memberof Audio */
|
|
2952
2989
|
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
@@ -2985,10 +3022,10 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
2985
3022
|
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
2986
3023
|
|
|
2987
3024
|
// check if there are more channels
|
|
2988
|
-
hasMore
|
|
3025
|
+
hasMore |= patterns[patternIndex][channelIndex]&&1;
|
|
2989
3026
|
|
|
2990
3027
|
// get next offset, use the length of first channel
|
|
2991
|
-
nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 -
|
|
3028
|
+
nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
|
|
2992
3029
|
// for each beat in pattern, plus one extra if end of sequence
|
|
2993
3030
|
isSequenceEnd = sequenceIndex == sequence.length - 1;
|
|
2994
3031
|
for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
|
|
@@ -3004,7 +3041,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
3004
3041
|
for (j = 0; j < beatLength && notFirstBeat;
|
|
3005
3042
|
|
|
3006
3043
|
// fade off attenuation at end of beat if stopping note, prevents clicking
|
|
3007
|
-
j++ > beatLength - 99 && stop
|
|
3044
|
+
j++ > beatLength - 99 && stop && attenuation < 1? attenuation += 1 / 99 : 0
|
|
3008
3045
|
) {
|
|
3009
3046
|
// copy sample to stereo buffers with panning
|
|
3010
3047
|
sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
|
|
@@ -3080,7 +3117,7 @@ function initTileCollision(size)
|
|
|
3080
3117
|
|
|
3081
3118
|
/** Set tile collision data
|
|
3082
3119
|
* @param {Vector2} pos
|
|
3083
|
-
* @param {Number} [data
|
|
3120
|
+
* @param {Number} [data]
|
|
3084
3121
|
* @memberof TileCollision */
|
|
3085
3122
|
function setTileCollisionData(pos, data=0)
|
|
3086
3123
|
{
|
|
@@ -3113,7 +3150,7 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
3113
3150
|
{
|
|
3114
3151
|
const tileData = tileCollision[y*tileCollisionSize.x+x];
|
|
3115
3152
|
if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
|
|
3116
|
-
return
|
|
3153
|
+
return true;
|
|
3117
3154
|
}
|
|
3118
3155
|
}
|
|
3119
3156
|
|
|
@@ -3179,11 +3216,11 @@ function tileCollisionRaycast(posStart, posEnd, object)
|
|
|
3179
3216
|
class TileLayerData
|
|
3180
3217
|
{
|
|
3181
3218
|
/** Create a tile layer data object, one for each tile in a TileLayer
|
|
3182
|
-
* @param {Number} [tile]
|
|
3183
|
-
* @param {Number} [direction
|
|
3184
|
-
* @param {Boolean} [mirror
|
|
3185
|
-
* @param {Color} [color
|
|
3186
|
-
constructor(tile, direction=0, mirror=
|
|
3219
|
+
* @param {Number} [tile] - The tile to use, untextured if undefined
|
|
3220
|
+
* @param {Number} [direction] - Integer direction of tile, in 90 degree increments
|
|
3221
|
+
* @param {Boolean} [mirror] - If the tile should be mirrored along the x axis
|
|
3222
|
+
* @param {Color} [color] - Color of the tile */
|
|
3223
|
+
constructor(tile, direction=0, mirror=false, color=new Color())
|
|
3187
3224
|
{
|
|
3188
3225
|
/** @property {Number} - The tile to use, untextured if undefined */
|
|
3189
3226
|
this.tile = tile;
|
|
@@ -3196,7 +3233,7 @@ class TileLayerData
|
|
|
3196
3233
|
}
|
|
3197
3234
|
|
|
3198
3235
|
/** Set this tile to clear, it will not be rendered */
|
|
3199
|
-
clear() { this.tile = this.direction = this.mirror =
|
|
3236
|
+
clear() { this.tile = this.direction = 0; this.mirror = false; this.color = new Color; }
|
|
3200
3237
|
}
|
|
3201
3238
|
|
|
3202
3239
|
/**
|
|
@@ -3213,25 +3250,25 @@ class TileLayerData
|
|
|
3213
3250
|
*/
|
|
3214
3251
|
class TileLayer extends EngineObject
|
|
3215
3252
|
{
|
|
3216
|
-
/** Create a tile layer object
|
|
3217
|
-
* @param {Vector2}
|
|
3218
|
-
* @param {Vector2}
|
|
3219
|
-
* @param {TileInfo} [tileInfo]
|
|
3220
|
-
* @param {Vector2}
|
|
3221
|
-
* @param {Number}
|
|
3253
|
+
/** Create a tile layer object
|
|
3254
|
+
* @param {Vector2} [position=Vector2()] - World space position
|
|
3255
|
+
* @param {Vector2} [size=tileCollisionSize] - World space size
|
|
3256
|
+
* @param {TileInfo} [tileInfo] - Tile info for layer
|
|
3257
|
+
* @param {Vector2} [scale=Vector2(1,1)] - How much to scale this layer when rendered
|
|
3258
|
+
* @param {Number} [renderOrder] - Objects sorted by renderOrder before being rendered
|
|
3222
3259
|
*/
|
|
3223
|
-
constructor(
|
|
3260
|
+
constructor(position, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderOrder=0)
|
|
3224
3261
|
{
|
|
3225
|
-
super(
|
|
3262
|
+
super(position, size, tileInfo, 0, undefined, renderOrder);
|
|
3226
3263
|
|
|
3227
|
-
/** @property {HTMLCanvasElement}
|
|
3264
|
+
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
3228
3265
|
this.canvas = document.createElement('canvas');
|
|
3229
3266
|
/** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
3230
3267
|
this.context = this.canvas.getContext('2d');
|
|
3231
|
-
/** @property {Vector2}
|
|
3268
|
+
/** @property {Vector2} - How much to scale this layer when rendered */
|
|
3232
3269
|
this.scale = scale;
|
|
3233
|
-
/** @property {Boolean}
|
|
3234
|
-
this.isOverlay;
|
|
3270
|
+
/** @property {Boolean} - If true this layer will render to overlay canvas and appear above all objects */
|
|
3271
|
+
this.isOverlay = false;
|
|
3235
3272
|
|
|
3236
3273
|
// init tile data
|
|
3237
3274
|
this.data = [];
|
|
@@ -3240,10 +3277,10 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
|
|
|
3240
3277
|
}
|
|
3241
3278
|
|
|
3242
3279
|
/** Set data at a given position in the array
|
|
3243
|
-
* @param {Vector2}
|
|
3244
|
-
* @param {TileLayerData} data
|
|
3245
|
-
* @param {Boolean} [redraw
|
|
3246
|
-
setData(layerPos, data, redraw)
|
|
3280
|
+
* @param {Vector2} layerPos - Local position in array
|
|
3281
|
+
* @param {TileLayerData} data - Data to set
|
|
3282
|
+
* @param {Boolean} [redraw] - Force the tile to redraw if true */
|
|
3283
|
+
setData(layerPos, data, redraw=false)
|
|
3247
3284
|
{
|
|
3248
3285
|
if (layerPos.arrayCheck(this.size))
|
|
3249
3286
|
{
|
|
@@ -3264,7 +3301,7 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
|
|
|
3264
3301
|
// Render the tile layer, called automatically by the engine
|
|
3265
3302
|
render()
|
|
3266
3303
|
{
|
|
3267
|
-
ASSERT(mainContext != this.context
|
|
3304
|
+
ASSERT(mainContext != this.context, 'must call redrawEnd() after drawing tiles');
|
|
3268
3305
|
|
|
3269
3306
|
// flush and copy gl canvas because tile canvas does not use webgl
|
|
3270
3307
|
glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
@@ -3283,16 +3320,17 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
|
|
|
3283
3320
|
*/
|
|
3284
3321
|
redraw()
|
|
3285
3322
|
{
|
|
3286
|
-
this.redrawStart(
|
|
3323
|
+
this.redrawStart(true);
|
|
3287
3324
|
this.drawAllTileData();
|
|
3288
3325
|
this.redrawEnd();
|
|
3289
3326
|
}
|
|
3290
3327
|
|
|
3291
3328
|
/** Call to start the redraw process
|
|
3292
|
-
* @param {Boolean} [clear
|
|
3293
|
-
redrawStart(clear
|
|
3329
|
+
* @param {Boolean} [clear] - Should it clear the canvas before drawing */
|
|
3330
|
+
redrawStart(clear=false)
|
|
3294
3331
|
{
|
|
3295
3332
|
// save current render settings
|
|
3333
|
+
/** @type {[HTMLCanvasElement, CanvasRenderingContext2D, Vector2, Vector2, number]} */
|
|
3296
3334
|
this.savedRenderSettings = [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale];
|
|
3297
3335
|
|
|
3298
3336
|
// hack: use normal rendering system to render the tiles
|
|
@@ -3315,8 +3353,8 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
|
|
|
3315
3353
|
/** Call to end the redraw process */
|
|
3316
3354
|
redrawEnd()
|
|
3317
3355
|
{
|
|
3318
|
-
ASSERT(mainContext == this.context
|
|
3319
|
-
glEnable && glCopyToContext(mainContext,
|
|
3356
|
+
ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
|
|
3357
|
+
glEnable && glCopyToContext(mainContext, true);
|
|
3320
3358
|
//debugSaveCanvas(this.canvas);
|
|
3321
3359
|
|
|
3322
3360
|
// set stuff back to normal
|
|
@@ -3329,13 +3367,13 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
|
|
|
3329
3367
|
{
|
|
3330
3368
|
// first clear out where the tile was
|
|
3331
3369
|
const pos = layerPos.floor().add(this.pos).add(vec2(.5));
|
|
3332
|
-
this.drawCanvas2D(pos, vec2(1), 0,
|
|
3370
|
+
this.drawCanvas2D(pos, vec2(1), 0, false, (context)=>context.clearRect(-.5, -.5, 1, 1));
|
|
3333
3371
|
|
|
3334
3372
|
// draw the tile if not undefined
|
|
3335
3373
|
const d = this.getData(layerPos);
|
|
3336
3374
|
if (d.tile != undefined)
|
|
3337
3375
|
{
|
|
3338
|
-
ASSERT(mainContext == this.context
|
|
3376
|
+
ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
|
|
3339
3377
|
const tileInfo = tile(d.tile, this.tileInfo.size, this.tileInfo.textureIndex);
|
|
3340
3378
|
drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
|
|
3341
3379
|
}
|
|
@@ -3403,7 +3441,7 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
|
|
|
3403
3441
|
* @param {Color} [color=Color()]
|
|
3404
3442
|
* @param {Number} [angle=0] */
|
|
3405
3443
|
drawRect(pos, size, color, angle)
|
|
3406
|
-
{ this.drawTile(pos, size,
|
|
3444
|
+
{ this.drawTile(pos, size, undefined, color, angle); }
|
|
3407
3445
|
}
|
|
3408
3446
|
/**
|
|
3409
3447
|
* LittleJS Particle System
|
|
@@ -3431,37 +3469,37 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
|
|
|
3431
3469
|
class ParticleEmitter extends EngineObject
|
|
3432
3470
|
{
|
|
3433
3471
|
/** Create a particle system with the given settings
|
|
3434
|
-
* @param {Vector2} position
|
|
3435
|
-
* @param {Number}
|
|
3436
|
-
* @param {Number} [emitSize
|
|
3437
|
-
* @param {Number}
|
|
3438
|
-
* @param {Number}
|
|
3439
|
-
* @param {Number}
|
|
3440
|
-
* @param {TileInfo} [tileInfo]
|
|
3441
|
-
* @param {Color}
|
|
3442
|
-
* @param {Color}
|
|
3443
|
-
* @param {Color}
|
|
3444
|
-
* @param {Color}
|
|
3445
|
-
* @param {Number}
|
|
3446
|
-
* @param {Number}
|
|
3447
|
-
* @param {Number}
|
|
3448
|
-
* @param {Number}
|
|
3449
|
-
* @param {Number}
|
|
3450
|
-
* @param {Number}
|
|
3451
|
-
* @param {Number}
|
|
3452
|
-
* @param {Number}
|
|
3453
|
-
* @param {Number}
|
|
3454
|
-
* @param {Number}
|
|
3455
|
-
* @param {Number}
|
|
3456
|
-
* @param {Boolean} [collideTiles
|
|
3457
|
-
* @param {Boolean} [additive
|
|
3458
|
-
* @param {Boolean} [randomColorLinear
|
|
3459
|
-
* @param {Number}
|
|
3460
|
-
* @param {Boolean} [localSpace
|
|
3472
|
+
* @param {Vector2} position - World space position of the emitter
|
|
3473
|
+
* @param {Number} [angle] - Angle to emit the particles
|
|
3474
|
+
* @param {Number|Vector2} [emitSize] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
3475
|
+
* @param {Number} [emitTime] - How long to stay alive (0 is forever)
|
|
3476
|
+
* @param {Number} [emitRate] - How many particles per second to spawn, does not emit if 0
|
|
3477
|
+
* @param {Number} [emitConeAngle=PI] - Local angle to apply velocity to particles from emitter
|
|
3478
|
+
* @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
|
|
3479
|
+
* @param {Color} [colorStartA=Color()] - Color at start of life 1, randomized between start colors
|
|
3480
|
+
* @param {Color} [colorStartB=Color()] - Color at start of life 2, randomized between start colors
|
|
3481
|
+
* @param {Color} [colorEndA=Color(1,1,1,0)] - Color at end of life 1, randomized between end colors
|
|
3482
|
+
* @param {Color} [colorEndB=Color(1,1,1,0)] - Color at end of life 2, randomized between end colors
|
|
3483
|
+
* @param {Number} [particleTime] - How long particles live
|
|
3484
|
+
* @param {Number} [sizeStart] - How big are particles at start
|
|
3485
|
+
* @param {Number} [sizeEnd] - How big are particles at end
|
|
3486
|
+
* @param {Number} [speed] - How fast are particles when spawned
|
|
3487
|
+
* @param {Number} [angleSpeed] - How fast are particles rotating
|
|
3488
|
+
* @param {Number} [damping] - How much to dampen particle speed
|
|
3489
|
+
* @param {Number} [angleDamping] - How much to dampen particle angular speed
|
|
3490
|
+
* @param {Number} [gravityScale] - How much gravity effect particles
|
|
3491
|
+
* @param {Number} [particleConeAngle] - Cone for start particle angle
|
|
3492
|
+
* @param {Number} [fadeRate] - How quick to fade particles at start/end in percent of life
|
|
3493
|
+
* @param {Number} [randomness] - Apply extra randomness percent
|
|
3494
|
+
* @param {Boolean} [collideTiles] - Do particles collide against tiles
|
|
3495
|
+
* @param {Boolean} [additive] - Should particles use addtive blend
|
|
3496
|
+
* @param {Boolean} [randomColorLinear] - Should color be randomized linearly or across each component
|
|
3497
|
+
* @param {Number} [renderOrder] - Render order for particles (additive is above other stuff by default)
|
|
3498
|
+
* @param {Boolean} [localSpace] - Should it be in local space of emitter (world space is default)
|
|
3461
3499
|
*/
|
|
3462
3500
|
constructor
|
|
3463
3501
|
(
|
|
3464
|
-
|
|
3502
|
+
position,
|
|
3465
3503
|
angle,
|
|
3466
3504
|
emitSize = 0,
|
|
3467
3505
|
emitTime = 0,
|
|
@@ -3483,17 +3521,17 @@ class ParticleEmitter extends EngineObject
|
|
|
3483
3521
|
particleConeAngle = PI,
|
|
3484
3522
|
fadeRate = .1,
|
|
3485
3523
|
randomness = .2,
|
|
3486
|
-
collideTiles,
|
|
3487
|
-
additive,
|
|
3488
|
-
randomColorLinear =
|
|
3524
|
+
collideTiles = false,
|
|
3525
|
+
additive = false,
|
|
3526
|
+
randomColorLinear = true,
|
|
3489
3527
|
renderOrder = additive ? 1e9 : 0,
|
|
3490
|
-
localSpace
|
|
3528
|
+
localSpace = false
|
|
3491
3529
|
)
|
|
3492
3530
|
{
|
|
3493
|
-
super(
|
|
3531
|
+
super(position, vec2(), tileInfo, angle, undefined, renderOrder);
|
|
3494
3532
|
|
|
3495
3533
|
// emitter settings
|
|
3496
|
-
/** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
3534
|
+
/** @property {Number|Vector2} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
3497
3535
|
this.emitSize = emitSize
|
|
3498
3536
|
/** @property {Number} - How long to stay alive (0 is forever) */
|
|
3499
3537
|
this.emitTime = emitTime;
|
|
@@ -3539,14 +3577,17 @@ class ParticleEmitter extends EngineObject
|
|
|
3539
3577
|
this.randomness = randomness;
|
|
3540
3578
|
/** @property {Boolean} - Do particles collide against tiles */
|
|
3541
3579
|
this.collideTiles = collideTiles;
|
|
3542
|
-
/** @property {
|
|
3580
|
+
/** @property {Boolean} - Should particles use addtive blend */
|
|
3543
3581
|
this.additive = additive;
|
|
3544
3582
|
/** @property {Boolean} - Should it be in local space of emitter */
|
|
3545
|
-
this.localSpace
|
|
3546
|
-
/** @property {Number} - If
|
|
3583
|
+
this.localSpace = localSpace;
|
|
3584
|
+
/** @property {Number} - If non zero the partile is drawn as a trail, stretched in the drection of velocity */
|
|
3547
3585
|
this.trailScale = 0;
|
|
3548
|
-
|
|
3549
|
-
|
|
3586
|
+
/** @property {Function} - Callback when particle is destroyed */
|
|
3587
|
+
this.particleDestroyCallback = undefined;
|
|
3588
|
+
/** @property {Function} - Callback when particle is created */
|
|
3589
|
+
this.particleCreateCallback = undefined;
|
|
3590
|
+
/** @property {Number} - Track particle emit time */
|
|
3550
3591
|
this.emitTimeBuffer = 0;
|
|
3551
3592
|
}
|
|
3552
3593
|
|
|
@@ -3578,18 +3619,16 @@ class ParticleEmitter extends EngineObject
|
|
|
3578
3619
|
emitParticle()
|
|
3579
3620
|
{
|
|
3580
3621
|
// spawn a particle
|
|
3581
|
-
let pos = this.emitSize
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3622
|
+
let pos = typeof this.emitSize === 'number' ? // check if number was used
|
|
3623
|
+
randInCircle(this.emitSize/2) // circle emitter
|
|
3624
|
+
: vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
|
|
3625
|
+
.multiply(this.emitSize).rotate(this.angle)
|
|
3585
3626
|
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
3586
3627
|
if (!this.localSpace)
|
|
3587
3628
|
{
|
|
3588
3629
|
pos = this.pos.add(pos);
|
|
3589
3630
|
angle += this.angle;
|
|
3590
3631
|
}
|
|
3591
|
-
|
|
3592
|
-
const particle = new Particle(pos, this.tileInfo, this.tileSize, angle);
|
|
3593
3632
|
|
|
3594
3633
|
// randomness scales each paremeter by a percentage
|
|
3595
3634
|
const randomness = this.randomness;
|
|
@@ -3605,30 +3644,21 @@ class ParticleEmitter extends EngineObject
|
|
|
3605
3644
|
const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
|
|
3606
3645
|
const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
|
|
3607
3646
|
const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
|
|
3608
|
-
|
|
3609
|
-
// build particle
|
|
3610
|
-
particle.colorStart
|
|
3611
|
-
particle.
|
|
3612
|
-
particle.
|
|
3613
|
-
particle.
|
|
3614
|
-
particle.
|
|
3615
|
-
particle.
|
|
3616
|
-
particle.
|
|
3617
|
-
particle.
|
|
3618
|
-
particle.
|
|
3619
|
-
particle.
|
|
3620
|
-
particle.
|
|
3621
|
-
|
|
3622
|
-
particle
|
|
3623
|
-
particle.collideTiles = this.collideTiles;
|
|
3624
|
-
particle.additive = this.additive;
|
|
3625
|
-
particle.renderOrder = this.renderOrder;
|
|
3626
|
-
particle.trailScale = this.trailScale;
|
|
3627
|
-
particle.mirror = randInt(2);
|
|
3628
|
-
particle.localSpaceEmitter = this.localSpace && this;
|
|
3629
|
-
|
|
3630
|
-
// setup callbacks for particles
|
|
3631
|
-
particle.destroyCallback = this.particleDestroyCallback;
|
|
3647
|
+
|
|
3648
|
+
// build particle
|
|
3649
|
+
const particle = new Particle(pos, this.tileInfo, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, this.fadeRate, this.additive, this.trailScale, this.localSpace && this, this.particleDestroyCallback);
|
|
3650
|
+
particle.velocity = vec2().setAngle(velocityAngle, speed);
|
|
3651
|
+
particle.fadeRate = this.fadeRate;
|
|
3652
|
+
particle.damping = this.damping;
|
|
3653
|
+
particle.angleDamping = this.angleDamping;
|
|
3654
|
+
particle.elasticity = this.elasticity;
|
|
3655
|
+
particle.friction = this.friction;
|
|
3656
|
+
particle.gravityScale = this.gravityScale;
|
|
3657
|
+
particle.collideTiles = this.collideTiles;
|
|
3658
|
+
particle.renderOrder = this.renderOrder;
|
|
3659
|
+
particle.mirror = !!randInt(2);
|
|
3660
|
+
|
|
3661
|
+
// call particle create callaback
|
|
3632
3662
|
this.particleCreateCallback && this.particleCreateCallback(particle);
|
|
3633
3663
|
|
|
3634
3664
|
// return the newly created particle
|
|
@@ -3647,13 +3677,47 @@ class ParticleEmitter extends EngineObject
|
|
|
3647
3677
|
class Particle extends EngineObject
|
|
3648
3678
|
{
|
|
3649
3679
|
/**
|
|
3650
|
-
* Create a particle with the given
|
|
3651
|
-
* @param {Vector2}
|
|
3652
|
-
* @param {TileInfo} [tileInfo]
|
|
3653
|
-
* @param {Number}
|
|
3680
|
+
* Create a particle with the given shis.colorStart = undefined;ettings
|
|
3681
|
+
* @param {Vector2} position - World space position of the particle
|
|
3682
|
+
* @param {TileInfo} [tileInfo] - Tile info to render particles
|
|
3683
|
+
* @param {Number} [angle] - Angle to rotate the particle
|
|
3684
|
+
* @param {Color} [colorStart] - Color at start of life
|
|
3685
|
+
* @param {Color} [colorEnd] - Color at end of life
|
|
3686
|
+
* @param {Number} [lifeTime] - How long to live for
|
|
3687
|
+
* @param {Number} [sizeStart] - Angle to rotate the particle
|
|
3688
|
+
* @param {Number} [sizeEnd] - Angle to rotate the particle
|
|
3689
|
+
* @param {Number} [fadeRate] - Angle to rotate the particle
|
|
3690
|
+
* @param {Boolean} [additive] - Angle to rotate the particle
|
|
3691
|
+
* @param {Number} [trailScale] - If a trail, how long to make it
|
|
3692
|
+
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
3693
|
+
* @param {Function} [destroyCallback] - Called when particle dies
|
|
3654
3694
|
*/
|
|
3655
|
-
constructor(
|
|
3656
|
-
|
|
3695
|
+
constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
|
|
3696
|
+
)
|
|
3697
|
+
{
|
|
3698
|
+
super(position, vec2(), tileInfo, angle);
|
|
3699
|
+
|
|
3700
|
+
/** @property {Color} - Color at start of life */
|
|
3701
|
+
this.colorStart = colorStart;
|
|
3702
|
+
/** @property {Color} - Calculated change in color */
|
|
3703
|
+
this.colorEndDelta = colorEnd.subtract(colorStart);
|
|
3704
|
+
/** @property {Number} - How long to live for */
|
|
3705
|
+
this.lifeTime = lifeTime;
|
|
3706
|
+
/** @property {Number} - Size at start of life */
|
|
3707
|
+
this.sizeStart = sizeStart;
|
|
3708
|
+
/** @property {Number} - Calculated change in size */
|
|
3709
|
+
this.sizeEndDelta = sizeEnd - sizeStart;
|
|
3710
|
+
/** @property {Number} - How quick to fade in/out */
|
|
3711
|
+
this.fadeRate = fadeRate;
|
|
3712
|
+
/** @property {Boolean} - Is it additive */
|
|
3713
|
+
this.additive = additive;
|
|
3714
|
+
/** @property {Number} - If a trail, how long to make it */
|
|
3715
|
+
this.trailScale = trailScale;
|
|
3716
|
+
/** @property {ParticleEmitter} - Parent emitter if local space */
|
|
3717
|
+
this.localSpaceEmitter = localSpaceEmitter;
|
|
3718
|
+
/** @property {Function} - Called when particle dies */
|
|
3719
|
+
this.destroyCallback = destroyCallback;
|
|
3720
|
+
}
|
|
3657
3721
|
|
|
3658
3722
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
3659
3723
|
render()
|
|
@@ -3671,7 +3735,7 @@ class Particle extends EngineObject
|
|
|
3671
3735
|
(p < fadeRate ? p/fadeRate : p > 1-fadeRate ? (1-p)/fadeRate : 1)); // fade alpha
|
|
3672
3736
|
|
|
3673
3737
|
// draw the particle
|
|
3674
|
-
this.additive && setBlendMode(
|
|
3738
|
+
this.additive && setBlendMode(true);
|
|
3675
3739
|
|
|
3676
3740
|
let pos = this.pos, angle = this.angle;
|
|
3677
3741
|
if (this.localSpaceEmitter)
|
|
@@ -3761,7 +3825,7 @@ class Medal
|
|
|
3761
3825
|
* @param {Number} id - The unique identifier of the medal
|
|
3762
3826
|
* @param {String} name - Name of the medal
|
|
3763
3827
|
* @param {String} [description] - Description of the medal
|
|
3764
|
-
* @param {String} [icon
|
|
3828
|
+
* @param {String} [icon] - Icon for the medal
|
|
3765
3829
|
* @param {String} [src] - Image location for the medal
|
|
3766
3830
|
*/
|
|
3767
3831
|
constructor(id, name, description='', icon='🏆', src)
|
|
@@ -3784,14 +3848,14 @@ class Medal
|
|
|
3784
3848
|
return;
|
|
3785
3849
|
|
|
3786
3850
|
// save the medal
|
|
3787
|
-
ASSERT(medalsSaveName
|
|
3851
|
+
ASSERT(medalsSaveName, 'save name must be set');
|
|
3788
3852
|
localStorage[this.storageKey()] = this.unlocked = 1;
|
|
3789
3853
|
medalsDisplayQueue.push(this);
|
|
3790
3854
|
newgrounds && newgrounds.unlockMedal(this.id);
|
|
3791
3855
|
}
|
|
3792
3856
|
|
|
3793
3857
|
/** Render a medal
|
|
3794
|
-
* @param {Number} [hidePercent
|
|
3858
|
+
* @param {Number} [hidePercent] - How much to slide the medal off screen
|
|
3795
3859
|
*/
|
|
3796
3860
|
render(hidePercent=0)
|
|
3797
3861
|
{
|
|
@@ -3803,25 +3867,25 @@ class Medal
|
|
|
3803
3867
|
// draw containing rect and clip to that region
|
|
3804
3868
|
context.save();
|
|
3805
3869
|
context.beginPath();
|
|
3806
|
-
context.fillStyle =
|
|
3807
|
-
context.strokeStyle =
|
|
3870
|
+
context.fillStyle = rgb(.9,.9,.9).toString();
|
|
3871
|
+
context.strokeStyle = rgb(0,0,0).toString();
|
|
3808
3872
|
context.lineWidth = 3;
|
|
3809
|
-
context.
|
|
3873
|
+
context.rect(x, y, width, medalDisplaySize.y);
|
|
3874
|
+
context.fill();
|
|
3810
3875
|
context.stroke();
|
|
3811
3876
|
context.clip();
|
|
3812
3877
|
|
|
3813
3878
|
// draw the icon and text
|
|
3814
3879
|
this.renderIcon(vec2(x+15+medalDisplayIconSize/2, y+medalDisplaySize.y/2));
|
|
3815
3880
|
const pos = vec2(x+medalDisplayIconSize+30, y+28);
|
|
3816
|
-
drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0,
|
|
3881
|
+
drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, undefined, 'left');
|
|
3817
3882
|
pos.y += 32;
|
|
3818
|
-
drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0,
|
|
3883
|
+
drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, undefined, 'left');
|
|
3819
3884
|
context.restore();
|
|
3820
3885
|
}
|
|
3821
3886
|
|
|
3822
3887
|
/** Render the icon for a medal
|
|
3823
|
-
* @param {
|
|
3824
|
-
* @param {Number} y - Screen space Y position
|
|
3888
|
+
* @param {Vector2} pos - Screen space position
|
|
3825
3889
|
* @param {Number} [size=medalDisplayIconSize] - Screen space size
|
|
3826
3890
|
*/
|
|
3827
3891
|
renderIcon(pos, size=medalDisplayIconSize)
|
|
@@ -3849,7 +3913,10 @@ function medalsRender()
|
|
|
3849
3913
|
if (!medalsDisplayTimeLast)
|
|
3850
3914
|
medalsDisplayTimeLast = timeReal;
|
|
3851
3915
|
else if (time > medalDisplayTime)
|
|
3852
|
-
|
|
3916
|
+
{
|
|
3917
|
+
medalsDisplayTimeLast = 0;
|
|
3918
|
+
medalsDisplayQueue.shift();
|
|
3919
|
+
}
|
|
3853
3920
|
else
|
|
3854
3921
|
{
|
|
3855
3922
|
// slide on/off medals
|
|
@@ -3889,8 +3956,8 @@ class Newgrounds
|
|
|
3889
3956
|
* @param {Object} [cryptoJS] - An instance of CryptoJS, if there is a cipher */
|
|
3890
3957
|
constructor(app_id, cipher, cryptoJS)
|
|
3891
3958
|
{
|
|
3892
|
-
ASSERT(!newgrounds && app_id
|
|
3893
|
-
ASSERT(!cipher || cryptoJS
|
|
3959
|
+
ASSERT(!newgrounds && app_id>0, 'there can only be one newgrounds object');
|
|
3960
|
+
ASSERT(!cipher || cryptoJS, 'must provide cryptojs if there is a cipher');
|
|
3894
3961
|
|
|
3895
3962
|
this.app_id = app_id;
|
|
3896
3963
|
this.cipher = cipher;
|
|
@@ -3933,39 +4000,39 @@ class Newgrounds
|
|
|
3933
4000
|
debugMedals && console.log(this.scoreboards);
|
|
3934
4001
|
|
|
3935
4002
|
const keepAliveMS = 5 * 60 * 1e3;
|
|
3936
|
-
setInterval(()=>this.call('Gateway.ping', 0,
|
|
4003
|
+
setInterval(()=>this.call('Gateway.ping', 0, true), keepAliveMS);
|
|
3937
4004
|
}
|
|
3938
4005
|
|
|
3939
4006
|
/** Send message to unlock a medal by id
|
|
3940
4007
|
* @param {Number} id - The medal id */
|
|
3941
|
-
unlockMedal(id) { return this.call('Medal.unlock', {'id':id},
|
|
4008
|
+
unlockMedal(id) { return this.call('Medal.unlock', {'id':id}, true); }
|
|
3942
4009
|
|
|
3943
4010
|
/** Send message to post score
|
|
3944
4011
|
* @param {Number} id - The scoreboard id
|
|
3945
4012
|
* @param {Number} value - The score value */
|
|
3946
|
-
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value},
|
|
4013
|
+
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, true); }
|
|
3947
4014
|
|
|
3948
4015
|
/** Get scores from a scoreboard
|
|
3949
|
-
* @param {Number} id
|
|
3950
|
-
* @param {String} [user
|
|
3951
|
-
* @param {Number} [social
|
|
3952
|
-
* @param {Number} [skip
|
|
3953
|
-
* @param {Number} [limit
|
|
3954
|
-
* @return {Object}
|
|
4016
|
+
* @param {Number} id - The scoreboard id
|
|
4017
|
+
* @param {String} [user] - A user's id or name
|
|
4018
|
+
* @param {Number} [social] - If true, only social scores will be loaded
|
|
4019
|
+
* @param {Number} [skip] - Number of scores to skip before start
|
|
4020
|
+
* @param {Number} [limit] - Number of scores to include in the list
|
|
4021
|
+
* @return {Object} - The response JSON object
|
|
3955
4022
|
*/
|
|
3956
|
-
getScores(id, user
|
|
4023
|
+
getScores(id, user, social=0, skip=0, limit=10)
|
|
3957
4024
|
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
3958
4025
|
|
|
3959
4026
|
/** Send message to log a view */
|
|
3960
|
-
logView() { return this.call('App.logView', {'host':this.host},
|
|
4027
|
+
logView() { return this.call('App.logView', {'host':this.host}, true); }
|
|
3961
4028
|
|
|
3962
4029
|
/** Send a message to call a component of the Newgrounds API
|
|
3963
|
-
* @param {String} component
|
|
3964
|
-
* @param {Object} [parameters
|
|
3965
|
-
* @param {Boolean} [async
|
|
3966
|
-
* @return {Object}
|
|
4030
|
+
* @param {String} component - Name of the component
|
|
4031
|
+
* @param {Object} [parameters] - Parameters to use for call
|
|
4032
|
+
* @param {Boolean} [async] - If true, don't wait for response before continuing
|
|
4033
|
+
* @return {Object} - The response JSON object
|
|
3967
4034
|
*/
|
|
3968
|
-
call(component, parameters
|
|
4035
|
+
call(component, parameters, async=false)
|
|
3969
4036
|
{
|
|
3970
4037
|
const call = {'component':component, 'parameters':parameters};
|
|
3971
4038
|
if (this.cipher)
|
|
@@ -4000,7 +4067,7 @@ class Newgrounds
|
|
|
4000
4067
|
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
4001
4068
|
}
|
|
4002
4069
|
}
|
|
4003
|
-
/**
|
|
4070
|
+
/**
|
|
4004
4071
|
* LittleJS WebGL Interface
|
|
4005
4072
|
* - All webgl used by the engine is wrapped up here
|
|
4006
4073
|
* - For normal stuff you won't need to see or call anything in this file
|
|
@@ -4019,13 +4086,13 @@ class Newgrounds
|
|
|
4019
4086
|
* @memberof WebGL */
|
|
4020
4087
|
let glCanvas;
|
|
4021
4088
|
|
|
4022
|
-
/** 2d context for glCanvas
|
|
4023
|
-
* @type {
|
|
4089
|
+
/** 2d context for glCanvas
|
|
4090
|
+
* @type {WebGL2RenderingContext}
|
|
4024
4091
|
* @memberof WebGL */
|
|
4025
4092
|
let glContext;
|
|
4026
4093
|
|
|
4027
4094
|
// WebGL internal variables not exposed to documentation
|
|
4028
|
-
let
|
|
4095
|
+
let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
|
|
4029
4096
|
|
|
4030
4097
|
///////////////////////////////////////////////////////////////////////////////
|
|
4031
4098
|
|
|
@@ -4041,73 +4108,89 @@ function glInit()
|
|
|
4041
4108
|
|
|
4042
4109
|
// setup vertex and fragment shaders
|
|
4043
4110
|
glShader = glCreateProgram(
|
|
4044
|
-
'#version 300 es\n' +
|
|
4045
|
-
'precision highp float;'+
|
|
4046
|
-
'uniform mat4 m;'+
|
|
4047
|
-
'in
|
|
4048
|
-
'
|
|
4049
|
-
'
|
|
4050
|
-
'
|
|
4051
|
-
'
|
|
4052
|
-
'
|
|
4111
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
4112
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
4113
|
+
'uniform mat4 m;'+ // transform matrix
|
|
4114
|
+
'in vec2 g;'+ // geometry
|
|
4115
|
+
'in vec4 p,u,c,a;'+ // position/size, uvs, color, additiveColor
|
|
4116
|
+
'in float r;'+ // rotation
|
|
4117
|
+
'out vec2 v;'+ // return uv, color, additiveColor
|
|
4118
|
+
'out vec4 d,e;'+ // return uv, color, additiveColor
|
|
4119
|
+
'void main(){'+ // shader entry point
|
|
4120
|
+
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
4121
|
+
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
4122
|
+
'v=mix(u.xw,u.zy,g);'+ // pass uv to fragment shader
|
|
4123
|
+
'd=c;e=a;'+ // pass colors to fragment shader
|
|
4124
|
+
'}' // end of shader
|
|
4053
4125
|
,
|
|
4054
|
-
'#version 300 es\n' +
|
|
4055
|
-
'precision highp float;'+
|
|
4056
|
-
'in
|
|
4057
|
-
'
|
|
4058
|
-
'
|
|
4059
|
-
'
|
|
4060
|
-
'
|
|
4061
|
-
'
|
|
4126
|
+
'#version 300 es\n' + // specify GLSL ES version
|
|
4127
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
4128
|
+
'in vec2 v;'+ // uv
|
|
4129
|
+
'in vec4 d,e;'+ // color, additiveColor
|
|
4130
|
+
'uniform sampler2D s;'+ // texture
|
|
4131
|
+
'out vec4 c;'+ // out color
|
|
4132
|
+
'void main(){'+ // shader entry point
|
|
4133
|
+
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
4134
|
+
'}' // end of shader
|
|
4062
4135
|
);
|
|
4063
4136
|
|
|
4064
4137
|
// init buffers
|
|
4065
|
-
|
|
4066
|
-
glPositionData = new Float32Array(
|
|
4067
|
-
glColorData = new Uint32Array(
|
|
4138
|
+
const glInstanceData = new ArrayBuffer(gl_INSTANCE_BUFFER_SIZE);
|
|
4139
|
+
glPositionData = new Float32Array(glInstanceData);
|
|
4140
|
+
glColorData = new Uint32Array(glInstanceData);
|
|
4068
4141
|
glArrayBuffer = glContext.createBuffer();
|
|
4069
|
-
|
|
4142
|
+
glGeometryBuffer = glContext.createBuffer();
|
|
4143
|
+
|
|
4144
|
+
// create the geometry buffer, triangle strip square
|
|
4145
|
+
const geometry = new Float32Array([glInstanceCount=0,0,1,0,0,1,1,1]);
|
|
4146
|
+
glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
|
|
4147
|
+
glContext.bufferData(gl_ARRAY_BUFFER, geometry, gl_STATIC_DRAW);
|
|
4070
4148
|
}
|
|
4071
4149
|
|
|
4072
4150
|
// Setup render each frame, called automatically by engine
|
|
4073
4151
|
function glPreRender()
|
|
4074
4152
|
{
|
|
4075
4153
|
// clear and set to same size as main canvas
|
|
4076
|
-
glContext.viewport(0, 0, glCanvas.width
|
|
4154
|
+
glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
|
|
4077
4155
|
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
4078
4156
|
|
|
4079
4157
|
// set up the shader
|
|
4080
4158
|
glContext.useProgram(glShader);
|
|
4081
4159
|
glContext.activeTexture(gl_TEXTURE0);
|
|
4082
4160
|
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
|
|
4083
|
-
|
|
4084
|
-
glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
|
|
4085
|
-
glAdditive = 0;
|
|
4086
|
-
|
|
4161
|
+
|
|
4087
4162
|
// set vertex attributes
|
|
4088
|
-
let offset = 0;
|
|
4089
|
-
|
|
4163
|
+
let offset = glAdditive = glBatchAdditive = 0;
|
|
4164
|
+
let initVertexAttribArray = (name, type, typeSize, size)=>
|
|
4090
4165
|
{
|
|
4091
4166
|
const location = glContext.getAttribLocation(glShader, name);
|
|
4167
|
+
const stride = typeSize && gl_INSTANCE_BYTE_STRIDE; // only if not geometry
|
|
4168
|
+
const divisor = typeSize && 1; // only if not geometry
|
|
4169
|
+
const normalize = typeSize==1; // only if color
|
|
4092
4170
|
glContext.enableVertexAttribArray(location);
|
|
4093
|
-
glContext.vertexAttribPointer(location, size, type, normalize,
|
|
4171
|
+
glContext.vertexAttribPointer(location, size, type, normalize, stride, offset);
|
|
4172
|
+
glContext.vertexAttribDivisor(location, divisor);
|
|
4094
4173
|
offset += size*typeSize;
|
|
4095
4174
|
}
|
|
4096
|
-
|
|
4097
|
-
initVertexAttribArray('
|
|
4098
|
-
|
|
4175
|
+
glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
|
|
4176
|
+
initVertexAttribArray('g', gl_FLOAT, 0, 2); // geometry
|
|
4177
|
+
glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
|
|
4178
|
+
glContext.bufferData(gl_ARRAY_BUFFER, gl_INSTANCE_BUFFER_SIZE, gl_DYNAMIC_DRAW);
|
|
4179
|
+
initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & size
|
|
4180
|
+
initVertexAttribArray('u', gl_FLOAT, 4, 4); // texture coords
|
|
4181
|
+
initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4); // color
|
|
4182
|
+
initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4); // additiveColor
|
|
4183
|
+
initVertexAttribArray('r', gl_FLOAT, 4, 1); // rotation
|
|
4099
4184
|
|
|
4100
4185
|
// build the transform matrix
|
|
4101
|
-
const
|
|
4102
|
-
const
|
|
4103
|
-
|
|
4104
|
-
const cy = -1 - sy*cameraPos.y;
|
|
4105
|
-
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
|
|
4186
|
+
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
4187
|
+
const p = vec2(-1).subtract(cameraPos.multiply(s));
|
|
4188
|
+
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
|
|
4106
4189
|
new Float32Array([
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4190
|
+
s.x, 0, 0, 0,
|
|
4191
|
+
0, s.y, 0, 0,
|
|
4192
|
+
1, 1, 1, 1,
|
|
4193
|
+
p.x, p.y, 0, 0
|
|
4111
4194
|
])
|
|
4112
4195
|
);
|
|
4113
4196
|
}
|
|
@@ -4128,7 +4211,7 @@ function glSetTexture(texture)
|
|
|
4128
4211
|
|
|
4129
4212
|
/** Compile WebGL shader of the given type, will throw errors if in debug mode
|
|
4130
4213
|
* @param {String} source
|
|
4131
|
-
* @param
|
|
4214
|
+
* @param {Number} type
|
|
4132
4215
|
* @return {WebGLShader}
|
|
4133
4216
|
* @memberof WebGL */
|
|
4134
4217
|
function glCompileShader(source, type)
|
|
@@ -4145,8 +4228,8 @@ function glCompileShader(source, type)
|
|
|
4145
4228
|
}
|
|
4146
4229
|
|
|
4147
4230
|
/** Create WebGL program with given shaders
|
|
4148
|
-
* @param {
|
|
4149
|
-
* @param {
|
|
4231
|
+
* @param {String} vsSource
|
|
4232
|
+
* @param {String} fsSource
|
|
4150
4233
|
* @return {WebGLProgram}
|
|
4151
4234
|
* @memberof WebGL */
|
|
4152
4235
|
function glCreateProgram(vsSource, fsSource)
|
|
@@ -4164,7 +4247,7 @@ function glCreateProgram(vsSource, fsSource)
|
|
|
4164
4247
|
}
|
|
4165
4248
|
|
|
4166
4249
|
/** Create WebGL texture from an image and init the texture settings
|
|
4167
|
-
* @param {
|
|
4250
|
+
* @param {HTMLImageElement} image
|
|
4168
4251
|
* @return {WebGLTexture}
|
|
4169
4252
|
* @memberof WebGL */
|
|
4170
4253
|
function glCreateTexture(image)
|
|
@@ -4174,7 +4257,7 @@ function glCreateTexture(image)
|
|
|
4174
4257
|
glContext.bindTexture(gl_TEXTURE_2D, texture);
|
|
4175
4258
|
if (image)
|
|
4176
4259
|
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
4177
|
-
|
|
4260
|
+
|
|
4178
4261
|
// use point filtering for pixelated rendering
|
|
4179
4262
|
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
4180
4263
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
@@ -4189,29 +4272,31 @@ function glCreateTexture(image)
|
|
|
4189
4272
|
* @memberof WebGL */
|
|
4190
4273
|
function glFlush()
|
|
4191
4274
|
{
|
|
4192
|
-
if (!
|
|
4275
|
+
if (!glInstanceCount) return;
|
|
4193
4276
|
|
|
4194
4277
|
const destBlend = glBatchAdditive ? gl_ONE : gl_ONE_MINUS_SRC_ALPHA;
|
|
4195
4278
|
glContext.blendFuncSeparate(gl_SRC_ALPHA, destBlend, gl_ONE, destBlend);
|
|
4196
4279
|
glContext.enable(gl_BLEND);
|
|
4197
4280
|
|
|
4198
4281
|
// draw all the sprites in the batch and reset the buffer
|
|
4199
|
-
glContext.bufferSubData(gl_ARRAY_BUFFER, 0,
|
|
4200
|
-
glContext.
|
|
4201
|
-
|
|
4282
|
+
glContext.bufferSubData(gl_ARRAY_BUFFER, 0, glPositionData);
|
|
4283
|
+
glContext.drawArraysInstanced(gl_TRIANGLE_STRIP, 0, 4, glInstanceCount);
|
|
4284
|
+
if (showWatermark)
|
|
4285
|
+
drawCount += glInstanceCount;
|
|
4286
|
+
glInstanceCount = 0;
|
|
4202
4287
|
glBatchAdditive = glAdditive;
|
|
4203
4288
|
}
|
|
4204
4289
|
|
|
4205
4290
|
/** Draw any sprites still in the buffer, copy to main canvas and clear
|
|
4206
4291
|
* @param {CanvasRenderingContext2D} context
|
|
4207
|
-
* @param {Boolean} [forceDraw
|
|
4292
|
+
* @param {Boolean} [forceDraw]
|
|
4208
4293
|
* @memberof WebGL */
|
|
4209
|
-
function glCopyToContext(context, forceDraw)
|
|
4294
|
+
function glCopyToContext(context, forceDraw=false)
|
|
4210
4295
|
{
|
|
4211
|
-
if (!
|
|
4212
|
-
|
|
4296
|
+
if (!glInstanceCount && !forceDraw) return;
|
|
4297
|
+
|
|
4213
4298
|
glFlush();
|
|
4214
|
-
|
|
4299
|
+
|
|
4215
4300
|
// do not draw in overlay mode because the canvas is visible
|
|
4216
4301
|
if (!glOverlay || forceDraw)
|
|
4217
4302
|
context.drawImage(glCanvas, 0, 0);
|
|
@@ -4233,60 +4318,22 @@ function glCopyToContext(context, forceDraw)
|
|
|
4233
4318
|
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
|
|
4234
4319
|
{
|
|
4235
4320
|
// flush if there is not enough room or if different blend mode
|
|
4236
|
-
|
|
4237
|
-
if (glBatchCount >= gl_MAX_BATCH-vertCount || glBatchAdditive != glAdditive)
|
|
4321
|
+
if (glInstanceCount >= gl_MAX_INSTANCES-1 || glBatchAdditive != glAdditive)
|
|
4238
4322
|
glFlush();
|
|
4239
4323
|
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
[
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
];
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
{
|
|
4254
|
-
const j = clamp(i-1, 0, 3)*4; // degenerate tri at ends
|
|
4255
|
-
glPositionData[offset++] = positionData[j+0];
|
|
4256
|
-
glPositionData[offset++] = positionData[j+1];
|
|
4257
|
-
glPositionData[offset++] = positionData[j+2];
|
|
4258
|
-
glPositionData[offset++] = positionData[j+3];
|
|
4259
|
-
glColorData[offset++] = rgba;
|
|
4260
|
-
glColorData[offset++] = rgbaAdditive;
|
|
4261
|
-
}
|
|
4262
|
-
glBatchCount += vertCount;
|
|
4263
|
-
}
|
|
4264
|
-
|
|
4265
|
-
/** Add a convex polygon to the gl draw list
|
|
4266
|
-
* @param {Array} points - Array of Vector2 points
|
|
4267
|
-
* @param {Number} rgba - Color of the polygon
|
|
4268
|
-
* @memberof WebGL */
|
|
4269
|
-
function glDrawPoints(points, rgba)
|
|
4270
|
-
{
|
|
4271
|
-
// flush if there is not enough room or if different blend mode
|
|
4272
|
-
const vertCount = points.length + 2;
|
|
4273
|
-
if (glBatchCount >= gl_MAX_BATCH-vertCount || glBatchAdditive != glAdditive)
|
|
4274
|
-
glFlush();
|
|
4275
|
-
|
|
4276
|
-
// setup triangle strip from list of points
|
|
4277
|
-
for(let i = vertCount, offset = glBatchCount * gl_INDICIES_PER_VERT; i--;)
|
|
4278
|
-
{
|
|
4279
|
-
const j = clamp(i-1, 0, vertCount-3); // degenerate tri at ends
|
|
4280
|
-
const h = j>>1;
|
|
4281
|
-
const point = points[j%2? h : vertCount-3-h];
|
|
4282
|
-
glPositionData[offset++] = point.x;
|
|
4283
|
-
glPositionData[offset++] = point.y;
|
|
4284
|
-
glPositionData[offset++] = 0; // uvx
|
|
4285
|
-
glPositionData[offset++] = 0; // uvy
|
|
4286
|
-
glColorData[offset++] = 0; // nothing to tint
|
|
4287
|
-
glColorData[offset++] = rgba; // apply rgba via additive
|
|
4288
|
-
}
|
|
4289
|
-
glBatchCount += vertCount;
|
|
4324
|
+
let offset = glInstanceCount * gl_INDICIES_PER_INSTANCE;
|
|
4325
|
+
glPositionData[offset++] = x;
|
|
4326
|
+
glPositionData[offset++] = y;
|
|
4327
|
+
glPositionData[offset++] = sizeX;
|
|
4328
|
+
glPositionData[offset++] = sizeY;
|
|
4329
|
+
glPositionData[offset++] = uv0X;
|
|
4330
|
+
glPositionData[offset++] = uv0Y;
|
|
4331
|
+
glPositionData[offset++] = uv1X;
|
|
4332
|
+
glPositionData[offset++] = uv1Y;
|
|
4333
|
+
glColorData[offset++] = rgba;
|
|
4334
|
+
glColorData[offset++] = rgbaAdditive;
|
|
4335
|
+
glPositionData[offset++] = angle;
|
|
4336
|
+
glInstanceCount++;
|
|
4290
4337
|
}
|
|
4291
4338
|
|
|
4292
4339
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -4300,7 +4347,7 @@ let glPostShader, glPostArrayBuffer, glPostTexture, glPostIncludeOverlay;
|
|
|
4300
4347
|
* @memberof WebGL */
|
|
4301
4348
|
function glInitPostProcess(shaderCode, includeOverlay)
|
|
4302
4349
|
{
|
|
4303
|
-
ASSERT(!glPostShader
|
|
4350
|
+
ASSERT(!glPostShader, 'can only have 1 post effects shader');
|
|
4304
4351
|
|
|
4305
4352
|
if (!shaderCode) // default shader pass through
|
|
4306
4353
|
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
@@ -4311,14 +4358,14 @@ function glInitPostProcess(shaderCode, includeOverlay)
|
|
|
4311
4358
|
'precision highp float;'+ // use highp for better accuracy
|
|
4312
4359
|
'in vec2 p;'+ // position
|
|
4313
4360
|
'void main(){'+ // shader entry point
|
|
4314
|
-
'gl_Position=vec4(p
|
|
4361
|
+
'gl_Position=vec4(p+p-1.,1,1);'+ // set position
|
|
4315
4362
|
'}' // end of shader
|
|
4316
4363
|
,
|
|
4317
4364
|
'#version 300 es\n' + // specify GLSL ES version
|
|
4318
4365
|
'precision highp float;'+ // use highp for better accuracy
|
|
4319
4366
|
'uniform sampler2D iChannel0;'+ // input texture
|
|
4320
4367
|
'uniform vec3 iResolution;'+ // size of output texture
|
|
4321
|
-
'uniform float iTime;'+ // time
|
|
4368
|
+
'uniform float iTime;'+ // time
|
|
4322
4369
|
'out vec4 c;'+ // out color
|
|
4323
4370
|
'\n' + shaderCode + '\n'+ // insert custom shader code
|
|
4324
4371
|
'void main(){'+ // shader entry point
|
|
@@ -4329,7 +4376,7 @@ function glInitPostProcess(shaderCode, includeOverlay)
|
|
|
4329
4376
|
|
|
4330
4377
|
// create buffer and texture
|
|
4331
4378
|
glPostArrayBuffer = glContext.createBuffer();
|
|
4332
|
-
glPostTexture = glCreateTexture();
|
|
4379
|
+
glPostTexture = glCreateTexture(undefined);
|
|
4333
4380
|
glPostIncludeOverlay = includeOverlay;
|
|
4334
4381
|
|
|
4335
4382
|
// hide the original 2d canvas
|
|
@@ -4365,10 +4412,9 @@ function glRenderPostProcess()
|
|
|
4365
4412
|
|
|
4366
4413
|
// setup shader program to draw one triangle
|
|
4367
4414
|
glContext.useProgram(glPostShader);
|
|
4415
|
+
glContext.bindBuffer(gl_ARRAY_BUFFER, glGeometryBuffer);
|
|
4416
|
+
glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, 1);
|
|
4368
4417
|
glContext.disable(gl_BLEND);
|
|
4369
|
-
glContext.bindBuffer(gl_ARRAY_BUFFER, glPostArrayBuffer);
|
|
4370
|
-
glContext.bufferData(gl_ARRAY_BUFFER, new Float32Array([-3,1,1,-3,1,1]), gl_STATIC_DRAW);
|
|
4371
|
-
glContext.pixelStorei(gl_UNPACK_FLIP_Y_WEBGL, true);
|
|
4372
4418
|
|
|
4373
4419
|
// set textures, pass in the 2d canvas and gl canvas in separate texture channels
|
|
4374
4420
|
glContext.activeTexture(gl_TEXTURE0);
|
|
@@ -4379,19 +4425,19 @@ function glRenderPostProcess()
|
|
|
4379
4425
|
const vertexByteStride = 8;
|
|
4380
4426
|
const pLocation = glContext.getAttribLocation(glPostShader, 'p');
|
|
4381
4427
|
glContext.enableVertexAttribArray(pLocation);
|
|
4382
|
-
glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT,
|
|
4428
|
+
glContext.vertexAttribPointer(pLocation, 2, gl_FLOAT, false, vertexByteStride, 0);
|
|
4383
4429
|
|
|
4384
4430
|
// set uniforms and draw
|
|
4385
4431
|
const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
|
|
4386
4432
|
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
4387
4433
|
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
4388
4434
|
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
4389
|
-
glContext.drawArrays(gl_TRIANGLE_STRIP, 0,
|
|
4435
|
+
glContext.drawArrays(gl_TRIANGLE_STRIP, 0, 4);
|
|
4390
4436
|
}
|
|
4391
4437
|
|
|
4392
4438
|
///////////////////////////////////////////////////////////////////////////////
|
|
4393
4439
|
// store gl constants as integers so their name doesn't use space in minifed
|
|
4394
|
-
const
|
|
4440
|
+
const
|
|
4395
4441
|
gl_ONE = 1,
|
|
4396
4442
|
gl_TRIANGLE_STRIP = 5,
|
|
4397
4443
|
gl_SRC_ALPHA = 770,
|
|
@@ -4413,17 +4459,17 @@ gl_TEXTURE0 = 33984,
|
|
|
4413
4459
|
gl_ARRAY_BUFFER = 34962,
|
|
4414
4460
|
gl_STATIC_DRAW = 35044,
|
|
4415
4461
|
gl_DYNAMIC_DRAW = 35048,
|
|
4416
|
-
gl_FRAGMENT_SHADER = 35632,
|
|
4462
|
+
gl_FRAGMENT_SHADER = 35632,
|
|
4417
4463
|
gl_VERTEX_SHADER = 35633,
|
|
4418
4464
|
gl_COMPILE_STATUS = 35713,
|
|
4419
4465
|
gl_LINK_STATUS = 35714,
|
|
4420
4466
|
gl_UNPACK_FLIP_Y_WEBGL = 37440,
|
|
4421
4467
|
|
|
4422
4468
|
// constants for batch rendering
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4469
|
+
gl_INDICIES_PER_INSTANCE = 11,
|
|
4470
|
+
gl_MAX_INSTANCES = 1e4,
|
|
4471
|
+
gl_INSTANCE_BYTE_STRIDE = gl_INDICIES_PER_INSTANCE * 4, // 11 * 4
|
|
4472
|
+
gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
|
|
4427
4473
|
/**
|
|
4428
4474
|
* LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
4429
4475
|
* MIT License - Copyright 2021 Frank Force
|
|
@@ -4456,7 +4502,7 @@ const engineName = 'LittleJS';
|
|
|
4456
4502
|
* @type {String}
|
|
4457
4503
|
* @default
|
|
4458
4504
|
* @memberof Engine */
|
|
4459
|
-
const engineVersion = '1.
|
|
4505
|
+
const engineVersion = '1.9.0';
|
|
4460
4506
|
|
|
4461
4507
|
/** Frames per second to update objects
|
|
4462
4508
|
* @type {Number}
|
|
@@ -4497,14 +4543,14 @@ let timeReal = 0;
|
|
|
4497
4543
|
|
|
4498
4544
|
/** Is the game paused? Causes time and objects to not be updated
|
|
4499
4545
|
* @type {Boolean}
|
|
4500
|
-
* @default
|
|
4546
|
+
* @default false
|
|
4501
4547
|
* @memberof Engine */
|
|
4502
|
-
let paused =
|
|
4548
|
+
let paused = false;
|
|
4503
4549
|
|
|
4504
4550
|
/** Set if game is paused
|
|
4505
|
-
* @param {Boolean}
|
|
4551
|
+
* @param {Boolean} isPaused
|
|
4506
4552
|
* @memberof Engine */
|
|
4507
|
-
function setPaused(
|
|
4553
|
+
function setPaused(isPaused) { paused = isPaused; }
|
|
4508
4554
|
|
|
4509
4555
|
// Frame time tracking
|
|
4510
4556
|
let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
@@ -4521,7 +4567,7 @@ let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
|
4521
4567
|
* @memberof Engine */
|
|
4522
4568
|
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=['tiles.png'])
|
|
4523
4569
|
{
|
|
4524
|
-
ASSERT(Array.isArray(imageSources)
|
|
4570
|
+
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
4525
4571
|
|
|
4526
4572
|
// internal update loop for engine
|
|
4527
4573
|
function engineUpdate(frameTimeMS=0)
|
|
@@ -4531,12 +4577,12 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4531
4577
|
frameTimeLastMS = frameTimeMS;
|
|
4532
4578
|
if (debug || showWatermark)
|
|
4533
4579
|
averageFPS = lerp(.05, averageFPS, 1e3/(frameTimeDeltaMS||1));
|
|
4534
|
-
const debugSpeedUp = debug && keyIsDown(
|
|
4535
|
-
const debugSpeedDown = debug && keyIsDown(
|
|
4580
|
+
const debugSpeedUp = debug && keyIsDown('Equal'); // +
|
|
4581
|
+
const debugSpeedDown = debug && keyIsDown('Minus'); // -
|
|
4536
4582
|
if (debug) // +/- to speed/slow time
|
|
4537
4583
|
frameTimeDeltaMS *= debugSpeedUp ? 5 : debugSpeedDown ? .2 : 1;
|
|
4538
4584
|
timeReal += frameTimeDeltaMS / 1e3;
|
|
4539
|
-
frameTimeBufferMS +=
|
|
4585
|
+
frameTimeBufferMS += paused ? 0 : frameTimeDeltaMS;
|
|
4540
4586
|
if (!debugSpeedUp)
|
|
4541
4587
|
frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp incase of slow framerate
|
|
4542
4588
|
|
|
@@ -4646,7 +4692,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4646
4692
|
'user-select:none;' + // prevent mobile hold to select
|
|
4647
4693
|
'-webkit-user-select:none;' + // compatibility for ios
|
|
4648
4694
|
'-webkit-touch-callout:none'; // compatibility for ios
|
|
4649
|
-
document.body.style = styleBody;
|
|
4695
|
+
document.body.style.cssText = styleBody;
|
|
4650
4696
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
4651
4697
|
mainContext = mainCanvas.getContext('2d');
|
|
4652
4698
|
|
|
@@ -4659,10 +4705,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4659
4705
|
overlayContext = overlayCanvas.getContext('2d');
|
|
4660
4706
|
|
|
4661
4707
|
// set canvas style
|
|
4662
|
-
const styleCanvas =
|
|
4663
|
-
'position:absolute;' + // position
|
|
4708
|
+
const styleCanvas = 'position:absolute;' + // position
|
|
4664
4709
|
'top:50%;left:50%;transform:translate(-50%,-50%)'; // center
|
|
4665
|
-
(glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
|
|
4710
|
+
(glCanvas||mainCanvas).style.cssText = mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
|
|
4666
4711
|
|
|
4667
4712
|
// create promises for loading images
|
|
4668
4713
|
const promises = imageSources.map((src, textureIndex)=>
|
|
@@ -4682,13 +4727,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4682
4727
|
showSplashScreen && promises.push(new Promise(resolve =>
|
|
4683
4728
|
{
|
|
4684
4729
|
let t = 0;
|
|
4685
|
-
console.log(
|
|
4730
|
+
console.log(`${engineName} Engine v${engineVersion}`);
|
|
4686
4731
|
updateSplash();
|
|
4687
4732
|
function updateSplash()
|
|
4688
4733
|
{
|
|
4689
4734
|
clearInput();
|
|
4690
4735
|
drawEngineSplashScreen(t+=.01);
|
|
4691
|
-
t>1 ? resolve() : setTimeout(updateSplash,16);
|
|
4736
|
+
t>1 ? resolve() : setTimeout(updateSplash, 16);
|
|
4692
4737
|
}
|
|
4693
4738
|
}));
|
|
4694
4739
|
|
|
@@ -4749,7 +4794,7 @@ function engineObjectsDestroy()
|
|
|
4749
4794
|
|
|
4750
4795
|
/** Triggers a callback for each object within a given area
|
|
4751
4796
|
* @param {Vector2} [pos] - Center of test area
|
|
4752
|
-
* @param {Number} [size]
|
|
4797
|
+
* @param {Number|Vector2} [size] - Radius of circle if float, rectangle size if Vector2
|
|
4753
4798
|
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
4754
4799
|
* @param {Array} [objects=engineObjects] - List of objects to check
|
|
4755
4800
|
* @memberof Engine */
|
|
@@ -4760,7 +4805,7 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
|
|
|
4760
4805
|
for (const o of objects)
|
|
4761
4806
|
callbackFunction(o);
|
|
4762
4807
|
}
|
|
4763
|
-
else if (size
|
|
4808
|
+
else if (typeof size === 'object') // bounding box test
|
|
4764
4809
|
{
|
|
4765
4810
|
for (const o of objects)
|
|
4766
4811
|
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
@@ -4778,21 +4823,23 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
|
|
|
4778
4823
|
|
|
4779
4824
|
function drawEngineSplashScreen(t)
|
|
4780
4825
|
{
|
|
4781
|
-
|
|
4782
|
-
const
|
|
4783
|
-
const
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4826
|
+
const x = overlayContext;
|
|
4827
|
+
const w = overlayCanvas.width = innerWidth;
|
|
4828
|
+
const h = overlayCanvas.height = innerHeight;
|
|
4829
|
+
|
|
4830
|
+
{
|
|
4831
|
+
// background
|
|
4832
|
+
const p3 = percent(t, 1, .8);
|
|
4833
|
+
const p4 = percent(t, 0, .5);
|
|
4834
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
|
|
4835
|
+
g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3).toString());
|
|
4836
|
+
g.addColorStop(1,hsl(0,0,0,p3).toString());
|
|
4837
|
+
x.save();
|
|
4838
|
+
x.fillStyle = g;
|
|
4839
|
+
x.fillRect(0,0,w,h);
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4842
|
+
// draw LittleJS logo...
|
|
4796
4843
|
const rect = (X, Y, W, H, C)=>
|
|
4797
4844
|
{
|
|
4798
4845
|
x.beginPath();
|
|
@@ -4817,7 +4864,7 @@ function drawEngineSplashScreen(t)
|
|
|
4817
4864
|
C ? x.fill() : x.stroke();
|
|
4818
4865
|
};
|
|
4819
4866
|
const color = (c=0, l=0) =>
|
|
4820
|
-
hsl([.98,.3,.57,.14][c%4]-10
|
|
4867
|
+
hsl([.98,.3,.57,.14][c%4]-10,.8,[0,.3,.5,.8,.9][l]).toString();
|
|
4821
4868
|
const alpha = wave(1,1,t);
|
|
4822
4869
|
const p = percent(alpha, .1, .5);
|
|
4823
4870
|
|
|
@@ -4827,7 +4874,7 @@ function drawEngineSplashScreen(t)
|
|
|
4827
4874
|
x.scale(size,size);
|
|
4828
4875
|
x.translate(-40,-35);
|
|
4829
4876
|
x.lineJoin = x.lineCap = 'round';
|
|
4830
|
-
x.lineWidth = 1+p;
|
|
4877
|
+
x.lineWidth = .1 + p*1.9;
|
|
4831
4878
|
|
|
4832
4879
|
// drawing effect
|
|
4833
4880
|
const p2 = percent(alpha,.1,1);
|
|
@@ -4852,7 +4899,7 @@ function drawEngineSplashScreen(t)
|
|
|
4852
4899
|
|
|
4853
4900
|
// little stack
|
|
4854
4901
|
rect(37,14,9,6,color(3,2));
|
|
4855
|
-
rect(37,14,4,6,color(3,3));
|
|
4902
|
+
rect(37,14,4.5,6,color(3,3));
|
|
4856
4903
|
rect(37,14,9,6);
|
|
4857
4904
|
|
|
4858
4905
|
// big stack
|
|
@@ -4860,9 +4907,9 @@ function drawEngineSplashScreen(t)
|
|
|
4860
4907
|
rect(50,20,6,-10,color(0,2));
|
|
4861
4908
|
rect(50,20,3,-10,color(0,3));
|
|
4862
4909
|
rect(50,10,10,10);
|
|
4863
|
-
circle(55,2,11,.5,PI-.5,color(3,3));
|
|
4864
|
-
circle(55,2,11,.5,PI/2,color(3,2),1);
|
|
4865
|
-
circle(55,2,11,.5,PI-.5);
|
|
4910
|
+
circle(55,2,11.4,.5,PI-.5,color(3,3));
|
|
4911
|
+
circle(55,2,11.4,.5,PI/2,color(3,2),1);
|
|
4912
|
+
circle(55,2,11.4,.5,PI-.5);
|
|
4866
4913
|
rect(45,7,20,-7,color(0,2));
|
|
4867
4914
|
rect(45,0,20,3,color(0,3));
|
|
4868
4915
|
rect(45,0,20,7);
|
|
@@ -4896,12 +4943,14 @@ function drawEngineSplashScreen(t)
|
|
|
4896
4943
|
x.lineTo(53+(1+i*2.9)*p,40);
|
|
4897
4944
|
x.lineTo(53+(4+i*3.5)*p,54);
|
|
4898
4945
|
x.fillStyle = color(0,i%2+2);
|
|
4899
|
-
x.fill()
|
|
4946
|
+
x.fill();
|
|
4947
|
+
i%2 && x.stroke();
|
|
4900
4948
|
}
|
|
4901
4949
|
|
|
4902
4950
|
// wheels
|
|
4903
|
-
rect(
|
|
4904
|
-
rect(
|
|
4951
|
+
rect(6,40,5,5);
|
|
4952
|
+
rect(6,40,5,5,color());
|
|
4953
|
+
rect(15,54,38,-14,color());
|
|
4905
4954
|
for (let i=3; i--;)
|
|
4906
4955
|
for (let j=2; j--;)
|
|
4907
4956
|
{
|
|
@@ -4910,26 +4959,26 @@ function drawEngineSplashScreen(t)
|
|
|
4910
4959
|
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
4911
4960
|
x.stroke();
|
|
4912
4961
|
}
|
|
4913
|
-
line(6,40,68,40) // center
|
|
4914
|
-
line(77,54,4,54) // bottom
|
|
4962
|
+
line(6,40,68,40); // center
|
|
4963
|
+
line(77,54,4,54); // bottom
|
|
4915
4964
|
|
|
4916
|
-
//
|
|
4965
|
+
// draw engine name
|
|
4917
4966
|
const s = engineName;
|
|
4918
4967
|
x.font = '900 16px arial';
|
|
4919
4968
|
x.textAlign = 'center';
|
|
4920
4969
|
x.textBaseline = 'top';
|
|
4921
|
-
x.lineWidth = 1+p*3
|
|
4970
|
+
x.lineWidth = .1+p*3.9;
|
|
4922
4971
|
let w2 = 0;
|
|
4923
4972
|
for (let i=0; i<s.length; ++i)
|
|
4924
4973
|
w2 += x.measureText(s[i]).width;
|
|
4925
4974
|
for (let j=2; j--;)
|
|
4926
4975
|
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
4927
4976
|
{
|
|
4928
|
-
x.fillStyle = color(i,
|
|
4977
|
+
x.fillStyle = color(i,2);
|
|
4929
4978
|
const w = x.measureText(s[i]).width;
|
|
4930
4979
|
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
4931
4980
|
X += w;
|
|
4932
4981
|
}
|
|
4933
|
-
|
|
4982
|
+
|
|
4934
4983
|
x.restore();
|
|
4935
4984
|
}
|