littlejsengine 1.4.7 → 1.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.bat +7 -3
- package/engine/engine.all.js +291 -323
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.module.js +307 -352
- package/engine/engine.all.module.min.js +1 -0
- package/engine/engine.all.release.js +291 -323
- package/engine/engine.js +21 -30
- package/engine/engineAudio.js +8 -15
- package/engine/engineBuild.bat +10 -3
- package/engine/engineDraw.js +18 -24
- package/engine/engineExport.js +16 -29
- package/engine/engineInput.js +84 -71
- package/engine/engineMedals.js +30 -38
- package/engine/engineObject.js +8 -8
- package/engine/engineParticles.js +5 -4
- package/engine/engineSettings.js +38 -38
- package/engine/engineTileLayer.js +15 -16
- package/engine/engineUtilities.js +26 -13
- package/engine/engineWebGL.js +38 -66
- package/engine/index.d.ts +65 -54
- package/examples/breakout/game.js +10 -10
- package/examples/breakout/gameObjects.js +7 -7
- package/examples/breakout/index.html +3 -3
- package/examples/empty/tiles.png +0 -0
- package/examples/module/game.js +5 -7
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +4 -14
- 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 -9
- package/index.html +13 -13
- package/package.json +1 -1
- package/README.md +0 -75
- package/engine/engine.all.release.module.js +0 -4464
|
@@ -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 = 32;
|
|
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
|
|
|
@@ -770,20 +783,20 @@ let vibrateEnable = 1;
|
|
|
770
783
|
///////////////////////////////////////////////////////////////////////////////
|
|
771
784
|
// Audio settings
|
|
772
785
|
|
|
773
|
-
/**
|
|
786
|
+
/** All audio code can be disabled and removed from build
|
|
774
787
|
* @default
|
|
775
788
|
* @memberof Settings */
|
|
776
|
-
let
|
|
789
|
+
let soundEnable = 1;
|
|
777
790
|
|
|
778
|
-
/**
|
|
791
|
+
/** Volume scale to apply to all sound, music and speech
|
|
779
792
|
* @default
|
|
780
793
|
* @memberof Settings */
|
|
781
|
-
let
|
|
794
|
+
let soundVolume = .5;
|
|
782
795
|
|
|
783
796
|
/** Default range where sound no longer plays
|
|
784
797
|
* @default
|
|
785
798
|
* @memberof Settings */
|
|
786
|
-
let soundDefaultRange =
|
|
799
|
+
let soundDefaultRange = 40;
|
|
787
800
|
|
|
788
801
|
/** Default range percent to start tapering off sound (0-1)
|
|
789
802
|
* @default
|
|
@@ -803,20 +816,20 @@ let medalDisplayTime = 5;
|
|
|
803
816
|
* @memberof Settings */
|
|
804
817
|
let medalDisplaySlideTime = .5;
|
|
805
818
|
|
|
806
|
-
/**
|
|
819
|
+
/** Size of medal display
|
|
807
820
|
* @default
|
|
808
821
|
* @memberof Settings */
|
|
809
|
-
let
|
|
822
|
+
let medalDisplaySize = vec2(640, 80);
|
|
810
823
|
|
|
811
|
-
/**
|
|
824
|
+
/** Size of icon in medal display
|
|
812
825
|
* @default
|
|
813
826
|
* @memberof Settings */
|
|
814
|
-
let
|
|
827
|
+
let medalDisplayIconSize = 50;
|
|
815
828
|
|
|
816
|
-
/**
|
|
829
|
+
/** Set to stop medals from being unlockable (like if cheats are enabled)
|
|
817
830
|
* @default
|
|
818
831
|
* @memberof Settings */
|
|
819
|
-
let
|
|
832
|
+
let medalsPreventUnlock;
|
|
820
833
|
/*
|
|
821
834
|
LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
822
835
|
MIT License - Copyright 2021 Frank Force
|
|
@@ -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.9';
|
|
845
859
|
|
|
846
860
|
/** Frames per second to update objects
|
|
847
861
|
* @default */
|
|
@@ -866,14 +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;
|
|
886
|
+
/** Set if game is paused
|
|
887
|
+
* @param {Boolean} paused
|
|
888
|
+
*/
|
|
889
|
+
function setPaused(_paused) { paused = _paused; }
|
|
877
890
|
|
|
878
891
|
///////////////////////////////////////////////////////////////////////////////
|
|
879
892
|
|
|
@@ -894,13 +907,11 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
894
907
|
tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
895
908
|
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
896
909
|
|
|
897
|
-
// setup
|
|
910
|
+
// setup html
|
|
898
911
|
const styleBody = 'margin:0;overflow:hidden;background:#000' + // fill the window
|
|
899
912
|
';touch-action:none' + // prevent mobile pinch to resize
|
|
900
913
|
';user-select:none' + // prevent mobile hold to select
|
|
901
|
-
';-webkit-user-select:none
|
|
902
|
-
|
|
903
|
-
// setup html
|
|
914
|
+
';-webkit-user-select:none'; // compatibility for ios
|
|
904
915
|
document.body.style = styleBody;
|
|
905
916
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
906
917
|
mainContext = mainCanvas.getContext('2d');
|
|
@@ -915,7 +926,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
915
926
|
|
|
916
927
|
// set canvas style to fill the window
|
|
917
928
|
const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
918
|
-
(glCanvas||mainCanvas).style =
|
|
929
|
+
(glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
|
|
919
930
|
|
|
920
931
|
gameInit();
|
|
921
932
|
touchGamepadCreate();
|
|
@@ -923,16 +934,16 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
923
934
|
};
|
|
924
935
|
|
|
925
936
|
// frame time tracking
|
|
926
|
-
let frameTimeLastMS = 0, frameTimeBufferMS
|
|
937
|
+
let frameTimeLastMS = 0, frameTimeBufferMS, averageFPS;
|
|
927
938
|
|
|
928
939
|
// main update loop
|
|
929
|
-
|
|
940
|
+
function engineUpdate(frameTimeMS=0)
|
|
930
941
|
{
|
|
931
942
|
// update time keeping
|
|
932
943
|
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
933
944
|
frameTimeLastMS = frameTimeMS;
|
|
934
945
|
if (debug || showWatermark)
|
|
935
|
-
averageFPS = lerp(.05, averageFPS
|
|
946
|
+
averageFPS = lerp(.05, averageFPS, 1e3/(frameTimeDeltaMS||1));
|
|
936
947
|
const debugSpeedUp = debug && keyIsDown(107); // +
|
|
937
948
|
const debugSpeedDown = debug && keyIsDown(109); // -
|
|
938
949
|
if (debug)
|
|
@@ -944,24 +955,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
944
955
|
|
|
945
956
|
if (canvasFixedSize.x)
|
|
946
957
|
{
|
|
947
|
-
// clear set fixed size
|
|
958
|
+
// clear canvas and set fixed size
|
|
948
959
|
overlayCanvas.width = mainCanvas.width = canvasFixedSize.x;
|
|
949
960
|
overlayCanvas.height = mainCanvas.height = canvasFixedSize.y;
|
|
950
961
|
|
|
951
962
|
// fit to window by adding space on top or bottom if necessary
|
|
952
963
|
const aspect = innerWidth / innerHeight;
|
|
953
964
|
const fixedAspect = mainCanvas.width / mainCanvas.height;
|
|
954
|
-
mainCanvas.style.width
|
|
955
|
-
mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
|
|
956
|
-
if (glCanvas)
|
|
957
|
-
{
|
|
958
|
-
glCanvas.style.width = mainCanvas.style.width;
|
|
959
|
-
glCanvas.style.height = mainCanvas.style.height;
|
|
960
|
-
}
|
|
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%';
|
|
961
967
|
}
|
|
962
968
|
else
|
|
963
969
|
{
|
|
964
|
-
|
|
970
|
+
// clear canvas and set size to same as window
|
|
965
971
|
overlayCanvas.width = mainCanvas.width = min(innerWidth, canvasMaxSize.x);
|
|
966
972
|
overlayCanvas.height = mainCanvas.height = min(innerHeight, canvasMaxSize.y);
|
|
967
973
|
}
|
|
@@ -1028,7 +1034,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
1028
1034
|
overlayContext.fillStyle = '#000';
|
|
1029
1035
|
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
1030
1036
|
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
1031
|
-
+
|
|
1037
|
+
+ (glEnable ? ' GL' : ' 2D') ;
|
|
1032
1038
|
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
1033
1039
|
overlayContext.fillStyle = '#fff';
|
|
1034
1040
|
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
@@ -1042,7 +1048,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
1042
1048
|
tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
|
|
1043
1049
|
}
|
|
1044
1050
|
|
|
1045
|
-
//
|
|
1051
|
+
// Called automatically by engine to setup render system
|
|
1046
1052
|
function enginePreRender()
|
|
1047
1053
|
{
|
|
1048
1054
|
// save canvas size
|
|
@@ -1052,12 +1058,10 @@ function enginePreRender()
|
|
|
1052
1058
|
mainContext.imageSmoothingEnabled = !cavasPixelated;
|
|
1053
1059
|
|
|
1054
1060
|
// setup gl rendering if enabled
|
|
1055
|
-
glEnable && glPreRender(
|
|
1061
|
+
glEnable && glPreRender();
|
|
1056
1062
|
}
|
|
1057
1063
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
/** 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 */
|
|
1061
1065
|
function engineObjectsUpdate()
|
|
1062
1066
|
{
|
|
1063
1067
|
// get list of solid objects for physics optimzation
|
|
@@ -1160,7 +1164,7 @@ class EngineObject
|
|
|
1160
1164
|
constructor(pos=vec2(), size=objectDefaultSize, tileIndex=-1, tileSize=tileSizeDefault, angle=0, color, renderOrder=0)
|
|
1161
1165
|
{
|
|
1162
1166
|
// set passed in params
|
|
1163
|
-
ASSERT(pos &&
|
|
1167
|
+
ASSERT(isVector2(pos) && isVector2(size)); // ensure pos and size are vec2s
|
|
1164
1168
|
|
|
1165
1169
|
/** @property {Vector2} - World space position of the object */
|
|
1166
1170
|
this.pos = pos.copy();
|
|
@@ -1226,9 +1230,9 @@ class EngineObject
|
|
|
1226
1230
|
|
|
1227
1231
|
// apply physics
|
|
1228
1232
|
const oldPos = this.pos.copy();
|
|
1229
|
-
this.
|
|
1230
|
-
this.pos.
|
|
1231
|
-
this.
|
|
1233
|
+
this.velocity.y += gravity * this.gravityScale;
|
|
1234
|
+
this.pos.x += this.velocity.x *= this.damping;
|
|
1235
|
+
this.pos.y += this.velocity.y *= this.damping;
|
|
1232
1236
|
|
|
1233
1237
|
// physics sanity checks
|
|
1234
1238
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
@@ -1250,7 +1254,7 @@ class EngineObject
|
|
|
1250
1254
|
if (this.collideSolidObjects)
|
|
1251
1255
|
{
|
|
1252
1256
|
// check collisions against solid objects
|
|
1253
|
-
const epsilon =
|
|
1257
|
+
const epsilon = .001; // necessary to push slightly outside of the collision
|
|
1254
1258
|
for (const o of engineObjectsCollide)
|
|
1255
1259
|
{
|
|
1256
1260
|
// non solid objects don't collide with eachother
|
|
@@ -1286,7 +1290,7 @@ class EngineObject
|
|
|
1286
1290
|
const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
|
|
1287
1291
|
const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
|
|
1288
1292
|
|
|
1289
|
-
if (smallStepUp
|
|
1293
|
+
if (smallStepUp | isBlockedY | !isBlockedX) // resolve y collision
|
|
1290
1294
|
{
|
|
1291
1295
|
// push outside object collision
|
|
1292
1296
|
this.pos.y = o.pos.y + (sizeBoth.y/2 + epsilon) * sign(oldPos.y - o.pos.y);
|
|
@@ -1316,7 +1320,7 @@ class EngineObject
|
|
|
1316
1320
|
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
1317
1321
|
}
|
|
1318
1322
|
}
|
|
1319
|
-
if (!smallStepUp
|
|
1323
|
+
if (!smallStepUp & isBlockedX) // resolve x collision
|
|
1320
1324
|
{
|
|
1321
1325
|
// push outside collision
|
|
1322
1326
|
this.pos.x = o.pos.x + (sizeBoth.x/2 + epsilon) * sign(oldPos.x - o.pos.x);
|
|
@@ -1354,7 +1358,7 @@ class EngineObject
|
|
|
1354
1358
|
// test which side we bounced off (or both if a corner)
|
|
1355
1359
|
const isBlockedY = tileCollisionTest(new Vector2(oldPos.x, this.pos.y), this.size, this);
|
|
1356
1360
|
const isBlockedX = tileCollisionTest(new Vector2(this.pos.x, oldPos.y), this.size, this);
|
|
1357
|
-
if (isBlockedY
|
|
1361
|
+
if (isBlockedY | !isBlockedX)
|
|
1358
1362
|
{
|
|
1359
1363
|
// set if landed on ground
|
|
1360
1364
|
this.groundObject = wasMovingDown;
|
|
@@ -1513,11 +1517,6 @@ class EngineObject
|
|
|
1513
1517
|
|
|
1514
1518
|
'use strict';
|
|
1515
1519
|
|
|
1516
|
-
/** Tile sheet for batch rendering system
|
|
1517
|
-
* @type {Image}
|
|
1518
|
-
* @memberof Draw */
|
|
1519
|
-
const tileImage = new Image();
|
|
1520
|
-
|
|
1521
1520
|
/** The primary 2D canvas visible to the user
|
|
1522
1521
|
* @type {HTMLCanvasElement}
|
|
1523
1522
|
* @memberof Draw */
|
|
@@ -1543,6 +1542,14 @@ let overlayContext;
|
|
|
1543
1542
|
* @memberof Draw */
|
|
1544
1543
|
let mainCanvasSize = vec2();
|
|
1545
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
|
+
|
|
1546
1553
|
/** Convert from screen to world space coordinates
|
|
1547
1554
|
* - if calling outside of render, you may need to manually set mainCanvasSize
|
|
1548
1555
|
* @param {Vector2} screenPos
|
|
@@ -1567,7 +1574,7 @@ const worldToScreen = (worldPos)=>
|
|
|
1567
1574
|
|
|
1568
1575
|
/** Draw textured tile centered in world space, with color applied if using WebGL
|
|
1569
1576
|
* @param {Vector2} pos - Center of the tile in world space
|
|
1570
|
-
* @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
|
|
1571
1578
|
* @param {Number} [tileIndex=-1] - Tile index to use, negative is untextured
|
|
1572
1579
|
* @param {Vector2} [tileSize=tileSizeDefault] - Tile size in source pixels
|
|
1573
1580
|
* @param {Color} [color=new Color(1,1,1)] - Color to modulate with
|
|
@@ -1773,25 +1780,23 @@ function drawText(text, pos, size=1, color, lineWidth, lineColor, textAlign, fon
|
|
|
1773
1780
|
* font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
|
|
1774
1781
|
*/
|
|
1775
1782
|
|
|
1783
|
+
// default font image created by engine
|
|
1776
1784
|
let engineFontImage;
|
|
1777
1785
|
|
|
1778
1786
|
class FontImage
|
|
1779
1787
|
{
|
|
1780
1788
|
/** Create an image font
|
|
1781
|
-
* @param {HTMLImageElement}
|
|
1782
|
-
* @param {Vector2} [tileSize=vec2(8)] -
|
|
1789
|
+
* @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
|
|
1790
|
+
* @param {Vector2} [tileSize=vec2(8)] - Size of the font source tiles
|
|
1783
1791
|
* @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
|
|
1784
1792
|
* @param {Number} [startTileIndex=0] - Tile index in image where font starts
|
|
1785
1793
|
* @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
1786
1794
|
*/
|
|
1787
1795
|
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
|
|
1788
1796
|
{
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
engineFontImage = new Image();
|
|
1793
|
-
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
1794
|
-
}
|
|
1797
|
+
// load default font image
|
|
1798
|
+
if (!engineFontImage)
|
|
1799
|
+
(engineFontImage = new Image).src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
1795
1800
|
|
|
1796
1801
|
this.image = image || engineFontImage;
|
|
1797
1802
|
this.tileSize = tileSize;
|
|
@@ -1856,7 +1861,7 @@ class FontImage
|
|
|
1856
1861
|
/** Returns true if fullscreen mode is active
|
|
1857
1862
|
* @return {Boolean}
|
|
1858
1863
|
* @memberof Draw */
|
|
1859
|
-
const isFullscreen =()=> document.fullscreenElement;
|
|
1864
|
+
const isFullscreen = ()=> document.fullscreenElement;
|
|
1860
1865
|
|
|
1861
1866
|
/** Toggle fullsceen mode
|
|
1862
1867
|
* @memberof Draw */
|
|
@@ -1866,16 +1871,9 @@ function toggleFullscreen()
|
|
|
1866
1871
|
{
|
|
1867
1872
|
if (document.exitFullscreen)
|
|
1868
1873
|
document.exitFullscreen();
|
|
1869
|
-
else if (document.mozCancelFullScreen)
|
|
1870
|
-
document.mozCancelFullScreen();
|
|
1871
|
-
}
|
|
1872
|
-
else
|
|
1873
|
-
{
|
|
1874
|
-
if (document.body.webkitRequestFullScreen)
|
|
1875
|
-
document.body.webkitRequestFullScreen();
|
|
1876
|
-
else if (document.body.mozRequestFullScreen)
|
|
1877
|
-
document.body.mozRequestFullScreen();
|
|
1878
1874
|
}
|
|
1875
|
+
else if (document.body.requestFullscreen)
|
|
1876
|
+
document.body.requestFullscreen();
|
|
1879
1877
|
}
|
|
1880
1878
|
|
|
1881
1879
|
/**
|
|
@@ -1894,7 +1892,7 @@ function toggleFullscreen()
|
|
|
1894
1892
|
* @param {Number} [device=0]
|
|
1895
1893
|
* @return {Boolean}
|
|
1896
1894
|
* @memberof Input */
|
|
1897
|
-
const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1
|
|
1895
|
+
const keyIsDown = (key, device=0)=> inputData[device] && inputData[device][key] & 1;
|
|
1898
1896
|
|
|
1899
1897
|
/** Returns true if device key was pressed this frame
|
|
1900
1898
|
* @param {Number} key
|
|
@@ -2134,35 +2132,40 @@ const isTouchDevice = window.ontouchstart !== undefined;
|
|
|
2134
2132
|
if (isTouchDevice)
|
|
2135
2133
|
{
|
|
2136
2134
|
// override mouse events
|
|
2137
|
-
|
|
2138
|
-
onmousedown = onmouseup =
|
|
2135
|
+
let wasTouching, mouseDown = onmousedown, mouseUp = onmouseup;
|
|
2136
|
+
onmousedown = onmouseup = ()=> 0;
|
|
2139
2137
|
|
|
2140
|
-
//
|
|
2141
|
-
|
|
2142
|
-
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2138
|
+
// setup touch input
|
|
2139
|
+
ontouchstart = (e)=>
|
|
2143
2140
|
{
|
|
2144
|
-
|
|
2141
|
+
// fix mobile audio, force it to play a sound on first touch
|
|
2142
|
+
zzfx(0);
|
|
2145
2143
|
|
|
2146
|
-
//
|
|
2147
|
-
|
|
2148
|
-
if (touching)
|
|
2144
|
+
// handle all touch events the same way
|
|
2145
|
+
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2149
2146
|
{
|
|
2150
|
-
|
|
2151
|
-
hadTouch || zzfx(0, hadTouch=1);
|
|
2147
|
+
e.button = 0; // all touches are left click
|
|
2152
2148
|
|
|
2153
|
-
//
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2149
|
+
// check if touching and pass to mouse events
|
|
2150
|
+
const touching = e.touches.length;
|
|
2151
|
+
if (touching)
|
|
2152
|
+
{
|
|
2153
|
+
// set event pos and pass it along
|
|
2154
|
+
e.x = e.touches[0].clientX;
|
|
2155
|
+
e.y = e.touches[0].clientY;
|
|
2156
|
+
wasTouching ? onmousemove(e) : mouseDown(e);
|
|
2157
|
+
}
|
|
2158
|
+
else if (wasTouching)
|
|
2159
|
+
mouseUp(e);
|
|
2160
|
+
|
|
2161
|
+
// set was touching
|
|
2162
|
+
wasTouching = touching;
|
|
2160
2163
|
|
|
2161
|
-
|
|
2162
|
-
|
|
2164
|
+
// must return true so the document will get focus
|
|
2165
|
+
return true;
|
|
2166
|
+
}
|
|
2163
2167
|
|
|
2164
|
-
|
|
2165
|
-
return true;
|
|
2168
|
+
return ontouchstart(e);
|
|
2166
2169
|
}
|
|
2167
2170
|
}
|
|
2168
2171
|
|
|
@@ -2170,7 +2173,7 @@ if (isTouchDevice)
|
|
|
2170
2173
|
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
2171
2174
|
|
|
2172
2175
|
// touch input internal variables
|
|
2173
|
-
let touchGamepadTimer = new Timer, touchGamepadButtons
|
|
2176
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
2174
2177
|
|
|
2175
2178
|
// create the touch gamepad, called automatically by the engine
|
|
2176
2179
|
function touchGamepadCreate()
|
|
@@ -2178,65 +2181,73 @@ function touchGamepadCreate()
|
|
|
2178
2181
|
if (!touchGamepadEnable || !isTouchDevice)
|
|
2179
2182
|
return;
|
|
2180
2183
|
|
|
2181
|
-
|
|
2184
|
+
// touch input internal variables
|
|
2185
|
+
touchGamepadButtons = [];
|
|
2186
|
+
touchGamepadStick = vec2();
|
|
2187
|
+
|
|
2188
|
+
// setup touch input
|
|
2189
|
+
ontouchstart = (e)=>
|
|
2182
2190
|
{
|
|
2183
|
-
|
|
2184
|
-
|
|
2191
|
+
// fix mobile audio, force it to play a sound on first touch
|
|
2192
|
+
zzfx(0);
|
|
2185
2193
|
|
|
2186
|
-
|
|
2187
|
-
touchGamepadStick = vec2();
|
|
2188
|
-
touchGamepadButtons = [];
|
|
2189
|
-
|
|
2190
|
-
const touching = e.touches.length;
|
|
2191
|
-
if (touching)
|
|
2194
|
+
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2192
2195
|
{
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
if (paused)
|
|
2196
|
+
// clear touch gamepad input
|
|
2197
|
+
touchGamepadStick = vec2();
|
|
2198
|
+
touchGamepadButtons = [];
|
|
2199
|
+
|
|
2200
|
+
const touching = e.touches.length;
|
|
2201
|
+
if (touching)
|
|
2200
2202
|
{
|
|
2201
|
-
//
|
|
2202
|
-
|
|
2203
|
-
|
|
2203
|
+
// set that gamepad is active
|
|
2204
|
+
isUsingGamepad = 1;
|
|
2205
|
+
touchGamepadTimer.set();
|
|
2206
|
+
|
|
2207
|
+
if (paused)
|
|
2208
|
+
{
|
|
2209
|
+
// touch anywhere to press start when paused
|
|
2210
|
+
touchGamepadButtons[9] = 1;
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2204
2213
|
}
|
|
2205
|
-
}
|
|
2206
2214
|
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2215
|
+
// get center of left and right sides
|
|
2216
|
+
const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2217
|
+
const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
|
|
2218
|
+
const startCenter = mainCanvasSize.scale(.5);
|
|
2211
2219
|
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
{
|
|
2215
|
-
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
2216
|
-
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
2220
|
+
// check each touch point
|
|
2221
|
+
for (const touch of e.touches)
|
|
2217
2222
|
{
|
|
2218
|
-
|
|
2219
|
-
if (
|
|
2220
|
-
touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
|
|
2221
|
-
else
|
|
2223
|
+
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
2224
|
+
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
2222
2225
|
{
|
|
2223
|
-
//
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
+
// virtual analog stick
|
|
2227
|
+
if (touchGamepadAnalog)
|
|
2228
|
+
touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
|
|
2229
|
+
else
|
|
2230
|
+
{
|
|
2231
|
+
// 8 way dpad
|
|
2232
|
+
const angle = touchPos.subtract(stickCenter).angle();
|
|
2233
|
+
touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
2237
|
+
{
|
|
2238
|
+
// virtual face buttons
|
|
2239
|
+
const button = touchPos.subtract(buttonCenter).direction();
|
|
2240
|
+
touchGamepadButtons[button] = 1;
|
|
2241
|
+
}
|
|
2242
|
+
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
2243
|
+
{
|
|
2244
|
+
// virtual start button in center
|
|
2245
|
+
touchGamepadButtons[9] = 1;
|
|
2226
2246
|
}
|
|
2227
|
-
}
|
|
2228
|
-
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
2229
|
-
{
|
|
2230
|
-
// virtual face buttons
|
|
2231
|
-
const button = touchPos.subtract(buttonCenter).direction();
|
|
2232
|
-
touchGamepadButtons[button] = 1;
|
|
2233
|
-
}
|
|
2234
|
-
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
2235
|
-
{
|
|
2236
|
-
// virtual start button in center
|
|
2237
|
-
touchGamepadButtons[9] = 1;
|
|
2238
2247
|
}
|
|
2239
2248
|
}
|
|
2249
|
+
|
|
2250
|
+
return ontouchstart(e);
|
|
2240
2251
|
}
|
|
2241
2252
|
}
|
|
2242
2253
|
|
|
@@ -2354,7 +2365,7 @@ class Sound
|
|
|
2354
2365
|
{
|
|
2355
2366
|
if (!soundEnable) return;
|
|
2356
2367
|
|
|
2357
|
-
let pan
|
|
2368
|
+
let pan;
|
|
2358
2369
|
if (pos)
|
|
2359
2370
|
{
|
|
2360
2371
|
const range = this.range;
|
|
@@ -2384,7 +2395,7 @@ class Sound
|
|
|
2384
2395
|
* @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
|
|
2385
2396
|
* @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
|
|
2386
2397
|
*/
|
|
2387
|
-
playNote(semitoneOffset, pos, volume
|
|
2398
|
+
playNote(semitoneOffset, pos, volume)
|
|
2388
2399
|
{
|
|
2389
2400
|
if (!soundEnable) return;
|
|
2390
2401
|
|
|
@@ -2439,7 +2450,7 @@ class Music
|
|
|
2439
2450
|
* @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
|
|
2440
2451
|
* @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
|
|
2441
2452
|
*/
|
|
2442
|
-
play(volume
|
|
2453
|
+
play(volume, loop = 1)
|
|
2443
2454
|
{
|
|
2444
2455
|
if (!soundEnable) return;
|
|
2445
2456
|
|
|
@@ -2521,7 +2532,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2521
2532
|
|
|
2522
2533
|
// create audio context
|
|
2523
2534
|
if (!audioContext)
|
|
2524
|
-
audioContext = new
|
|
2535
|
+
audioContext = new AudioContext;
|
|
2525
2536
|
|
|
2526
2537
|
// fix stalled audio
|
|
2527
2538
|
audioContext.resume();
|
|
@@ -2540,18 +2551,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2540
2551
|
source.playbackRate.value = rate;
|
|
2541
2552
|
source.loop = loop;
|
|
2542
2553
|
|
|
2543
|
-
// create and connect gain node (createGain is more
|
|
2554
|
+
// create and connect gain node (createGain is more widely spported then GainNode construtor)
|
|
2544
2555
|
const gainNode = audioContext.createGain();
|
|
2545
2556
|
gainNode.gain.value = soundVolume*volume;
|
|
2546
2557
|
gainNode.connect(audioContext.destination);
|
|
2547
2558
|
|
|
2548
|
-
// connect source to gain
|
|
2549
|
-
(
|
|
2550
|
-
window.StereoPannerNode ? // create pan node if possible
|
|
2551
|
-
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
|
|
2552
|
-
: source
|
|
2553
|
-
)
|
|
2554
|
-
.connect(gainNode);
|
|
2559
|
+
// connect source to stereo panner and gain
|
|
2560
|
+
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)})).connect(gainNode);
|
|
2555
2561
|
|
|
2556
2562
|
// play and return sound
|
|
2557
2563
|
source.start();
|
|
@@ -2669,10 +2675,8 @@ function zzfxG
|
|
|
2669
2675
|
* @memberof Audio */
|
|
2670
2676
|
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
2671
2677
|
{
|
|
2678
|
+
let i, j, k;
|
|
2672
2679
|
let instrumentParameters;
|
|
2673
|
-
let i;
|
|
2674
|
-
let j;
|
|
2675
|
-
let k;
|
|
2676
2680
|
let note;
|
|
2677
2681
|
let sample;
|
|
2678
2682
|
let patternChannel;
|
|
@@ -2842,14 +2846,11 @@ function tileCollisionRaycast(posStart, posEnd, object)
|
|
|
2842
2846
|
{
|
|
2843
2847
|
// test if a ray collides with tiles from start to end
|
|
2844
2848
|
// todo: a way to get the exact hit point, it must still register as inside the hit tile
|
|
2845
|
-
posStart = posStart.floor();
|
|
2846
|
-
posEnd = posEnd.floor();
|
|
2847
|
-
const posDelta = posEnd.subtract(posStart);
|
|
2849
|
+
const posDelta = (posEnd = posEnd.floor()).subtract(posStart = posStart.floor());
|
|
2848
2850
|
const dx = abs(posDelta.x), dy = -abs(posDelta.y);
|
|
2849
2851
|
const sx = sign(posDelta.x), sy = sign(posDelta.y);
|
|
2850
|
-
let e = dx + dy;
|
|
2851
2852
|
|
|
2852
|
-
for (let x = posStart.x, y = posStart.y;;)
|
|
2853
|
+
for (let x = posStart.x, y = posStart.y, e = dx + dy;;)
|
|
2853
2854
|
{
|
|
2854
2855
|
const tileData = getTileCollisionData(vec2(x,y));
|
|
2855
2856
|
if (tileData && (object ? object.collideWithTileRaycast(tileData, new Vector2(x, y)) : tileData > 0))
|
|
@@ -2941,7 +2942,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
|
|
|
2941
2942
|
// init tile data
|
|
2942
2943
|
this.data = [];
|
|
2943
2944
|
for (let j = this.size.area(); j--;)
|
|
2944
|
-
this.data.push(new TileLayerData
|
|
2945
|
+
this.data.push(new TileLayerData);
|
|
2945
2946
|
}
|
|
2946
2947
|
|
|
2947
2948
|
/** Set data at a given position in the array
|
|
@@ -2997,21 +2998,23 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
|
|
|
2997
2998
|
* @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
|
|
2998
2999
|
redrawStart(clear = 0)
|
|
2999
3000
|
{
|
|
3000
|
-
if (clear)
|
|
3001
|
-
{
|
|
3002
|
-
// clear and set size
|
|
3003
|
-
this.canvas.width = this.size.x * this.tileSize.x;
|
|
3004
|
-
this.canvas.height = this.size.y * this.tileSize.y;
|
|
3005
|
-
}
|
|
3006
|
-
|
|
3007
3001
|
// save current render settings
|
|
3008
|
-
this.savedRenderSettings = [mainCanvas, mainContext, cameraPos, cameraScale];
|
|
3002
|
+
this.savedRenderSettings = [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale];
|
|
3009
3003
|
|
|
3010
|
-
// use normal rendering system to render the tiles
|
|
3004
|
+
// hack: use normal rendering system to render the tiles
|
|
3011
3005
|
mainCanvas = this.canvas;
|
|
3012
3006
|
mainContext = this.context;
|
|
3013
3007
|
cameraPos = this.size.scale(.5);
|
|
3014
3008
|
cameraScale = this.tileSize.x;
|
|
3009
|
+
|
|
3010
|
+
if (clear)
|
|
3011
|
+
{
|
|
3012
|
+
// clear and set size
|
|
3013
|
+
mainCanvas.width = this.size.x * this.tileSize.x;
|
|
3014
|
+
mainCanvas.height = this.size.y * this.tileSize.y;
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
// begin a new render for the tile canvas
|
|
3015
3018
|
enginePreRender();
|
|
3016
3019
|
}
|
|
3017
3020
|
|
|
@@ -3023,7 +3026,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
|
|
|
3023
3026
|
//debugSaveCanvas(this.canvas);
|
|
3024
3027
|
|
|
3025
3028
|
// set stuff back to normal
|
|
3026
|
-
[mainCanvas, mainContext, cameraPos, cameraScale] = this.savedRenderSettings;
|
|
3029
|
+
[mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale] = this.savedRenderSettings;
|
|
3027
3030
|
}
|
|
3028
3031
|
|
|
3029
3032
|
/** Draw the tile at a given position
|
|
@@ -3264,7 +3267,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3264
3267
|
this.parent && super.update();
|
|
3265
3268
|
|
|
3266
3269
|
// update emitter
|
|
3267
|
-
if (!this.emitTime
|
|
3270
|
+
if (!this.emitTime | this.getAliveTime() <= this.emitTime)
|
|
3268
3271
|
{
|
|
3269
3272
|
// emit particles
|
|
3270
3273
|
if (this.emitRate * particleEmitRateScale)
|
|
@@ -3288,7 +3291,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3288
3291
|
let pos = this.emitSize.x != undefined ? // check if vec2 was used for size
|
|
3289
3292
|
(new Vector2(rand(-.5,.5), rand(-.5,.5)))
|
|
3290
3293
|
.multiply(this.emitSize).rotate(this.angle) // box emitter
|
|
3291
|
-
: randInCircle(this.emitSize
|
|
3294
|
+
: randInCircle(this.emitSize/2); // circle emitter
|
|
3292
3295
|
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
3293
3296
|
if (!this.localSpace)
|
|
3294
3297
|
{
|
|
@@ -3331,7 +3334,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3331
3334
|
particle.additive = this.additive;
|
|
3332
3335
|
particle.renderOrder = this.renderOrder;
|
|
3333
3336
|
particle.trailScale = this.trailScale;
|
|
3334
|
-
particle.mirror =
|
|
3337
|
+
particle.mirror = randInt(2);
|
|
3335
3338
|
particle.localSpaceEmitter = this.localSpace && this;
|
|
3336
3339
|
|
|
3337
3340
|
// setup callbacks for particles
|
|
@@ -3360,7 +3363,8 @@ class Particle extends EngineObject
|
|
|
3360
3363
|
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
3361
3364
|
* @param {Number} [angle=0] - Angle to rotate the particle
|
|
3362
3365
|
*/
|
|
3363
|
-
constructor(pos, tileIndex, tileSize, angle)
|
|
3366
|
+
constructor(pos, tileIndex, tileSize, angle)
|
|
3367
|
+
{ super(pos, new Vector2, tileIndex, tileSize, angle); }
|
|
3364
3368
|
|
|
3365
3369
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
3366
3370
|
render()
|
|
@@ -3429,10 +3433,6 @@ class Particle extends EngineObject
|
|
|
3429
3433
|
* @memberof Medals */
|
|
3430
3434
|
const medals = [];
|
|
3431
3435
|
|
|
3432
|
-
/** Set to stop medals from being unlockable (like if cheats are enabled)
|
|
3433
|
-
* @memberof Medals */
|
|
3434
|
-
let medalsPreventUnlock;
|
|
3435
|
-
|
|
3436
3436
|
// Engine internal variables not exposed to documentation
|
|
3437
3437
|
let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast;
|
|
3438
3438
|
|
|
@@ -3480,9 +3480,8 @@ class Medal
|
|
|
3480
3480
|
this.name = name;
|
|
3481
3481
|
this.description = description;
|
|
3482
3482
|
this.icon = icon;
|
|
3483
|
-
this.image = new Image();
|
|
3484
3483
|
if (src)
|
|
3485
|
-
this.image.src = src;
|
|
3484
|
+
(this.image = new Image).src = src;
|
|
3486
3485
|
}
|
|
3487
3486
|
|
|
3488
3487
|
/** Unlocks a medal if not already unlocked */
|
|
@@ -3495,10 +3494,7 @@ class Medal
|
|
|
3495
3494
|
ASSERT(medalsSaveName); // save name must be set
|
|
3496
3495
|
localStorage[this.storageKey()] = this.unlocked = 1;
|
|
3497
3496
|
medalsDisplayQueue.push(this);
|
|
3498
|
-
|
|
3499
|
-
// save for newgrounds and OS13K
|
|
3500
3497
|
newgrounds && newgrounds.unlockMedal(this.id);
|
|
3501
|
-
localStorage['OS13kTrophy,' + this.icon + ',' + medalsSaveName + ',' + this.name] = this.description;
|
|
3502
3498
|
}
|
|
3503
3499
|
|
|
3504
3500
|
/** Render a medal
|
|
@@ -3507,27 +3503,26 @@ class Medal
|
|
|
3507
3503
|
render(hidePercent=0)
|
|
3508
3504
|
{
|
|
3509
3505
|
const context = overlayContext;
|
|
3510
|
-
const width = min(
|
|
3506
|
+
const width = min(medalDisplaySize.x, mainCanvas.width);
|
|
3511
3507
|
const x = overlayCanvas.width - width;
|
|
3512
|
-
const y = -
|
|
3508
|
+
const y = -medalDisplaySize.y*hidePercent;
|
|
3513
3509
|
|
|
3514
3510
|
// draw containing rect and clip to that region
|
|
3515
3511
|
context.save();
|
|
3516
3512
|
context.beginPath();
|
|
3517
|
-
context.fillStyle =
|
|
3518
|
-
context.
|
|
3519
|
-
context.strokeStyle = '#000';
|
|
3513
|
+
context.fillStyle = new Color(.9,.9,.9);
|
|
3514
|
+
context.strokeStyle = new Color(0,0,0);
|
|
3520
3515
|
context.lineWidth = 3;
|
|
3516
|
+
context.fill(context.rect(x, y, width, medalDisplaySize.y));
|
|
3521
3517
|
context.stroke();
|
|
3522
3518
|
context.clip();
|
|
3523
3519
|
|
|
3524
3520
|
// draw the icon and text
|
|
3525
|
-
this.renderIcon(x+15+medalDisplayIconSize/2, y+
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
|
|
3521
|
+
this.renderIcon(vec2(x+15+medalDisplayIconSize/2, y+medalDisplaySize.y/2));
|
|
3522
|
+
const pos = vec2(x+medalDisplayIconSize+30, y+28);
|
|
3523
|
+
drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, 0, 'left');
|
|
3524
|
+
pos.y += 32;
|
|
3525
|
+
drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, 0, 'left');
|
|
3531
3526
|
context.restore();
|
|
3532
3527
|
}
|
|
3533
3528
|
|
|
@@ -3536,18 +3531,13 @@ class Medal
|
|
|
3536
3531
|
* @param {Number} y - Screen space Y position
|
|
3537
3532
|
* @param {Number} [size=medalDisplayIconSize] - Screen space size
|
|
3538
3533
|
*/
|
|
3539
|
-
renderIcon(
|
|
3534
|
+
renderIcon(pos, size=medalDisplayIconSize)
|
|
3540
3535
|
{
|
|
3541
3536
|
// draw the image or icon
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
context.textAlign = 'center';
|
|
3545
|
-
context.textBaseline = 'middle';
|
|
3546
|
-
context.font = size*.7 + 'px '+ fontDefault;
|
|
3547
|
-
if (this.image.src)
|
|
3548
|
-
context.drawImage(this.image, x-size/2, y-size/2, size, size);
|
|
3537
|
+
if (this.image)
|
|
3538
|
+
overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size);
|
|
3549
3539
|
else
|
|
3550
|
-
|
|
3540
|
+
drawTextScreen(this.icon, pos, size*.7, new Color(0,0,0));
|
|
3551
3541
|
}
|
|
3552
3542
|
|
|
3553
3543
|
// Get local storage key used by the medal
|
|
@@ -3605,18 +3595,20 @@ class Newgrounds
|
|
|
3605
3595
|
constructor(app_id, cipher)
|
|
3606
3596
|
{
|
|
3607
3597
|
ASSERT(!newgrounds && app_id);
|
|
3598
|
+
|
|
3608
3599
|
this.app_id = app_id;
|
|
3609
3600
|
this.cipher = cipher;
|
|
3610
3601
|
this.host = location ? location.hostname : '';
|
|
3611
|
-
|
|
3602
|
+
|
|
3612
3603
|
// create an instance of CryptoJS for encrypted calls
|
|
3613
|
-
|
|
3604
|
+
if (cipher)
|
|
3605
|
+
this.cryptoJS = this.CryptoJS();
|
|
3614
3606
|
|
|
3615
3607
|
// get session id from url search params
|
|
3616
3608
|
const url = new URL(location.href);
|
|
3617
|
-
this.session_id = url.searchParams.get('ngio_session_id')
|
|
3609
|
+
this.session_id = url.searchParams.get('ngio_session_id');
|
|
3618
3610
|
|
|
3619
|
-
if (this.session_id
|
|
3611
|
+
if (!this.session_id)
|
|
3620
3612
|
return; // only use newgrounds when logged in
|
|
3621
3613
|
|
|
3622
3614
|
// get medals
|
|
@@ -3629,6 +3621,7 @@ class Newgrounds
|
|
|
3629
3621
|
if (medal)
|
|
3630
3622
|
{
|
|
3631
3623
|
// copy newgrounds medal data
|
|
3624
|
+
medal.image = new Image;
|
|
3632
3625
|
medal.image.src = newgroundsMedal['icon'];
|
|
3633
3626
|
medal.name = newgroundsMedal['name'];
|
|
3634
3627
|
medal.description = newgroundsMedal['description'];
|
|
@@ -3713,13 +3706,16 @@ class Newgrounds
|
|
|
3713
3706
|
debugMedals && console.log(xmlHttp.responseText);
|
|
3714
3707
|
return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
|
|
3715
3708
|
}
|
|
3716
|
-
}
|
|
3717
3709
|
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3710
|
+
CryptoJS()
|
|
3711
|
+
{
|
|
3712
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3713
|
+
// Crypto-JS - https://github.com/brix/crypto-js [The MIT License (MIT)]
|
|
3714
|
+
// Copyright (c) 2009-2013 Jeff Mott Copyright (c) 2013-2016 Evan Vosberg
|
|
3721
3715
|
|
|
3722
|
-
|
|
3716
|
+
return eval(Function("[M='GBMGXz^oVYPPKKbB`agTXU|LxPc_ZBcMrZvCr~wyGfWrwk@ATqlqeTp^N?p{we}jIpEnB_sEr`l?YDkDhWhprc|Er|XETG?pTl`e}dIc[_N~}fzRycIfpW{HTolvoPB_FMe_eH~BTMx]yyOhv?biWPCGc]kABencBhgERHGf{OL`Dj`c^sh@canhy[secghiyotcdOWgO{tJIE^JtdGQRNSCrwKYciZOa]Y@tcRATYKzv|sXpboHcbCBf`}SKeXPFM|RiJsSNaIb]QPc[D]Jy_O^XkOVTZep`ONmntLL`Qz~UupHBX_Ia~WX]yTRJIxG`ioZ{fefLJFhdyYoyLPvqgH?b`[TMnTwwfzDXhfM?rKs^aFr|nyBdPmVHTtAjXoYUloEziWDCw_suyYT~lSMksI~ZNCS[Bex~j]Vz?kx`gdYSEMCsHpjbyxQvw|XxX_^nQYue{sBzVWQKYndtYQMWRef{bOHSfQhiNdtR{o?cUAHQAABThwHPT}F{VvFmgN`E@FiFYS`UJmpQNM`X|tPKHlccT}z}k{sACHL?Rt@MkWplxO`ASgh?hBsuuP|xD~LSH~KBlRs]t|l|_tQAroDRqWS^SEr[sYdPB}TAROtW{mIkE|dWOuLgLmJrucGLpebrAFKWjikTUzS|j}M}szasKOmrjy[?hpwnEfX[jGpLt@^v_eNwSQHNwtOtDgWD{rk|UgASs@mziIXrsHN_|hZuxXlPJOsA^^?QY^yGoCBx{ekLuZzRqQZdsNSx@ezDAn{XNj@fRXIwrDX?{ZQHwTEfu@GhxDOykqts|n{jOeZ@c`dvTY?e^]ATvWpb?SVyg]GC?SlzteilZJAL]mlhLjYZazY__qcVFYvt@|bIQnSno@OXyt]OulzkWqH`rYFWrwGs`v|~XeTsIssLrbmHZCYHiJrX}eEzSssH}]l]IhPQhPoQ}rCXLyhFIT[clhzYOvyHqigxmjz`phKUU^TPf[GRAIhNqSOdayFP@FmKmuIzMOeoqdpxyCOwCthcLq?n`L`tLIBboNn~uXeFcPE{C~mC`h]jUUUQe^`UqvzCutYCgct|SBrAeiYQW?X~KzCz}guXbsUw?pLsg@hDArw?KeJD[BN?GD@wgFWCiHq@Ypp_QKFixEKWqRp]oJFuVIEvjDcTFu~Zz]a{IcXhWuIdMQjJ]lwmGQ|]g~c]Hl]pl`Pd^?loIcsoNir_kikBYyg?NarXZEGYspt_vLBIoj}LI[uBFvm}tbqvC|xyR~a{kob|HlctZslTGtPDhBKsNsoZPuH`U`Fqg{gKnGSHVLJ^O`zmNgMn~{rsQuoymw^JY?iUBvw_~mMr|GrPHTERS[MiNpY[Mm{ggHpzRaJaoFomtdaQ_?xuTRm}@KjU~RtPsAdxa|uHmy}n^i||FVL[eQAPrWfLm^ndczgF~Nk~aplQvTUpHvnTya]kOenZlLAQIm{lPl@CCTchvCF[fI{^zPkeYZTiamoEcKmBMfZhk_j_~Fjp|wPVZlkh_nHu]@tP|hS@^G^PdsQ~f[RqgTDqezxNFcaO}HZhb|MMiNSYSAnQWCDJukT~e|OTgc}sf[cnr?fyzTa|EwEtRG|I~|IO}O]S|rp]CQ}}DWhSjC_|z|oY|FYl@WkCOoPuWuqr{fJu?Brs^_EBI[@_OCKs}?]O`jnDiXBvaIWhhMAQDNb{U`bqVR}oqVAvR@AZHEBY@depD]OLh`kf^UsHhzKT}CS}HQKy}Q~AeMydXPQztWSSzDnghULQgMAmbWIZ|lWWeEXrE^EeNoZApooEmrXe{NAnoDf`m}UNlRdqQ@jOc~HLOMWs]IDqJHYoMziEedGBPOxOb?[X`KxkFRg@`mgFYnP{hSaxwZfBQqTm}_?RSEaQga]w[vxc]hMne}VfSlqUeMo_iqmd`ilnJXnhdj^EEFifvZyxYFRf^VaqBhLyrGlk~qowqzHOBlOwtx?i{m~`n^G?Yxzxux}b{LSlx]dS~thO^lYE}bzKmUEzwW^{rPGhbEov[Plv??xtyKJshbG`KuO?hjBdS@Ru}iGpvFXJRrvOlrKN?`I_n_tplk}kgwSXuKylXbRQ]]?a|{xiT[li?k]CJpwy^o@ebyGQrPfF`aszGKp]baIx~H?ElETtFh]dz[OjGl@C?]VDhr}OE@V]wLTc[WErXacM{We`F|utKKjgllAxvsVYBZ@HcuMgLboFHVZmi}eIXAIFhS@A@FGRbjeoJWZ_NKd^oEH`qgy`q[Tq{x?LRP|GfBFFJV|fgZs`MLbpPYUdIV^]mD@FG]pYAT^A^RNCcXVrPsgk{jTrAIQPs_`mD}rOqAZA[}RETFz]WkXFTz_m{N@{W@_fPKZLT`@aIqf|L^Mb|crNqZ{BVsijzpGPEKQQZGlApDn`ruH}cvF|iXcNqK}cxe_U~HRnKV}sCYb`D~oGvwG[Ca|UaybXea~DdD~LiIbGRxJ_VGheI{ika}KC[OZJLn^IBkPrQj_EuoFwZ}DpoBRcK]Q}?EmTv~i_Tul{bky?Iit~tgS|o}JL_VYcCQdjeJ_MfaA`FgCgc[Ii|CBHwq~nbJeYTK{e`CNstKfTKPzw{jdhp|qsZyP_FcugxCFNpKitlR~vUrx^NrSVsSTaEgnxZTmKc`R|lGJeX}ccKLsQZQhsFkeFd|ckHIVTlGMg`~uPwuHRJS_CPuN_ogXe{Ba}dO_UBhuNXby|h?JlgBIqMKx^_u{molgL[W_iavNQuOq?ap]PGB`clAicnl@k~pA?MWHEZ{HuTLsCpOxxrKlBh]FyMjLdFl|nMIvTHyGAlPogqfZ?PlvlFJvYnDQd}R@uAhtJmDfe|iJqdkYr}r@mEjjIetDl_I`TELfoR|qTBu@Tic[BaXjP?dCS~MUK[HPRI}OUOwAaf|_}HZzrwXvbnNgltjTwkBE~MztTQhtRSWoQHajMoVyBBA`kdgK~h`o[J`dm~pm]tk@i`[F~F]DBlJKklrkR]SNw@{aG~Vhl`KINsQkOy?WhcqUMTGDOM_]bUjVd|Yh_KUCCgIJ|LDIGZCPls{RzbVWVLEhHvWBzKq|^N?DyJB|__aCUjoEgsARki}j@DQXS`RNU|DJ^a~d{sh_Iu{ONcUtSrGWW@cvUjefHHi}eSSGrNtO?cTPBShLqzwMVjWQQCCFB^culBjZHEK_{dO~Q`YhJYFn]jq~XSnG@[lQr]eKrjXpG~L^h~tDgEma^AUFThlaR{xyuP@[^VFwXSeUbVetufa@dX]CLyAnDV@Bs[DnpeghJw^?UIana}r_CKGDySoRudklbgio}kIDpA@McDoPK?iYcG?_zOmnWfJp}a[JLR[stXMo?_^Ng[whQlrDbrawZeSZ~SJstIObdDSfAA{MV}?gNunLOnbMv_~KFQUAjIMj^GkoGxuYtYbGDImEYiwEMyTpMxN_LSnSMdl{bg@dtAnAMvhDTBR_FxoQgANniRqxd`pWv@rFJ|mWNWmh[GMJz_Nq`BIN@KsjMPASXORcdHjf~rJfgZYe_uulzqM_KdPlMsuvU^YJuLtofPhGonVOQxCMuXliNvJIaoC?hSxcxKVVxWlNs^ENDvCtSmO~WxI[itnjs^RDvI@KqG}YekaSbTaB]ki]XM@[ZnDAP~@|BzLRgOzmjmPkRE@_sobkT|SszXK[rZN?F]Z_u}Yue^[BZgLtR}FHzWyxWEX^wXC]MJmiVbQuBzkgRcKGUhOvUc_bga|Tx`KEM`JWEgTpFYVeXLCm|mctZR@uKTDeUONPozBeIkrY`cz]]~WPGMUf`MNUGHDbxZuO{gmsKYkAGRPqjc|_FtblEOwy}dnwCHo]PJhN~JoteaJ?dmYZeB^Xd?X^pOKDbOMF@Ugg^hETLdhwlA}PL@_ur|o{VZosP?ntJ_kG][g{Zq`Tu]dzQlSWiKfnxDnk}KOzp~tdFstMobmy[oPYjyOtUzMWdjcNSUAjRuqhLS@AwB^{BFnqjCmmlk?jpn}TksS{KcKkDboXiwK]qMVjm~V`LgWhjS^nLGwfhAYrjDSBL_{cRus~{?xar_xqPlArrYFd?pHKdMEZzzjJpfC?Hv}mAuIDkyBxFpxhstTx`IO{rp}XGuQ]VtbHerlRc_LFGWK[XluFcNGUtDYMZny[M^nVKVeMllQI[xtvwQnXFlWYqxZZFp_|]^oWX[{pOMpxXxvkbyJA[DrPzwD|LW|QcV{Nw~U^dgguSpG]ClmO@j_TENIGjPWwgdVbHganhM?ema|dBaqla|WBd`poj~klxaasKxGG^xbWquAl~_lKWxUkDFagMnE{zHug{b`A~IYcQYBF_E}wiA}K@yxWHrZ{[d~|ARsYsjeNWzkMs~IOqqp[yzDE|WFrivsidTcnbHFRoW@XpAV`lv_zj?B~tPCppRjgbbDTALeFaOf?VcjnKTQMLyp{NwdylHCqmo?oelhjWuXj~}{fpuX`fra?GNkDiChYgVSh{R[BgF~eQa^WVz}ATI_CpY?g_diae]|ijH`TyNIF}|D_xpmBq_JpKih{Ba|sWzhnAoyraiDvk`h{qbBfsylBGmRH}DRPdryEsSaKS~tIaeF[s]I~xxHVrcNe@Jjxa@jlhZueLQqHh_]twVMqG_EGuwyab{nxOF?`HCle}nBZzlTQjkLmoXbXhOtBglFoMz?eqre`HiE@vNwBulglmQjj]DB@pPkPUgA^sjOAUNdSu_`oAzar?n?eMnw{{hYmslYi[TnlJD'",...']charCodeAtUinyxpf',"for(;e<10359;c[e++]=p-=128,A=A?p-A&&A:p==34&&p)for(p=1;p<128;y=f.map((n,x)=>(U=r[n]*2+1,U=Math.log(U/(h-U)),t-=a[x]*U,U/500)),t=~-h/(1+Math.exp(t))|1,i=o%h<t,o=o%h+(i?t:h-t)*(o>>17)-!i*t,f.map((n,x)=>(U=r[n]+=(i*h/2-r[n]<<13)/((C[n]+=C[n]<5)+1/20)>>13,a[x]+=y[x]*(i-t/h))),p=p*2+i)for(f='010202103203210431053105410642065206541'.split(t=0).map((n,x)=>(U=0,[...n].map((n,x)=>(U=U*997+(c[e-n]|0)|0)),h*32-1&U*997+p+!!A*129)*12+x);o<h*32;o=o*64|M.charCodeAt(d++)&63);for(C=String.fromCharCode(...c);r=/[\0-#?@\\\\~]/.exec(C);)with(C.split(r))C=join(shift());return C")([],[],1<<17,[0,0,0,0,0,0,0,0,0,0,0,0],new Uint16Array(51e6).fill(1<<15),new Uint8Array(51e6),0,0,0,0));
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3723
3719
|
/**
|
|
3724
3720
|
* LittleJS WebGL Interface
|
|
3725
3721
|
* <br> - All webgl used by the engine is wrapped up here
|
|
@@ -3749,7 +3745,7 @@ let glContext;
|
|
|
3749
3745
|
let glTileTexture;
|
|
3750
3746
|
|
|
3751
3747
|
// WebGL internal variables not exposed to documentation
|
|
3752
|
-
let glActiveTexture, glShader, glArrayBuffer,
|
|
3748
|
+
let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
|
|
3753
3749
|
|
|
3754
3750
|
///////////////////////////////////////////////////////////////////////////////
|
|
3755
3751
|
|
|
@@ -3770,27 +3766,25 @@ function glInit()
|
|
|
3770
3766
|
'uniform mat4 m;'+ // transform matrix
|
|
3771
3767
|
'attribute vec2 p,t;'+ // position, uv
|
|
3772
3768
|
'attribute vec4 c,a;'+ // color, additiveColor
|
|
3773
|
-
'varying
|
|
3774
|
-
'varying vec4 d,e;'+ // return color, additiveColor
|
|
3769
|
+
'varying vec4 v,d,e;'+ // return uv, color, additiveColor
|
|
3775
3770
|
'void main(){'+ // shader entry point
|
|
3776
3771
|
'gl_Position=m*vec4(p,1,1);'+ // transform position
|
|
3777
|
-
'v=t;d=c;e=a;'+
|
|
3772
|
+
'v=vec4(t,p);d=c;e=a;'+ // pass stuff to fragment shader
|
|
3778
3773
|
'}' // end of shader
|
|
3779
3774
|
,
|
|
3780
|
-
'precision highp float;'+
|
|
3781
|
-
'varying
|
|
3782
|
-
'
|
|
3783
|
-
'
|
|
3784
|
-
'
|
|
3785
|
-
'
|
|
3786
|
-
'}' // end of shader
|
|
3775
|
+
'precision highp float;'+ // use highp for better accuracy
|
|
3776
|
+
'varying vec4 v,d,e;'+ // uv, color, additiveColor
|
|
3777
|
+
'uniform sampler2D s;'+ // texture
|
|
3778
|
+
'void main(){'+ // shader entry point
|
|
3779
|
+
'gl_FragColor=texture2D(s,v.xy)*d+e;'+ // modulate texture by color plus additive
|
|
3780
|
+
'}' // end of shader
|
|
3787
3781
|
);
|
|
3788
3782
|
|
|
3789
3783
|
// init buffers
|
|
3790
|
-
|
|
3784
|
+
const vertexData = new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);
|
|
3791
3785
|
glArrayBuffer = glContext.createBuffer();
|
|
3792
|
-
glPositionData = new Float32Array(
|
|
3793
|
-
glColorData = new Uint32Array(
|
|
3786
|
+
glPositionData = new Float32Array(vertexData);
|
|
3787
|
+
glColorData = new Uint32Array(vertexData);
|
|
3794
3788
|
glBatchCount = 0;
|
|
3795
3789
|
}
|
|
3796
3790
|
|
|
@@ -3865,18 +3859,19 @@ function glCreateTexture(image)
|
|
|
3865
3859
|
image && image.width && glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
3866
3860
|
|
|
3867
3861
|
// use point filtering for pixelated rendering
|
|
3868
|
-
|
|
3869
|
-
glContext.texParameteri(gl_TEXTURE_2D,
|
|
3862
|
+
const filter = cavasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
3863
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
3864
|
+
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
3870
3865
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
|
|
3871
3866
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
|
|
3872
3867
|
return texture;
|
|
3873
3868
|
}
|
|
3874
3869
|
|
|
3875
3870
|
// called automatically by engine before render
|
|
3876
|
-
function glPreRender(
|
|
3871
|
+
function glPreRender()
|
|
3877
3872
|
{
|
|
3878
3873
|
// clear and set to same size as main canvas
|
|
3879
|
-
glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
|
|
3874
|
+
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
3880
3875
|
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3881
3876
|
|
|
3882
3877
|
// set up the shader
|
|
@@ -3884,7 +3879,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
|
|
|
3884
3879
|
glContext.activeTexture(gl_TEXTURE0);
|
|
3885
3880
|
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
|
|
3886
3881
|
glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
|
|
3887
|
-
glContext.bufferData(gl_ARRAY_BUFFER,
|
|
3882
|
+
glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
|
|
3888
3883
|
glSetBlendMode();
|
|
3889
3884
|
|
|
3890
3885
|
// set vertex attributes
|
|
@@ -3902,14 +3897,14 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
|
|
|
3902
3897
|
initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
3903
3898
|
|
|
3904
3899
|
// build the transform matrix
|
|
3905
|
-
const sx = 2 * cameraScale / width;
|
|
3906
|
-
const sy = 2 * cameraScale / height;
|
|
3900
|
+
const sx = 2 * cameraScale / mainCanvas.width;
|
|
3901
|
+
const sy = 2 * cameraScale / mainCanvas.height;
|
|
3907
3902
|
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
|
|
3908
3903
|
new Float32Array([
|
|
3909
3904
|
sx, 0, 0, 0,
|
|
3910
3905
|
0, sy, 0, 0,
|
|
3911
3906
|
1, 1, -1, 1,
|
|
3912
|
-
-1-sx*
|
|
3907
|
+
-1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
|
|
3913
3908
|
])
|
|
3914
3909
|
);
|
|
3915
3910
|
}
|
|
@@ -3957,10 +3952,10 @@ function glCopyToContext(context, forceDraw)
|
|
|
3957
3952
|
* @param uv0Y
|
|
3958
3953
|
* @param uv1X
|
|
3959
3954
|
* @param uv1Y
|
|
3960
|
-
* @param
|
|
3955
|
+
* @param rgba
|
|
3961
3956
|
* @param [rgbaAdditive=0]
|
|
3962
3957
|
* @memberof WebGL */
|
|
3963
|
-
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba
|
|
3958
|
+
function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdditive=0)
|
|
3964
3959
|
{
|
|
3965
3960
|
// flush if there is no room for more verts or if different blend mode
|
|
3966
3961
|
if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
|
|
@@ -3971,43 +3966,16 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
|
|
|
3971
3966
|
const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
|
|
3972
3967
|
|
|
3973
3968
|
// setup 2 triangles to form a quad
|
|
3974
|
-
let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
glPositionData[offset++] = y + cy - sx;
|
|
3985
|
-
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3986
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3987
|
-
|
|
3988
|
-
// vertex 2
|
|
3989
|
-
glPositionData[offset++] = x - cx + sy;
|
|
3990
|
-
glPositionData[offset++] = y + cy + sx;
|
|
3991
|
-
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
|
|
3992
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3993
|
-
|
|
3994
|
-
// vertex 0
|
|
3995
|
-
glPositionData[offset++] = x - cx - sy;
|
|
3996
|
-
glPositionData[offset++] = y - cy + sx;
|
|
3997
|
-
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3998
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3999
|
-
|
|
4000
|
-
// vertex 3
|
|
4001
|
-
glPositionData[offset++] = x + cx - sy;
|
|
4002
|
-
glPositionData[offset++] = y - cy - sx;
|
|
4003
|
-
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
|
|
4004
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
4005
|
-
|
|
4006
|
-
// vertex 1
|
|
4007
|
-
glPositionData[offset++] = x + cx + sy;
|
|
4008
|
-
glPositionData[offset++] = y + cy - sx;
|
|
4009
|
-
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
4010
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3969
|
+
for(let i=6, offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT; i--;)
|
|
3970
|
+
{
|
|
3971
|
+
const a = i-4&&i>1, b = i-5&&i-2&&i-1;
|
|
3972
|
+
glPositionData[offset++] = x + (a?-cx:cx) + (b?sy:-sy);
|
|
3973
|
+
glPositionData[offset++] = y + (b?cy:-cy) + (a?sx:-sx);
|
|
3974
|
+
glPositionData[offset++] = a ? uv0X : uv1X;
|
|
3975
|
+
glPositionData[offset++] = b ? uv0Y : uv1Y;
|
|
3976
|
+
glColorData[offset++] = rgba;
|
|
3977
|
+
glColorData[offset++] = rgbaAdditive;
|
|
3978
|
+
}
|
|
4011
3979
|
}
|
|
4012
3980
|
|
|
4013
3981
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -4059,14 +4027,13 @@ function glRenderPostProcess()
|
|
|
4059
4027
|
return;
|
|
4060
4028
|
|
|
4061
4029
|
// prepare to render post process shader
|
|
4062
|
-
const width = mainCanvas.width, height = mainCanvas.height;
|
|
4063
4030
|
if (glEnable)
|
|
4064
4031
|
{
|
|
4065
4032
|
glFlush(); // clear out the buffer
|
|
4066
4033
|
mainContext.drawImage(glCanvas, 0, 0); // copy to the main canvas
|
|
4067
4034
|
}
|
|
4068
|
-
else
|
|
4069
|
-
glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
|
|
4035
|
+
else // set viewport
|
|
4036
|
+
glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
|
|
4070
4037
|
|
|
4071
4038
|
// setup shader program to draw one triangle
|
|
4072
4039
|
glContext.useProgram(glPostShader);
|
|
@@ -4090,7 +4057,7 @@ function glRenderPostProcess()
|
|
|
4090
4057
|
const uniformLocation = (name)=>glContext.getUniformLocation(glPostShader, name);
|
|
4091
4058
|
glContext.uniform1i(uniformLocation('iChannel0'), 0);
|
|
4092
4059
|
glContext.uniform1f(uniformLocation('iTime'), time);
|
|
4093
|
-
glContext.uniform3f(uniformLocation('iResolution'), width, height, 1);
|
|
4060
|
+
glContext.uniform3f(uniformLocation('iResolution'), mainCanvas.width, mainCanvas.height, 1);
|
|
4094
4061
|
glContext.drawArrays(gl_TRIANGLES, 0, 3);
|
|
4095
4062
|
}
|
|
4096
4063
|
|
|
@@ -4130,4 +4097,5 @@ gl_UNPACK_FLIP_Y_WEBGL = 37440,
|
|
|
4130
4097
|
gl_VERTICES_PER_QUAD = 6,
|
|
4131
4098
|
gl_INDICIES_PER_VERT = 6,
|
|
4132
4099
|
gl_MAX_BATCH = 1<<16,
|
|
4133
|
-
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2
|
|
4100
|
+
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2, // vec2 * 2 + (char * 4) * 2
|
|
4101
|
+
gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTICES_PER_QUAD * gl_VERTEX_BYTE_STRIDE;
|