littlejsengine 1.4.6 → 1.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -0
- package/build.bat +7 -3
- package/engine/engine.all.js +276 -295
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.module.js +286 -318
- package/engine/engine.all.module.min.js +1 -0
- package/engine/engine.all.release.js +276 -295
- package/engine/engine.js +28 -30
- package/engine/engineAudio.js +8 -15
- package/engine/engineBuild.bat +10 -3
- package/engine/engineDraw.js +14 -18
- package/engine/engineExport.js +10 -23
- package/engine/engineFont.png +0 -0
- package/engine/engineInput.js +86 -69
- package/engine/engineMedals.js +25 -32
- package/engine/engineObject.js +2 -2
- package/engine/engineParticles.js +7 -6
- package/engine/engineSettings.js +28 -28
- package/engine/engineTileLayer.js +15 -16
- package/engine/engineUtilities.js +26 -13
- package/engine/engineWebGL.js +38 -67
- package/engine/index.d.ts +52 -43
- package/examples/breakout/index.html +3 -3
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +12 -12
- package/examples/platformer/index.html +6 -6
- package/examples/puzzle/index.html +2 -2
- package/examples/stress/index.html +1 -1
- package/favicon.png +0 -0
- package/game.js +6 -8
- package/index.html +13 -13
- package/package.json +1 -1
- package/engine/engine.all.release.module.js +0 -4457
|
@@ -137,7 +137,7 @@ const wave = (frequency=1, amplitude=1, t=time)=> amplitude/2 * (1 - Math.cos(t*
|
|
|
137
137
|
* @param {Number} t - time in seconds
|
|
138
138
|
* @return {String}
|
|
139
139
|
* @memberof Utilities */
|
|
140
|
-
const formatTime = (t)=> (t/60|0)+':'+(t%60<10?'0':'')+(t%60|0);
|
|
140
|
+
const formatTime = (t)=> (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0);
|
|
141
141
|
|
|
142
142
|
///////////////////////////////////////////////////////////////////////////////
|
|
143
143
|
|
|
@@ -161,7 +161,7 @@ const randInt = (a=1, b=0)=> rand(a,b)|0;
|
|
|
161
161
|
/** Randomly returns either -1 or 1
|
|
162
162
|
* @return {Number}
|
|
163
163
|
* @memberof Random */
|
|
164
|
-
const randSign = ()=> (
|
|
164
|
+
const randSign = ()=> randInt(2) * 2 - 1;
|
|
165
165
|
|
|
166
166
|
/** Returns a random Vector2 within a circular shape
|
|
167
167
|
* @param {Number} [radius=1]
|
|
@@ -185,10 +185,15 @@ const randVector = (length=1)=> new Vector2().setAngle(rand(2*PI), length);
|
|
|
185
185
|
const randColor = (cA = new Color, cB = new Color(0,0,0,1), linear)=>
|
|
186
186
|
linear ? cA.lerp(cB, rand()) : new Color(rand(cA.r,cB.r),rand(cA.g,cB.g),rand(cA.b,cB.b),rand(cA.a,cB.a));
|
|
187
187
|
|
|
188
|
-
/**
|
|
188
|
+
/** Seed used by the randSeeded function
|
|
189
189
|
* @memberof Random */
|
|
190
190
|
let randSeed = 1;
|
|
191
191
|
|
|
192
|
+
/** Set seed used by the randSeeded function, should not be 0
|
|
193
|
+
* @param {Number} seed
|
|
194
|
+
* @memberof Random */
|
|
195
|
+
const setRandSeed = (seed)=> randSeed = seed;
|
|
196
|
+
|
|
192
197
|
/** Returns a seeded random value between the two values passed in using randSeed
|
|
193
198
|
* @param {Number} [valueA=1]
|
|
194
199
|
* @param {Number} [valueB=0]
|
|
@@ -216,6 +221,14 @@ const randSeeded = (a=1, b=0)=>
|
|
|
216
221
|
*/
|
|
217
222
|
const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y) : new Vector2(x.x, x.y);
|
|
218
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Check if object is a valid Vector2
|
|
226
|
+
* @param {Vector2} vector
|
|
227
|
+
* @return {Boolean}
|
|
228
|
+
* @memberof Utilities
|
|
229
|
+
*/
|
|
230
|
+
const isVector2 = (v)=> !isNaN(v.x) && !isNaN(v.y);
|
|
231
|
+
|
|
219
232
|
/**
|
|
220
233
|
* 2D Vector object with vector math library
|
|
221
234
|
* <br> - Functions do not change this so they can be chained together
|
|
@@ -245,27 +258,27 @@ class Vector2
|
|
|
245
258
|
/** Returns a copy of this vector plus the vector passed in
|
|
246
259
|
* @param {Vector2} vector
|
|
247
260
|
* @return {Vector2} */
|
|
248
|
-
add(v) { ASSERT(v
|
|
261
|
+
add(v) { ASSERT(isVector2(v)); return new Vector2(this.x + v.x, this.y + v.y); }
|
|
249
262
|
|
|
250
263
|
/** Returns a copy of this vector minus the vector passed in
|
|
251
264
|
* @param {Vector2} vector
|
|
252
265
|
* @return {Vector2} */
|
|
253
|
-
subtract(v) { ASSERT(v
|
|
266
|
+
subtract(v) { ASSERT(isVector2(v)); return new Vector2(this.x - v.x, this.y - v.y); }
|
|
254
267
|
|
|
255
268
|
/** Returns a copy of this vector times the vector passed in
|
|
256
269
|
* @param {Vector2} vector
|
|
257
270
|
* @return {Vector2} */
|
|
258
|
-
multiply(v) { ASSERT(v
|
|
271
|
+
multiply(v) { ASSERT(isVector2(v)); return new Vector2(this.x * v.x, this.y * v.y); }
|
|
259
272
|
|
|
260
273
|
/** Returns a copy of this vector divided by the vector passed in
|
|
261
274
|
* @param {Vector2} vector
|
|
262
275
|
* @return {Vector2} */
|
|
263
|
-
divide(v) { ASSERT(v
|
|
276
|
+
divide(v) { ASSERT(isVector2(v)); return new Vector2(this.x / v.x, this.y / v.y); }
|
|
264
277
|
|
|
265
278
|
/** Returns a copy of this vector scaled by the vector passed in
|
|
266
279
|
* @param {Number} scale
|
|
267
280
|
* @return {Vector2} */
|
|
268
|
-
scale(s) { ASSERT(s
|
|
281
|
+
scale(s) { ASSERT(!isVector2(s)); return new Vector2(this.x * s, this.y * s); }
|
|
269
282
|
|
|
270
283
|
/** Returns the length of this vector
|
|
271
284
|
* @return {Number} */
|
|
@@ -298,12 +311,12 @@ class Vector2
|
|
|
298
311
|
/** Returns the dot product of this and the vector passed in
|
|
299
312
|
* @param {Vector2} vector
|
|
300
313
|
* @return {Number} */
|
|
301
|
-
dot(v) { ASSERT(v
|
|
314
|
+
dot(v) { ASSERT(isVector2(v)); return this.x*v.x + this.y*v.y; }
|
|
302
315
|
|
|
303
316
|
/** Returns the cross product of this and the vector passed in
|
|
304
317
|
* @param {Vector2} vector
|
|
305
318
|
* @return {Number} */
|
|
306
|
-
cross(v) { ASSERT(v
|
|
319
|
+
cross(v) { ASSERT(isVector2(v)); return this.x*v.y - this.y*v.x; }
|
|
307
320
|
|
|
308
321
|
/** Returns the angle of this vector, up is angle 0
|
|
309
322
|
* @return {Number} */
|
|
@@ -339,7 +352,7 @@ class Vector2
|
|
|
339
352
|
* @param {Vector2} vector
|
|
340
353
|
* @param {Number} percent
|
|
341
354
|
* @return {Vector2} */
|
|
342
|
-
lerp(v, p) { ASSERT(v
|
|
355
|
+
lerp(v, p) { ASSERT(isVector2(v)); return this.add(v.subtract(this).scale(clamp(p))); }
|
|
343
356
|
|
|
344
357
|
/** Returns true if this vector is within the bounds of an array size passed in
|
|
345
358
|
* @param {Vector2} arraySize
|
|
@@ -350,7 +363,7 @@ class Vector2
|
|
|
350
363
|
* @param {float} digits - precision to display
|
|
351
364
|
* @return {String} */
|
|
352
365
|
toString(digits=3)
|
|
353
|
-
{ return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }
|
|
366
|
+
{ if (debug) { return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }}
|
|
354
367
|
}
|
|
355
368
|
|
|
356
369
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -588,7 +601,7 @@ class Timer
|
|
|
588
601
|
|
|
589
602
|
/** Returns this timer expressed as a string
|
|
590
603
|
* @return {String} */
|
|
591
|
-
toString() { if (debug) { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); }
|
|
604
|
+
toString() { if (debug) { return this.unset() ? 'unset' : Math.abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ); }}
|
|
592
605
|
|
|
593
606
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
594
607
|
* @return {Number} */
|
|
@@ -601,6 +614,20 @@ class Timer
|
|
|
601
614
|
|
|
602
615
|
'use strict';
|
|
603
616
|
|
|
617
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
618
|
+
// Camera settings
|
|
619
|
+
|
|
620
|
+
/** Position of camera in world space
|
|
621
|
+
* @type {Vector2}
|
|
622
|
+
* @default
|
|
623
|
+
* @memberof Settings */
|
|
624
|
+
let cameraPos = vec2();
|
|
625
|
+
|
|
626
|
+
/** Scale of camera in world space
|
|
627
|
+
* @default
|
|
628
|
+
* @memberof Settings */
|
|
629
|
+
let cameraScale = 16;
|
|
630
|
+
|
|
604
631
|
///////////////////////////////////////////////////////////////////////////////
|
|
605
632
|
// Display settings
|
|
606
633
|
|
|
@@ -627,6 +654,19 @@ let cavasPixelated = 1;
|
|
|
627
654
|
* @memberof Settings */
|
|
628
655
|
let fontDefault = 'arial';
|
|
629
656
|
|
|
657
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
658
|
+
// WebGL settings
|
|
659
|
+
|
|
660
|
+
/** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
|
|
661
|
+
* @default
|
|
662
|
+
* @memberof Settings */
|
|
663
|
+
let glEnable = 1;
|
|
664
|
+
|
|
665
|
+
/** Fixes slow rendering in some browsers by not compositing the WebGL canvas
|
|
666
|
+
* @default
|
|
667
|
+
* @memberof Settings */
|
|
668
|
+
let glOverlay = 1;
|
|
669
|
+
|
|
630
670
|
///////////////////////////////////////////////////////////////////////////////
|
|
631
671
|
// Tile sheet settings
|
|
632
672
|
|
|
@@ -695,33 +735,6 @@ let gravity = 0;
|
|
|
695
735
|
* @memberof Settings */
|
|
696
736
|
let particleEmitRateScale = 1;
|
|
697
737
|
|
|
698
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
699
|
-
// Camera settings
|
|
700
|
-
|
|
701
|
-
/** Position of camera in world space
|
|
702
|
-
* @type {Vector2}
|
|
703
|
-
* @default
|
|
704
|
-
* @memberof Settings */
|
|
705
|
-
let cameraPos = vec2();
|
|
706
|
-
|
|
707
|
-
/** Scale of camera in world space
|
|
708
|
-
* @default
|
|
709
|
-
* @memberof Settings */
|
|
710
|
-
let cameraScale = max(tileSizeDefault.x, tileSizeDefault.y);
|
|
711
|
-
|
|
712
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
713
|
-
// WebGL settings
|
|
714
|
-
|
|
715
|
-
/** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
|
|
716
|
-
* @default
|
|
717
|
-
* @memberof Settings */
|
|
718
|
-
let glEnable = 1;
|
|
719
|
-
|
|
720
|
-
/** Fixes slow rendering in some browsers by not compositing the WebGL canvas
|
|
721
|
-
* @default
|
|
722
|
-
* @memberof Settings */
|
|
723
|
-
let glOverlay = 1;
|
|
724
|
-
|
|
725
738
|
///////////////////////////////////////////////////////////////////////////////
|
|
726
739
|
// Input settings
|
|
727
740
|
|
|
@@ -755,7 +768,7 @@ let touchGamepadAnalog = 1;
|
|
|
755
768
|
/** Size of virutal gamepad for touch devices in pixels
|
|
756
769
|
* @default
|
|
757
770
|
* @memberof Settings */
|
|
758
|
-
let touchGamepadSize =
|
|
771
|
+
let touchGamepadSize = 99;
|
|
759
772
|
|
|
760
773
|
/** Transparency of touch gamepad overlay
|
|
761
774
|
* @default
|
|
@@ -832,6 +845,7 @@ let medalDisplayIconSize = 50;
|
|
|
832
845
|
- Particle effect system
|
|
833
846
|
- Medal system tracks and displays achievements
|
|
834
847
|
- Debug tools and debug rendering system
|
|
848
|
+
- Post processing effects
|
|
835
849
|
- Call engineInit() to start it up!
|
|
836
850
|
*/
|
|
837
851
|
|
|
@@ -841,7 +855,7 @@ let medalDisplayIconSize = 50;
|
|
|
841
855
|
const engineName = 'LittleJS';
|
|
842
856
|
|
|
843
857
|
/** Version of engine */
|
|
844
|
-
const engineVersion = '1.4.
|
|
858
|
+
const engineVersion = '1.4.8';
|
|
845
859
|
|
|
846
860
|
/** Frames per second to update objects
|
|
847
861
|
* @default */
|
|
@@ -866,18 +880,13 @@ let time = 0;
|
|
|
866
880
|
/** Actual clock time since start in seconds (not affected by pause or frame rate clamping) */
|
|
867
881
|
let timeReal = 0;
|
|
868
882
|
|
|
869
|
-
/** Is the game paused? Causes time and objects to not be updated
|
|
883
|
+
/** Is the game paused? Causes time and objects to not be updated */
|
|
870
884
|
let paused = 0;
|
|
871
885
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
let averageFPS, drawCount;
|
|
877
|
-
|
|
878
|
-
// css text used for elements created by engine
|
|
879
|
-
const styleBody = 'margin:0;overflow:hidden;background:#000';
|
|
880
|
-
const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
886
|
+
/** Set if game is paused
|
|
887
|
+
* @param {Boolean} paused
|
|
888
|
+
*/
|
|
889
|
+
function setPaused(_paused) { paused = _paused; }
|
|
881
890
|
|
|
882
891
|
///////////////////////////////////////////////////////////////////////////////
|
|
883
892
|
|
|
@@ -899,10 +908,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
899
908
|
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
900
909
|
|
|
901
910
|
// setup html
|
|
911
|
+
const styleBody = 'margin:0;overflow:hidden;background:#000' + // fill the window
|
|
912
|
+
';touch-action:none' + // prevent mobile pinch to resize
|
|
913
|
+
';user-select:none' + // prevent mobile hold to select
|
|
914
|
+
';-webkit-user-select:none'; // compatibility for ios
|
|
902
915
|
document.body.style = styleBody;
|
|
903
916
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
904
917
|
mainContext = mainCanvas.getContext('2d');
|
|
905
|
-
mainCanvas.style = styleCanvas;
|
|
906
918
|
|
|
907
919
|
// init stuff and start engine
|
|
908
920
|
debugInit();
|
|
@@ -911,21 +923,27 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
911
923
|
// create overlay canvas for hud to appear above gl canvas
|
|
912
924
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
913
925
|
overlayContext = overlayCanvas.getContext('2d');
|
|
914
|
-
|
|
926
|
+
|
|
927
|
+
// set canvas style to fill the window
|
|
928
|
+
const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
929
|
+
(glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
|
|
915
930
|
|
|
916
931
|
gameInit();
|
|
917
932
|
touchGamepadCreate();
|
|
918
933
|
engineUpdate();
|
|
919
934
|
};
|
|
920
935
|
|
|
936
|
+
// frame time tracking
|
|
937
|
+
let frameTimeLastMS = 0, frameTimeBufferMS, averageFPS;
|
|
938
|
+
|
|
921
939
|
// main update loop
|
|
922
|
-
|
|
940
|
+
function engineUpdate(frameTimeMS=0)
|
|
923
941
|
{
|
|
924
942
|
// update time keeping
|
|
925
943
|
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
926
944
|
frameTimeLastMS = frameTimeMS;
|
|
927
945
|
if (debug || showWatermark)
|
|
928
|
-
averageFPS = lerp(.05, averageFPS
|
|
946
|
+
averageFPS = lerp(.05, averageFPS, 1e3/(frameTimeDeltaMS||1));
|
|
929
947
|
const debugSpeedUp = debug && keyIsDown(107); // +
|
|
930
948
|
const debugSpeedDown = debug && keyIsDown(109); // -
|
|
931
949
|
if (debug)
|
|
@@ -937,24 +955,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
937
955
|
|
|
938
956
|
if (canvasFixedSize.x)
|
|
939
957
|
{
|
|
940
|
-
// clear set fixed size
|
|
958
|
+
// clear canvas and set fixed size
|
|
941
959
|
overlayCanvas.width = mainCanvas.width = canvasFixedSize.x;
|
|
942
960
|
overlayCanvas.height = mainCanvas.height = canvasFixedSize.y;
|
|
943
961
|
|
|
944
962
|
// fit to window by adding space on top or bottom if necessary
|
|
945
963
|
const aspect = innerWidth / innerHeight;
|
|
946
964
|
const fixedAspect = mainCanvas.width / mainCanvas.height;
|
|
947
|
-
mainCanvas.style.width
|
|
948
|
-
mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
949
|
-
if (glCanvas)
|
|
950
|
-
{
|
|
951
|
-
glCanvas.style.width = mainCanvas.style.width;
|
|
952
|
-
glCanvas.style.height = mainCanvas.style.height;
|
|
953
|
-
}
|
|
965
|
+
(glCanvas||mainCanvas).style.width = mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
|
|
966
|
+
(glCanvas||mainCanvas).style.height = mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
954
967
|
}
|
|
955
968
|
else
|
|
956
969
|
{
|
|
957
|
-
|
|
970
|
+
// clear canvas and set size to same as window
|
|
958
971
|
overlayCanvas.width = mainCanvas.width = min(innerWidth, canvasMaxSize.x);
|
|
959
972
|
overlayCanvas.height = mainCanvas.height = min(innerHeight, canvasMaxSize.y);
|
|
960
973
|
}
|
|
@@ -1021,7 +1034,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
1021
1034
|
overlayContext.fillStyle = '#000';
|
|
1022
1035
|
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
1023
1036
|
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
1024
|
-
+
|
|
1037
|
+
+ (glEnable ? ' GL' : ' 2D') ;
|
|
1025
1038
|
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
1026
1039
|
overlayContext.fillStyle = '#fff';
|
|
1027
1040
|
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
@@ -1035,7 +1048,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
1035
1048
|
tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
|
|
1036
1049
|
}
|
|
1037
1050
|
|
|
1038
|
-
//
|
|
1051
|
+
// Called automatically by engine to setup render system
|
|
1039
1052
|
function enginePreRender()
|
|
1040
1053
|
{
|
|
1041
1054
|
// save canvas size
|
|
@@ -1045,12 +1058,10 @@ function enginePreRender()
|
|
|
1045
1058
|
mainContext.imageSmoothingEnabled = !cavasPixelated;
|
|
1046
1059
|
|
|
1047
1060
|
// setup gl rendering if enabled
|
|
1048
|
-
glEnable && glPreRender(
|
|
1061
|
+
glEnable && glPreRender();
|
|
1049
1062
|
}
|
|
1050
1063
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
/** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
|
|
1064
|
+
/** Update each engine object, remove destroyed objects, and update time */
|
|
1054
1065
|
function engineObjectsUpdate()
|
|
1055
1066
|
{
|
|
1056
1067
|
// get list of solid objects for physics optimzation
|
|
@@ -1153,7 +1164,7 @@ class EngineObject
|
|
|
1153
1164
|
constructor(pos=vec2(), size=objectDefaultSize, tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
|
|
1154
1165
|
{
|
|
1155
1166
|
// set passed in params
|
|
1156
|
-
ASSERT(pos &&
|
|
1167
|
+
ASSERT(isVector2(pos) && isVector2(size)); // ensure pos and size are vec2s
|
|
1157
1168
|
|
|
1158
1169
|
/** @property {Vector2} - World space position of the object */
|
|
1159
1170
|
this.pos = pos.copy();
|
|
@@ -1243,7 +1254,7 @@ class EngineObject
|
|
|
1243
1254
|
if (this.collideSolidObjects)
|
|
1244
1255
|
{
|
|
1245
1256
|
// check collisions against solid objects
|
|
1246
|
-
const epsilon =
|
|
1257
|
+
const epsilon = .001; // necessary to push slightly outside of the collision
|
|
1247
1258
|
for (const o of engineObjectsCollide)
|
|
1248
1259
|
{
|
|
1249
1260
|
// non solid objects don't collide with eachother
|
|
@@ -1506,11 +1517,6 @@ class EngineObject
|
|
|
1506
1517
|
|
|
1507
1518
|
'use strict';
|
|
1508
1519
|
|
|
1509
|
-
/** Tile sheet for batch rendering system
|
|
1510
|
-
* @type {Image}
|
|
1511
|
-
* @memberof Draw */
|
|
1512
|
-
const tileImage = new Image();
|
|
1513
|
-
|
|
1514
1520
|
/** The primary 2D canvas visible to the user
|
|
1515
1521
|
* @type {HTMLCanvasElement}
|
|
1516
1522
|
* @memberof Draw */
|
|
@@ -1536,6 +1542,14 @@ let overlayContext;
|
|
|
1536
1542
|
* @memberof Draw */
|
|
1537
1543
|
let mainCanvasSize = vec2();
|
|
1538
1544
|
|
|
1545
|
+
/** Tile sheet for batch rendering system
|
|
1546
|
+
* @type {Image}
|
|
1547
|
+
* @memberof Draw */
|
|
1548
|
+
const tileImage = new Image;
|
|
1549
|
+
|
|
1550
|
+
// Engine internal variables not exposed to documentation
|
|
1551
|
+
let tileImageSize, tileImageFixBleed, drawCount;
|
|
1552
|
+
|
|
1539
1553
|
/** Convert from screen to world space coordinates
|
|
1540
1554
|
* - if calling outside of render, you may need to manually set mainCanvasSize
|
|
1541
1555
|
* @param {Vector2} screenPos
|
|
@@ -1560,7 +1574,7 @@ const worldToScreen = (worldPos)=>
|
|
|
1560
1574
|
|
|
1561
1575
|
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
1562
1576
|
* @param {Vector2} pos - Center of the tile in world space
|
|
1563
|
-
* @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space
|
|
1577
|
+
* @param {Vector2} [size=new Vector2(1,1)] - Size of the tile in world space
|
|
1564
1578
|
* @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
|
|
1565
1579
|
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
|
|
1566
1580
|
* @param {Color} [color=new Color(1,1,1)] - Color to modulate with
|
|
@@ -1779,10 +1793,10 @@ class FontImage
|
|
|
1779
1793
|
*/
|
|
1780
1794
|
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
|
|
1781
1795
|
{
|
|
1782
|
-
if (!
|
|
1796
|
+
if (!engineFontImage)
|
|
1783
1797
|
{
|
|
1784
1798
|
// load default font image
|
|
1785
|
-
engineFontImage = new Image
|
|
1799
|
+
engineFontImage = new Image;
|
|
1786
1800
|
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
1787
1801
|
}
|
|
1788
1802
|
|
|
@@ -1849,7 +1863,7 @@ class FontImage
|
|
|
1849
1863
|
/** Returns true if fullscreen mode is active
|
|
1850
1864
|
* @return {Boolean}
|
|
1851
1865
|
* @memberof Draw */
|
|
1852
|
-
const isFullscreen =()=> document.fullscreenElement;
|
|
1866
|
+
const isFullscreen = ()=> document.fullscreenElement;
|
|
1853
1867
|
|
|
1854
1868
|
/** Toggle fullsceen mode
|
|
1855
1869
|
* @memberof Draw */
|
|
@@ -1859,16 +1873,9 @@ function toggleFullscreen()
|
|
|
1859
1873
|
{
|
|
1860
1874
|
if (document.exitFullscreen)
|
|
1861
1875
|
document.exitFullscreen();
|
|
1862
|
-
else if (document.mozCancelFullScreen)
|
|
1863
|
-
document.mozCancelFullScreen();
|
|
1864
|
-
}
|
|
1865
|
-
else
|
|
1866
|
-
{
|
|
1867
|
-
if (document.body.webkitRequestFullScreen)
|
|
1868
|
-
document.body.webkitRequestFullScreen();
|
|
1869
|
-
else if (document.body.mozRequestFullScreen)
|
|
1870
|
-
document.body.mozRequestFullScreen();
|
|
1871
1876
|
}
|
|
1877
|
+
else if (document.body.requestFullscreen)
|
|
1878
|
+
document.body.requestFullscreen();
|
|
1872
1879
|
}
|
|
1873
1880
|
|
|
1874
1881
|
/**
|
|
@@ -1887,7 +1894,7 @@ function toggleFullscreen()
|
|
|
1887
1894
|
* @param {Number} [device=0]
|
|
1888
1895
|
* @return {Boolean}
|
|
1889
1896
|
* @memberof Input */
|
|
1890
|
-
const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1
|
|
1897
|
+
const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1;
|
|
1891
1898
|
|
|
1892
1899
|
/** Returns true if device key was pressed this frame
|
|
1893
1900
|
* @param {Number} key
|
|
@@ -2126,32 +2133,41 @@ const isTouchDevice = window.ontouchstart !== undefined;
|
|
|
2126
2133
|
// try to enable touch mouse
|
|
2127
2134
|
if (isTouchDevice)
|
|
2128
2135
|
{
|
|
2129
|
-
//
|
|
2130
|
-
let wasTouching,
|
|
2131
|
-
|
|
2136
|
+
// override mouse events
|
|
2137
|
+
let wasTouching, mouseDown = onmousedown, mouseUp = onmouseup;
|
|
2138
|
+
onmousedown = onmouseup = ()=> 0;
|
|
2139
|
+
|
|
2140
|
+
// setup touch input
|
|
2141
|
+
ontouchstart = (e)=>
|
|
2132
2142
|
{
|
|
2133
|
-
|
|
2143
|
+
// fix mobile audio, force it to play a sound on first touch
|
|
2144
|
+
zzfx(0);
|
|
2134
2145
|
|
|
2135
|
-
//
|
|
2136
|
-
|
|
2137
|
-
if (touching)
|
|
2146
|
+
// handle all touch events the same way
|
|
2147
|
+
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2138
2148
|
{
|
|
2139
|
-
|
|
2140
|
-
hadTouch || zzfx(0, hadTouch=1);
|
|
2149
|
+
e.button = 0; // all touches are left click
|
|
2141
2150
|
|
|
2142
|
-
//
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2151
|
+
// check if touching and pass to mouse events
|
|
2152
|
+
const touching = e.touches.length;
|
|
2153
|
+
if (touching)
|
|
2154
|
+
{
|
|
2155
|
+
// set event pos and pass it along
|
|
2156
|
+
e.x = e.touches[0].clientX;
|
|
2157
|
+
e.y = e.touches[0].clientY;
|
|
2158
|
+
wasTouching ? onmousemove(e) : mouseDown(e);
|
|
2159
|
+
}
|
|
2160
|
+
else if (wasTouching)
|
|
2161
|
+
mouseUp(e);
|
|
2149
2162
|
|
|
2150
|
-
|
|
2151
|
-
|
|
2163
|
+
// set was touching
|
|
2164
|
+
wasTouching = touching;
|
|
2152
2165
|
|
|
2153
|
-
|
|
2154
|
-
|
|
2166
|
+
// must return true so the document will get focus
|
|
2167
|
+
return true;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
return ontouchstart(e);
|
|
2155
2171
|
}
|
|
2156
2172
|
}
|
|
2157
2173
|
|
|
@@ -2159,7 +2175,7 @@ if (isTouchDevice)
|
|
|
2159
2175
|
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
2160
2176
|
|
|
2161
2177
|
// touch input internal variables
|
|
2162
|
-
let touchGamepadTimer = new Timer, touchGamepadButtons
|
|
2178
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2163
2179
|
|
|
2164
2180
|
// create the touch gamepad, called automatically by the engine
|
|
2165
2181
|
function touchGamepadCreate()
|
|
@@ -2167,65 +2183,73 @@ function touchGamepadCreate()
|
|
|
2167
2183
|
if (!touchGamepadEnable || !isTouchDevice)
|
|
2168
2184
|
return;
|
|
2169
2185
|
|
|
2170
|
-
|
|
2186
|
+
// touch input internal variables
|
|
2187
|
+
touchGamepadButtons = [];
|
|
2188
|
+
touchGamepadStick = vec2();
|
|
2189
|
+
|
|
2190
|
+
// setup touch input
|
|
2191
|
+
ontouchstart = (e)=>
|
|
2171
2192
|
{
|
|
2172
|
-
|
|
2173
|
-
|
|
2193
|
+
// fix mobile audio, force it to play a sound on first touch
|
|
2194
|
+
zzfx(0);
|
|
2174
2195
|
|
|
2175
|
-
|
|
2176
|
-
touchGamepadStick = vec2();
|
|
2177
|
-
touchGamepadButtons = [];
|
|
2178
|
-
|
|
2179
|
-
const touching = e.touches.length;
|
|
2180
|
-
if (touching)
|
|
2196
|
+
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2181
2197
|
{
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
if (paused)
|
|
2198
|
+
// clear touch gamepad input
|
|
2199
|
+
touchGamepadStick = vec2();
|
|
2200
|
+
touchGamepadButtons = [];
|
|
2201
|
+
|
|
2202
|
+
const touching = e.touches.length;
|
|
2203
|
+
if (touching)
|
|
2189
2204
|
{
|
|
2190
|
-
//
|
|
2191
|
-
|
|
2192
|
-
|
|
2205
|
+
// set that gamepad is active
|
|
2206
|
+
isUsingGamepad = 1;
|
|
2207
|
+
touchGamepadTimer.set();
|
|
2208
|
+
|
|
2209
|
+
if (paused)
|
|
2210
|
+
{
|
|
2211
|
+
// touch anywhere to press start when paused
|
|
2212
|
+
touchGamepadButtons[9] = 1;
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2193
2215
|
}
|
|
2194
|
-
}
|
|
2195
2216
|
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2217
|
+
// get center of left and right sides
|
|
2218
|
+
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2219
|
+
const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
|
|
2220
|
+
const startCenter = mainCanvasSize.scale(.5);
|
|
2200
2221
|
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
{
|
|
2204
|
-
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
2205
|
-
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
2222
|
+
// check each touch point
|
|
2223
|
+
for (const touch of e.touches)
|
|
2206
2224
|
{
|
|
2207
|
-
|
|
2208
|
-
if (
|
|
2209
|
-
|
|
2210
|
-
|
|
2225
|
+
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
2226
|
+
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
2227
|
+
{
|
|
2228
|
+
// virtual analog stick
|
|
2229
|
+
if (touchGamepadAnalog)
|
|
2230
|
+
touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
|
|
2231
|
+
else
|
|
2232
|
+
{
|
|
2233
|
+
// 8 way dpad
|
|
2234
|
+
const angle = touchPos.subtract(stickCenter).angle();
|
|
2235
|
+
touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
2211
2239
|
{
|
|
2212
|
-
//
|
|
2213
|
-
const
|
|
2214
|
-
|
|
2240
|
+
// virtual face buttons
|
|
2241
|
+
const button = touchPos.subtract(buttonCenter).direction();
|
|
2242
|
+
touchGamepadButtons[button] = 1;
|
|
2243
|
+
}
|
|
2244
|
+
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
2245
|
+
{
|
|
2246
|
+
// virtual start button in center
|
|
2247
|
+
touchGamepadButtons[9] = 1;
|
|
2215
2248
|
}
|
|
2216
|
-
}
|
|
2217
|
-
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
2218
|
-
{
|
|
2219
|
-
// virtual face buttons
|
|
2220
|
-
const button = touchPos.subtract(buttonCenter).direction();
|
|
2221
|
-
touchGamepadButtons[button] = 1;
|
|
2222
|
-
}
|
|
2223
|
-
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
2224
|
-
{
|
|
2225
|
-
// virtual start button in center
|
|
2226
|
-
touchGamepadButtons[9] = 1;
|
|
2227
2249
|
}
|
|
2228
2250
|
}
|
|
2251
|
+
|
|
2252
|
+
return ontouchstart(e);
|
|
2229
2253
|
}
|
|
2230
2254
|
}
|
|
2231
2255
|
|
|
@@ -2343,7 +2367,7 @@ class Sound
|
|
|
2343
2367
|
{
|
|
2344
2368
|
if (!soundEnable) return;
|
|
2345
2369
|
|
|
2346
|
-
let pan
|
|
2370
|
+
let pan;
|
|
2347
2371
|
if (pos)
|
|
2348
2372
|
{
|
|
2349
2373
|
const range = this.range;
|
|
@@ -2373,7 +2397,7 @@ class Sound
|
|
|
2373
2397
|
* @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
|
|
2374
2398
|
* @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
|
|
2375
2399
|
*/
|
|
2376
|
-
playNote(semitoneOffset, pos, volume
|
|
2400
|
+
playNote(semitoneOffset, pos, volume)
|
|
2377
2401
|
{
|
|
2378
2402
|
if (!soundEnable) return;
|
|
2379
2403
|
|
|
@@ -2428,7 +2452,7 @@ class Music
|
|
|
2428
2452
|
* @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
|
|
2429
2453
|
* @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
|
|
2430
2454
|
*/
|
|
2431
|
-
play(volume
|
|
2455
|
+
play(volume, loop = 1)
|
|
2432
2456
|
{
|
|
2433
2457
|
if (!soundEnable) return;
|
|
2434
2458
|
|
|
@@ -2510,7 +2534,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2510
2534
|
|
|
2511
2535
|
// create audio context
|
|
2512
2536
|
if (!audioContext)
|
|
2513
|
-
audioContext = new
|
|
2537
|
+
audioContext = new AudioContext;
|
|
2514
2538
|
|
|
2515
2539
|
// fix stalled audio
|
|
2516
2540
|
audioContext.resume();
|
|
@@ -2529,18 +2553,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2529
2553
|
source.playbackRate.value = rate;
|
|
2530
2554
|
source.loop = loop;
|
|
2531
2555
|
|
|
2532
|
-
// create and connect gain node (createGain is more
|
|
2556
|
+
// create and connect gain node (createGain is more widely spported then GainNode construtor)
|
|
2533
2557
|
const gainNode = audioContext.createGain();
|
|
2534
2558
|
gainNode.gain.value = soundVolume*volume;
|
|
2535
2559
|
gainNode.connect(audioContext.destination);
|
|
2536
2560
|
|
|
2537
|
-
// connect source to gain
|
|
2538
|
-
(
|
|
2539
|
-
window.StereoPannerNode ? // create pan node if possible
|
|
2540
|
-
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
|
|
2541
|
-
: source
|
|
2542
|
-
)
|
|
2543
|
-
.connect(gainNode);
|
|
2561
|
+
// connect source to stereo panner and gain
|
|
2562
|
+
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)})).connect(gainNode);
|
|
2544
2563
|
|
|
2545
2564
|
// play and return sound
|
|
2546
2565
|
source.start();
|
|
@@ -2658,10 +2677,8 @@ function zzfxG
|
|
|
2658
2677
|
* @memberof Audio */
|
|
2659
2678
|
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
2660
2679
|
{
|
|
2680
|
+
let i, j, k;
|
|
2661
2681
|
let instrumentParameters;
|
|
2662
|
-
let i;
|
|
2663
|
-
let j;
|
|
2664
|
-
let k;
|
|
2665
2682
|
let note;
|
|
2666
2683
|
let sample;
|
|
2667
2684
|
let patternChannel;
|
|
@@ -2831,14 +2848,11 @@ function tileCollisionRaycast(posStart, posEnd, object)
|
|
|
2831
2848
|
{
|
|
2832
2849
|
// test if a ray collides with tiles from start to end
|
|
2833
2850
|
// todo: a way to get the exact hit point, it must still register as inside the hit tile
|
|
2834
|
-
posStart = posStart.floor();
|
|
2835
|
-
posEnd = posEnd.floor();
|
|
2836
|
-
const posDelta = posEnd.subtract(posStart);
|
|
2851
|
+
const posDelta = (posEnd = posEnd.floor()).subtract(posStart = posStart.floor());
|
|
2837
2852
|
const dx = abs(posDelta.x), dy = -abs(posDelta.y);
|
|
2838
2853
|
const sx = sign(posDelta.x), sy = sign(posDelta.y);
|
|
2839
|
-
let e = dx + dy;
|
|
2840
2854
|
|
|
2841
|
-
for (let x = posStart.x, y = posStart.y;;)
|
|
2855
|
+
for (let x = posStart.x, y = posStart.y, e = dx + dy;;)
|
|
2842
2856
|
{
|
|
2843
2857
|
const tileData = getTileCollisionData(vec2(x,y));
|
|
2844
2858
|
if (tileData && (object ? object.collideWithTileRaycast(tileData, new Vector2(x, y)) : tileData > 0))
|
|
@@ -2930,7 +2944,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
|
|
|
2930
2944
|
// init tile data
|
|
2931
2945
|
this.data = [];
|
|
2932
2946
|
for (let j = this.size.area(); j--;)
|
|
2933
|
-
this.data.push(new TileLayerData
|
|
2947
|
+
this.data.push(new TileLayerData);
|
|
2934
2948
|
}
|
|
2935
2949
|
|
|
2936
2950
|
/** Set data at a given position in the array
|
|
@@ -2986,21 +3000,23 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
|
|
|
2986
3000
|
* @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
|
|
2987
3001
|
redrawStart(clear = 0)
|
|
2988
3002
|
{
|
|
2989
|
-
if (clear)
|
|
2990
|
-
{
|
|
2991
|
-
// clear and set size
|
|
2992
|
-
this.canvas.width = this.size.x * this.tileSize.x;
|
|
2993
|
-
this.canvas.height = this.size.y * this.tileSize.y;
|
|
2994
|
-
}
|
|
2995
|
-
|
|
2996
3003
|
// save current render settings
|
|
2997
|
-
this.savedRenderSettings = [mainCanvas, mainContext, cameraPos, cameraScale];
|
|
3004
|
+
this.savedRenderSettings = [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale];
|
|
2998
3005
|
|
|
2999
|
-
// use normal rendering system to render the tiles
|
|
3006
|
+
// hack: use normal rendering system to render the tiles
|
|
3000
3007
|
mainCanvas = this.canvas;
|
|
3001
3008
|
mainContext = this.context;
|
|
3002
3009
|
cameraPos = this.size.scale(.5);
|
|
3003
3010
|
cameraScale = this.tileSize.x;
|
|
3011
|
+
|
|
3012
|
+
if (clear)
|
|
3013
|
+
{
|
|
3014
|
+
// clear and set size
|
|
3015
|
+
mainCanvas.width = this.size.x * this.tileSize.x;
|
|
3016
|
+
mainCanvas.height = this.size.y * this.tileSize.y;
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
// begin a new render for the tile canvas
|
|
3004
3020
|
enginePreRender();
|
|
3005
3021
|
}
|
|
3006
3022
|
|
|
@@ -3012,7 +3028,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
|
|
|
3012
3028
|
//debugSaveCanvas(this.canvas);
|
|
3013
3029
|
|
|
3014
3030
|
// set stuff back to normal
|
|
3015
|
-
[mainCanvas, mainContext, cameraPos, cameraScale] = this.savedRenderSettings;
|
|
3031
|
+
[mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale] = this.savedRenderSettings;
|
|
3016
3032
|
}
|
|
3017
3033
|
|
|
3018
3034
|
/** Draw the tile at a given position
|
|
@@ -3253,7 +3269,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3253
3269
|
this.parent && super.update();
|
|
3254
3270
|
|
|
3255
3271
|
// update emitter
|
|
3256
|
-
if (!this.emitTime
|
|
3272
|
+
if (!this.emitTime | this.getAliveTime() <= this.emitTime)
|
|
3257
3273
|
{
|
|
3258
3274
|
// emit particles
|
|
3259
3275
|
if (this.emitRate * particleEmitRateScale)
|
|
@@ -3275,9 +3291,9 @@ class ParticleEmitter extends EngineObject
|
|
|
3275
3291
|
{
|
|
3276
3292
|
// spawn a particle
|
|
3277
3293
|
let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
|
|
3278
|
-
(new Vector2(rand(
|
|
3279
|
-
.multiply(this.emitSize).rotate(this.angle) // box emitter
|
|
3280
|
-
: randInCircle(this.emitSize
|
|
3294
|
+
(new Vector2(rand(-1,1), rand(-1,1)))
|
|
3295
|
+
.multiply(this.emitSize/2).rotate(this.angle) // box emitter
|
|
3296
|
+
: randInCircle(this.emitSize/2); // circle emitter
|
|
3281
3297
|
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
3282
3298
|
if (!this.localSpace)
|
|
3283
3299
|
{
|
|
@@ -3320,7 +3336,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3320
3336
|
particle.additive = this.additive;
|
|
3321
3337
|
particle.renderOrder = this.renderOrder;
|
|
3322
3338
|
particle.trailScale = this.trailScale;
|
|
3323
|
-
particle.mirror =
|
|
3339
|
+
particle.mirror = randInt(2);
|
|
3324
3340
|
particle.localSpaceEmitter = this.localSpace && this;
|
|
3325
3341
|
|
|
3326
3342
|
// setup callbacks for particles
|
|
@@ -3349,7 +3365,8 @@ class Particle extends EngineObject
|
|
|
3349
3365
|
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
3350
3366
|
* @param {Number} [angle=0] - Angle to rotate the particle
|
|
3351
3367
|
*/
|
|
3352
|
-
constructor(pos, tileIndex, tileSize, angle)
|
|
3368
|
+
constructor(pos, tileIndex, tileSize, angle)
|
|
3369
|
+
{ super(pos, new Vector2, tileIndex, tileSize, angle); }
|
|
3353
3370
|
|
|
3354
3371
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
3355
3372
|
render()
|
|
@@ -3469,7 +3486,7 @@ class Medal
|
|
|
3469
3486
|
this.name = name;
|
|
3470
3487
|
this.description = description;
|
|
3471
3488
|
this.icon = icon;
|
|
3472
|
-
this.image = new Image
|
|
3489
|
+
this.image = new Image;
|
|
3473
3490
|
if (src)
|
|
3474
3491
|
this.image.src = src;
|
|
3475
3492
|
}
|
|
@@ -3484,10 +3501,7 @@ class Medal
|
|
|
3484
3501
|
ASSERT(medalsSaveName); // save name must be set
|
|
3485
3502
|
localStorage[this.storageKey()] = this.unlocked = 1;
|
|
3486
3503
|
medalsDisplayQueue.push(this);
|
|
3487
|
-
|
|
3488
|
-
// save for newgrounds and OS13K
|
|
3489
3504
|
newgrounds && newgrounds.unlockMedal(this.id);
|
|
3490
|
-
localStorage['OS13kTrophy,' + this.icon + ',' + medalsSaveName + ',' + this.name] = this.description;
|
|
3491
3505
|
}
|
|
3492
3506
|
|
|
3493
3507
|
/** Render a medal
|
|
@@ -3503,20 +3517,19 @@ class Medal
|
|
|
3503
3517
|
// draw containing rect and clip to that region
|
|
3504
3518
|
context.save();
|
|
3505
3519
|
context.beginPath();
|
|
3506
|
-
context.fillStyle =
|
|
3507
|
-
context.
|
|
3508
|
-
context.strokeStyle = '#000';
|
|
3520
|
+
context.fillStyle = new Color(.9,.9,.9);
|
|
3521
|
+
context.strokeStyle = new Color(0,0,0);
|
|
3509
3522
|
context.lineWidth = 3;
|
|
3523
|
+
context.fill(context.rect(x, y, width, medalDisplayHeight));
|
|
3510
3524
|
context.stroke();
|
|
3511
3525
|
context.clip();
|
|
3512
3526
|
|
|
3513
3527
|
// draw the icon and text
|
|
3514
|
-
this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
|
|
3528
|
+
this.renderIcon(vec2(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2));
|
|
3529
|
+
const pos = vec2(x+medalDisplayIconSize+30, y+28);
|
|
3530
|
+
drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, 0, 'left');
|
|
3531
|
+
pos.y += 32;
|
|
3532
|
+
drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, 0, 'left');
|
|
3520
3533
|
context.restore();
|
|
3521
3534
|
}
|
|
3522
3535
|
|
|
@@ -3525,18 +3538,13 @@ class Medal
|
|
|
3525
3538
|
* @param {Number} y - Screen space Y position
|
|
3526
3539
|
* @param {Number} [size=medalDisplayIconSize] - Screen space size
|
|
3527
3540
|
*/
|
|
3528
|
-
renderIcon(
|
|
3541
|
+
renderIcon(pos, size=medalDisplayIconSize)
|
|
3529
3542
|
{
|
|
3530
3543
|
// draw the image or icon
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
context.textAlign = 'center';
|
|
3534
|
-
context.textBaseline = 'middle';
|
|
3535
|
-
context.font = size*.7 + 'px '+ fontDefault;
|
|
3536
|
-
if (this.image.src)
|
|
3537
|
-
context.drawImage(this.image, x-size/2, y-size/2, size, size);
|
|
3544
|
+
if (this.image)
|
|
3545
|
+
overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
|
|
3538
3546
|
else
|
|
3539
|
-
|
|
3547
|
+
drawTextScreen(this.icon, pos, size*.7, new Color(0,0,0));
|
|
3540
3548
|
}
|
|
3541
3549
|
|
|
3542
3550
|
// Get local storage key used by the medal
|
|
@@ -3594,13 +3602,21 @@ class Newgrounds
|
|
|
3594
3602
|
constructor(app_id, cipher)
|
|
3595
3603
|
{
|
|
3596
3604
|
ASSERT(!newgrounds && app_id);
|
|
3605
|
+
|
|
3606
|
+
// create an instance of CryptoJS for encrypted calls
|
|
3607
|
+
if (cipher)
|
|
3608
|
+
{
|
|
3609
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3610
|
+
// Crypto-JS - https://github.com/brix/crypto-js [The MIT License (MIT)]
|
|
3611
|
+
// Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
|
|
3612
|
+
|
|
3613
|
+
this.cryptoJS = eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",...']charCodeAtUinyxpf',"for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3597
3616
|
this.app_id = app_id;
|
|
3598
3617
|
this.cipher = cipher;
|
|
3599
3618
|
this.host = location ? location.hostname : '';
|
|
3600
3619
|
|
|
3601
|
-
// create an instance of CryptoJS for encrypted calls
|
|
3602
|
-
cipher && (this.cryptoJS = CryptoJS());
|
|
3603
|
-
|
|
3604
3620
|
// get session id from url search params
|
|
3605
3621
|
const url = new URL(location.href);
|
|
3606
3622
|
this.session_id = url.searchParams.get('ngio_session_id') || 0;
|
|
@@ -3703,12 +3719,6 @@ class Newgrounds
|
|
|
3703
3719
|
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
3704
3720
|
}
|
|
3705
3721
|
}
|
|
3706
|
-
|
|
3707
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
3708
|
-
// Crypto-JS - https://github.com/brix/crypto-js [The MIT License (MIT)]
|
|
3709
|
-
// Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
|
|
3710
|
-
|
|
3711
|
-
const CryptoJS=()=>eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",...']charCodeAtUinyxpf',"for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
|
|
3712
3722
|
/**
|
|
3713
3723
|
* LittleJS WebGL Interface
|
|
3714
3724
|
* <br> - All webgl used by the engine is wrapped up here
|
|
@@ -3738,7 +3748,7 @@ let glContext;
|
|
|
3738
3748
|
let glTileTexture;
|
|
3739
3749
|
|
|
3740
3750
|
// WebGL internal variables not exposed to documentation
|
|
3741
|
-
let glActiveTexture, glShader, glArrayBuffer,
|
|
3751
|
+
let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
|
|
3742
3752
|
|
|
3743
3753
|
///////////////////////////////////////////////////////////////////////////////
|
|
3744
3754
|
|
|
@@ -3748,7 +3758,6 @@ function glInit()
|
|
|
3748
3758
|
// create the canvas and tile texture
|
|
3749
3759
|
glCanvas = document.createElement('canvas');
|
|
3750
3760
|
glContext = glCanvas.getContext('webgl', {antialias: false});
|
|
3751
|
-
glCanvas.style = styleCanvas;
|
|
3752
3761
|
glTileTexture = glCreateTexture(tileImage);
|
|
3753
3762
|
|
|
3754
3763
|
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
@@ -3760,27 +3769,25 @@ function glInit()
|
|
|
3760
3769
|
'uniform mat4 m;'+ // transform matrix
|
|
3761
3770
|
'attribute vec2 p,t;'+ // position, uv
|
|
3762
3771
|
'attribute vec4 c,a;'+ // color, additiveColor
|
|
3763
|
-
'varying
|
|
3764
|
-
'varying vec4 d,e;'+ // return color, additiveColor
|
|
3772
|
+
'varying vec4 v,d,e;'+ // return uv, color, additiveColor
|
|
3765
3773
|
'void main(){'+ // shader entry point
|
|
3766
3774
|
'gl_Position=m*vec4(p,1,1);'+ // transform position
|
|
3767
|
-
'v=t;d=c;e=a;'+
|
|
3775
|
+
'v=vec4(t,p);d=c;e=a;'+ // pass stuff to fragment shader
|
|
3768
3776
|
'}' // end of shader
|
|
3769
3777
|
,
|
|
3770
|
-
'precision highp float;'+
|
|
3771
|
-
'varying
|
|
3772
|
-
'
|
|
3773
|
-
'
|
|
3774
|
-
'
|
|
3775
|
-
'
|
|
3776
|
-
'}' // end of shader
|
|
3778
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
3779
|
+
'varying vec4 v,d,e;'+ // uv, color, additiveColor
|
|
3780
|
+
'uniform sampler2D s;'+ // texture
|
|
3781
|
+
'void main(){'+ // shader entry point
|
|
3782
|
+
'gl_FragColor=texture2D(s,v.xy)*d+e;'+ // modulate texture by color plus additive
|
|
3783
|
+
'}' // end of shader
|
|
3777
3784
|
);
|
|
3778
3785
|
|
|
3779
3786
|
// init buffers
|
|
3780
|
-
|
|
3787
|
+
const vertexData = new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);
|
|
3781
3788
|
glArrayBuffer = glContext.createBuffer();
|
|
3782
|
-
glPositionData = new Float32Array(
|
|
3783
|
-
glColorData = new Uint32Array(
|
|
3789
|
+
glPositionData = new Float32Array(vertexData);
|
|
3790
|
+
glColorData = new Uint32Array(vertexData);
|
|
3784
3791
|
glBatchCount = 0;
|
|
3785
3792
|
}
|
|
3786
3793
|
|
|
@@ -3855,18 +3862,19 @@ function glCreateTexture(image)
|
|
|
3855
3862
|
image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
3856
3863
|
|
|
3857
3864
|
// use point filtering for pixelated rendering
|
|
3858
|
-
|
|
3859
|
-
glContext.texParameteri(gl_TEXTURE_2D,
|
|
3865
|
+
const filter = cavasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
3866
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
3867
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
3860
3868
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
|
|
3861
3869
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
|
|
3862
3870
|
return texture;
|
|
3863
3871
|
}
|
|
3864
3872
|
|
|
3865
3873
|
// called automatically by engine before render
|
|
3866
|
-
function glPreRender(
|
|
3874
|
+
function glPreRender()
|
|
3867
3875
|
{
|
|
3868
3876
|
// clear and set to same size as main canvas
|
|
3869
|
-
glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
|
|
3877
|
+
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
3870
3878
|
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3871
3879
|
|
|
3872
3880
|
// set up the shader
|
|
@@ -3874,7 +3882,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
|
|
|
3874
3882
|
glContext.activeTexture(gl_TEXTURE0);
|
|
3875
3883
|
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
|
|
3876
3884
|
glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
|
|
3877
|
-
glContext.bufferData(gl_ARRAY_BUFFER,
|
|
3885
|
+
glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
|
|
3878
3886
|
glSetBlendMode();
|
|
3879
3887
|
|
|
3880
3888
|
// set vertex attributes
|
|
@@ -3892,14 +3900,14 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
|
|
|
3892
3900
|
initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
3893
3901
|
|
|
3894
3902
|
// build the transform matrix
|
|
3895
|
-
const sx = 2 * cameraScale / width;
|
|
3896
|
-
const sy = 2 * cameraScale / height;
|
|
3903
|
+
const sx = 2 * cameraScale / mainCanvas.width;
|
|
3904
|
+
const sy = 2 * cameraScale / mainCanvas.height;
|
|
3897
3905
|
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
|
|
3898
3906
|
new Float32Array([
|
|
3899
3907
|
sx, 0, 0, 0,
|
|
3900
3908
|
0, sy, 0, 0,
|
|
3901
3909
|
1, 1, -1, 1,
|
|
3902
|
-
-1-sx*
|
|
3910
|
+
-1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
|
|
3903
3911
|
])
|
|
3904
3912
|
);
|
|
3905
3913
|
}
|
|
@@ -3947,10 +3955,10 @@ function glCopyToContext(context, forceDraw)
|
|
|
3947
3955
|
* @param uv0Y
|
|
3948
3956
|
* @param uv1X
|
|
3949
3957
|
* @param uv1Y
|
|
3950
|
-
* @param
|
|
3958
|
+
* @param rgba
|
|
3951
3959
|
* @param [rgbaAdditive=0]
|
|
3952
3960
|
* @memberof WebGL */
|
|
3953
|
-
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba
|
|
3961
|
+
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
|
|
3954
3962
|
{
|
|
3955
3963
|
// flush if there is no room for more verts or if different blend mode
|
|
3956
3964
|
if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
|
|
@@ -3961,43 +3969,16 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
|
|
|
3961
3969
|
const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
|
|
3962
3970
|
|
|
3963
3971
|
// setup 2 triangles to form a quad
|
|
3964
|
-
let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
glPositionData[offset++] = y + cy - sx;
|
|
3975
|
-
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3976
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3977
|
-
|
|
3978
|
-
// vertex 2
|
|
3979
|
-
glPositionData[offset++] = x - cx + sy;
|
|
3980
|
-
glPositionData[offset++] = y + cy + sx;
|
|
3981
|
-
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
|
|
3982
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3983
|
-
|
|
3984
|
-
// vertex 0
|
|
3985
|
-
glPositionData[offset++] = x - cx - sy;
|
|
3986
|
-
glPositionData[offset++] = y - cy + sx;
|
|
3987
|
-
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3988
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3989
|
-
|
|
3990
|
-
// vertex 3
|
|
3991
|
-
glPositionData[offset++] = x + cx - sy;
|
|
3992
|
-
glPositionData[offset++] = y - cy - sx;
|
|
3993
|
-
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
|
|
3994
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3995
|
-
|
|
3996
|
-
// vertex 1
|
|
3997
|
-
glPositionData[offset++] = x + cx + sy;
|
|
3998
|
-
glPositionData[offset++] = y + cy - sx;
|
|
3999
|
-
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
4000
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3972
|
+
for(let i=6, offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT; i--;)
|
|
3973
|
+
{
|
|
3974
|
+
const a = i-4&&i>1, b = i-5&&i-2&&i-1;
|
|
3975
|
+
glPositionData[offset++] = x + (a?-cx:cx) + (b?sy:-sy);
|
|
3976
|
+
glPositionData[offset++] = y + (b?cy:-cy) + (a?sx:-sx);
|
|
3977
|
+
glPositionData[offset++] = a ? uv0X : uv1X;
|
|
3978
|
+
glPositionData[offset++] = b ? uv0Y : uv1Y;
|
|
3979
|
+
glColorData[offset++] = rgba;
|
|
3980
|
+
glColorData[offset++] = rgbaAdditive;
|
|
3981
|
+
}
|
|
4001
3982
|
}
|
|
4002
3983
|
|
|
4003
3984
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -4049,14 +4030,13 @@ function glRenderPostProcess()
|
|
|
4049
4030
|
return;
|
|
4050
4031
|
|
|
4051
4032
|
// prepare to render post process shader
|
|
4052
|
-
const width = mainCanvas.width, height = mainCanvas.height;
|
|
4053
4033
|
if (glEnable)
|
|
4054
4034
|
{
|
|
4055
4035
|
glFlush(); // clear out the buffer
|
|
4056
4036
|
mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
|
|
4057
4037
|
}
|
|
4058
|
-
else
|
|
4059
|
-
glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
|
|
4038
|
+
else // set viewport
|
|
4039
|
+
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
4060
4040
|
|
|
4061
4041
|
// setup shader program to draw one triangle
|
|
4062
4042
|
glContext.useProgram(glPostShader);
|
|
@@ -4080,7 +4060,7 @@ function glRenderPostProcess()
|
|
|
4080
4060
|
const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
|
|
4081
4061
|
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
4082
4062
|
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
4083
|
-
glContext.uniform3f(uniformLocation('iResolution'), width, height, 1);
|
|
4063
|
+
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
4084
4064
|
glContext.drawArrays(gl_TRIANGLES, 0, 3);
|
|
4085
4065
|
}
|
|
4086
4066
|
|
|
@@ -4120,4 +4100,5 @@ gl_UNPACK_FLIP_Y_WEBGL = 37440,
|
|
|
4120
4100
|
gl_VERTICES_PER_QUAD = 6,
|
|
4121
4101
|
gl_INDICIES_PER_VERT = 6,
|
|
4122
4102
|
gl_MAX_BATCH = 1<<16,
|
|
4123
|
-
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2
|
|
4103
|
+
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
|
|
4104
|
+
gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE;
|