littlejsengine 1.7.13 → 1.7.21

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.
@@ -1,5 +1,7 @@
1
1
  // LittleJS - MIT License - Copyright 2021 Frank Force
2
2
 
3
+ 'use strict';
4
+
3
5
  /**
4
6
  * LittleJS Debug System
5
7
  * - Press Esc to show debug overlay with mouse pick
@@ -10,7 +12,7 @@
10
12
  * @namespace Debug
11
13
  */
12
14
 
13
- 'use strict';
15
+
14
16
 
15
17
  /** True if debug is enabled
16
18
  * @type {Boolean}
@@ -418,7 +420,7 @@ function debugRender()
418
420
  * @namespace Utilities
419
421
  */
420
422
 
421
- 'use strict';
423
+
422
424
 
423
425
  /** A shortcut to get Math.PI
424
426
  * @type {Number}
@@ -1073,7 +1075,7 @@ class Timer
1073
1075
  * @namespace Settings
1074
1076
  */
1075
1077
 
1076
- 'use strict';
1078
+
1077
1079
 
1078
1080
  ///////////////////////////////////////////////////////////////////////////////
1079
1081
  // Camera settings
@@ -1321,7 +1323,7 @@ let medalsPreventUnlock;
1321
1323
  * LittleJS Object System
1322
1324
  */
1323
1325
 
1324
- 'use strict';
1326
+
1325
1327
 
1326
1328
  /**
1327
1329
  * LittleJS Object Base Object Class
@@ -1717,7 +1719,7 @@ class EngineObject
1717
1719
  * @namespace Draw
1718
1720
  */
1719
1721
 
1720
- 'use strict';
1722
+
1721
1723
 
1722
1724
  /** The primary 2D canvas visible to the user
1723
1725
  * @type {HTMLCanvasElement}
@@ -1908,12 +1910,12 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
1908
1910
  {
1909
1911
  if (!screenSpace)
1910
1912
  {
1911
- // create canvas transform from world space to screen space
1913
+ // transform from world space to screen space
1912
1914
  pos = worldToScreen(pos);
1913
1915
  size = size.scale(cameraScale);
1914
1916
  }
1915
1917
  context.save();
1916
- context.translate(pos.x+.5|0, pos.y+.5|0);
1918
+ context.translate(pos.x+.5, pos.y+.5);
1917
1919
  context.rotate(angle);
1918
1920
  context.scale(mirror ? -size.x : size.x, size.y);
1919
1921
  drawFunction(context);
@@ -2098,7 +2100,7 @@ function toggleFullscreen()
2098
2100
  * @namespace Input
2099
2101
  */
2100
2102
 
2101
- 'use strict';
2103
+
2102
2104
 
2103
2105
  /** Returns true if device key is down
2104
2106
  * @param {Number} key
@@ -2282,18 +2284,26 @@ function mouseToScreen(mousePos)
2282
2284
  const stickData = [];
2283
2285
  function gamepadsUpdate()
2284
2286
  {
2285
- if (touchGamepadEnable && touchGamepadTimer.isSet())
2287
+ // update touch gamepad if enabled
2288
+ if (touchGamepadEnable && isTouchDevice)
2286
2289
  {
2287
- // read virtual analog stick
2288
- const sticks = stickData[0] || (stickData[0] = []);
2289
- sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
2290
+ // create the touch gamepad if it doesn't exist
2291
+ if (!touchGamepadButtons)
2292
+ createTouchGamepad();
2290
2293
 
2291
- // read virtual gamepad buttons
2292
- const data = inputData[1] || (inputData[1] = []);
2293
- for (let i=10; i--;)
2294
+ if (touchGamepadTimer.isSet())
2294
2295
  {
2295
- const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
2296
- data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
2296
+ // read virtual analog stick
2297
+ const sticks = stickData[0] || (stickData[0] = []);
2298
+ sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
2299
+
2300
+ // read virtual gamepad buttons
2301
+ const data = inputData[1] || (inputData[1] = []);
2302
+ for (let i=10; i--;)
2303
+ {
2304
+ const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
2305
+ data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
2306
+ }
2297
2307
  }
2298
2308
  }
2299
2309
 
@@ -2390,6 +2400,10 @@ if (isTouchDevice)
2390
2400
  // set was touching
2391
2401
  wasTouching = touching;
2392
2402
 
2403
+ // prevent default handling like copy and magnifier lens
2404
+ if (document.hasFocus()) // allow document to get focus
2405
+ e.preventDefault();
2406
+
2393
2407
  // must return true so the document will get focus
2394
2408
  return true;
2395
2409
  }
@@ -2402,7 +2416,7 @@ if (isTouchDevice)
2402
2416
  let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
2403
2417
 
2404
2418
  // create the touch gamepad, called automatically by the engine
2405
- if (touchGamepadEnable)
2419
+ function createTouchGamepad()
2406
2420
  {
2407
2421
  // touch input internal variables
2408
2422
  touchGamepadButtons = [];
@@ -2536,7 +2550,7 @@ function touchGamepadRender()
2536
2550
  * @namespace Audio
2537
2551
  */
2538
2552
 
2539
- 'use strict';
2553
+
2540
2554
 
2541
2555
  /**
2542
2556
  * Sound Object - Stores a zzfx sound for later use and can be played positionally
@@ -2572,8 +2586,9 @@ class Sound
2572
2586
  if (zzfxSound)
2573
2587
  {
2574
2588
  // generate zzfx sound now for fast playback
2575
- this.randomness = zzfxSound[1] || (zzfxSound[1] = 0);
2576
- this.cachedSamples = zzfxSound && zzfxG(...zzfxSound);
2589
+ this.randomness = zzfxSound[1] || (zzfxSound[1] = 0);
2590
+ this.sampleChannels = [zzfxG(...zzfxSound)];
2591
+ this.sampleRate = zzfxR;
2577
2592
  }
2578
2593
  }
2579
2594
 
@@ -2582,11 +2597,12 @@ class Sound
2582
2597
  * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2583
2598
  * @param {Number} [pitch=1] - How much to scale pitch by (also adjusted by this.randomness)
2584
2599
  * @param {Number} [randomnessScale=1] - How much to scale randomness
2585
- * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
2600
+ * @param {Boolean} [loop=0] - Should the sound loop
2601
+ * @return {AudioBufferSourceNode} - The audio source node
2586
2602
  */
2587
- play(pos, volume=1, pitch=1, randomnessScale=1)
2603
+ play(pos, volume=1, pitch=1, randomnessScale=1, loop=0)
2588
2604
  {
2589
- if (!soundEnable || !this.cachedSamples) return;
2605
+ if (!soundEnable || !this.sampleChannels) return;
2590
2606
 
2591
2607
  let pan;
2592
2608
  if (pos)
@@ -2609,43 +2625,80 @@ class Sound
2609
2625
 
2610
2626
  // play the sound
2611
2627
  const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
2612
- return playSamples([this.cachedSamples], volume, playbackRate, pan);
2628
+ return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate);
2629
+ }
2630
+
2631
+ /** Stop the last instance of this sound that was played */
2632
+ stop()
2633
+ {
2634
+ if (this.source)
2635
+ this.source.stop();
2636
+ this.source = 0;
2613
2637
  }
2614
2638
 
2615
2639
  /** Play the sound as a note with a semitone offset
2616
2640
  * @param {Number} semitoneOffset - How many semitones to offset pitch
2617
2641
  * @param {Vector2} [pos] - World space position to play the sound, sound is not attenuated if null
2618
2642
  * @param {Number} [volume=1] - How much to scale volume by (in addition to range fade)
2619
- * @return {AudioBufferSourceNode} - The audio, can be used to stop sound later
2643
+ * @return {AudioBufferSourceNode} - The audio source node
2620
2644
  */
2621
2645
  playNote(semitoneOffset, pos, volume)
2622
2646
  { return this.play(pos, volume, 2**(semitoneOffset/12), 0); }
2647
+
2648
+ /** Get how long this sound is in seconds
2649
+ * @return {Number} - How long the sound is in seconds (undefined if loading)
2650
+ */
2651
+ getDuration()
2652
+ { return this.sampleChannels && this.sampleChannels[0].length / this.sampleRate; }
2653
+
2654
+ /** Check if the last instance of this sound is playing
2655
+ * @return {Boolean} - True if the sound is playing
2656
+ */
2657
+ isPlaying() { return this.source && !this.source.ended; }
2658
+
2659
+ /** Check if sound is loading, for sounds fetched from a url
2660
+ * @return {Boolean} - True if sound is loading and not ready to play
2661
+ */
2662
+ isLoading() { return !this.sampleChannels; }
2623
2663
  }
2624
2664
 
2625
2665
  /**
2626
2666
  * Sound Wave Object - Stores a wave sound for later use and can be played positionally
2667
+ * - this can be used to play wave, mp3, and ogg files
2668
+ * @example
2669
+ * // create a sound
2670
+ * const sound_example = new SoundWave('sound.mp3');
2671
+ *
2672
+ * // play the sound
2673
+ * sound_example.play();
2627
2674
  */
2628
2675
  class SoundWave extends Sound
2629
2676
  {
2630
2677
  /** Create a sound object and cache the wave file for later use
2631
- * @param {String} waveFilename - Filename of wave file to load
2632
- * @param {Number} [randomness=.05] - How much to randomize frequency each time sound plays
2678
+ * @param {String} filename - Filename of audio file to load
2679
+ * @param {Number} [randomness=0] - How much to randomize frequency each time sound plays
2633
2680
  * @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
2634
2681
  * @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
2635
2682
  */
2636
- constructor(waveFilename, randomness=.05, range, taper)
2683
+ constructor(filename, randomness=0, range, taper)
2637
2684
  {
2638
2685
  super(0, range, taper);
2639
2686
  this.randomness = randomness;
2640
2687
 
2641
2688
  if (!soundEnable) return;
2642
- if (!soundWaveDecoderContext)
2689
+ if (!soundDecoderContext)
2643
2690
  soundDecoderContext = new AudioContext;
2644
2691
 
2645
- fetch(waveFilename)
2692
+ fetch(filename)
2646
2693
  .then(response => response.arrayBuffer())
2647
- .then(arrayBuffer => soundWaveDecoderContext.decodeAudioData(arrayBuffer))
2648
- .then(audioBuffer => this.cachedSamples = audioBuffer.getChannelData(0));
2694
+ .then(arrayBuffer => soundDecoderContext.decodeAudioData(arrayBuffer))
2695
+ .then(audioBuffer =>
2696
+ {
2697
+ this.sampleChannels = [];
2698
+ for (let i = audioBuffer.numberOfChannels; i--;)
2699
+ this.sampleChannels[i] = audioBuffer.getChannelData(i);
2700
+ this.sampleRate = audioBuffer.sampleRate;
2701
+ });
2649
2702
  }
2650
2703
  }
2651
2704
  let soundDecoderContext; // audio context used only to decode audio files
@@ -2680,48 +2733,34 @@ let soundDecoderContext; // audio context used only to decode audio files
2680
2733
  * // play the music
2681
2734
  * music_example.play();
2682
2735
  */
2683
- class Music
2736
+ class Music extends Sound
2684
2737
  {
2685
2738
  /** Create a music object and cache the zzfx music samples for later use
2686
2739
  * @param {Array} zzfxMusic - Array of zzfx music parameters
2687
2740
  */
2688
2741
  constructor(zzfxMusic)
2689
2742
  {
2690
- if (!soundEnable) return;
2743
+ super();
2691
2744
 
2692
- this.cachedSamples = zzfxM(...zzfxMusic);
2745
+ if (!soundEnable) return;
2746
+ this.randomness = 0;
2747
+ this.sampleChannels = zzfxM(...zzfxMusic);
2748
+ this.sampleRate = zzfxR;
2693
2749
  }
2694
2750
 
2695
2751
  /** Play the music
2696
2752
  * @param {Number} [volume=1] - How much to scale volume by
2697
- * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
2698
- * @return {AudioBufferSourceNode} - The audio node, can be used to stop sound later
2753
+ * @param {Boolean} [loop=1] - True if the music should loop
2754
+ * @return {AudioBufferSourceNode} - The audio source node
2699
2755
  */
2700
2756
  play(volume, loop = 1)
2701
- {
2702
- if (!soundEnable) return;
2703
-
2704
- return this.source = playSamples(this.cachedSamples, volume, 1, 0, loop);
2705
- }
2706
-
2707
- /** Stop the music */
2708
- stop()
2709
- {
2710
- if (this.source)
2711
- this.source.stop();
2712
- this.source = 0;
2713
- }
2714
-
2715
- /** Check if music is playing
2716
- * @return {Boolean}
2717
- */
2718
- isPlaying() { return this.source; }
2757
+ { return super.play(0, volume, 1, 1, loop); }
2719
2758
  }
2720
2759
 
2721
- /** Play an mp3 or wav audio from a local file or url
2760
+ /** Play an mp3, ogg, or wav audio from a local file or url
2722
2761
  * @param {String} url - Location of sound file to play
2723
2762
  * @param {Number} [volume=1] - How much to scale volume by
2724
- * @param {Boolean} [loop=1] - True if the music should loop when it reaches the end
2763
+ * @param {Boolean} [loop=1] - True if the music should loop
2725
2764
  * @return {HTMLAudioElement} - The audio element for this sound
2726
2765
  * @memberof Audio */
2727
2766
  function playAudioFile(url, volume=1, loop=1)
@@ -2785,9 +2824,10 @@ let audioContext;
2785
2824
  * @param {Number} [rate=1] - The playback rate to use
2786
2825
  * @param {Number} [pan=0] - How much to apply stereo panning
2787
2826
  * @param {Boolean} [loop=0] - True if the sound should loop when it reaches the end
2827
+ * @param {Number} [sampleRate=44100] - Sample rate for the sound
2788
2828
  * @return {AudioBufferSourceNode} - The audio node of the sound played
2789
2829
  * @memberof Audio */
2790
- function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2830
+ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0, sampleRate=zzfxR)
2791
2831
  {
2792
2832
  if (!soundEnable) return;
2793
2833
 
@@ -2804,7 +2844,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
2804
2844
  }
2805
2845
 
2806
2846
  // create buffer and source
2807
- const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, zzfxR),
2847
+ const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, sampleRate),
2808
2848
  source = audioContext.createBufferSource();
2809
2849
 
2810
2850
  // copy samples to buffer and setup source
@@ -3063,7 +3103,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
3063
3103
  * @namespace TileCollision
3064
3104
  */
3065
3105
 
3066
- 'use strict';
3106
+
3067
3107
 
3068
3108
  /** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access
3069
3109
  * @type {Array}
@@ -3416,7 +3456,7 @@ constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1)
3416
3456
  * LittleJS Particle System
3417
3457
  */
3418
3458
 
3419
- 'use strict';
3459
+
3420
3460
 
3421
3461
  /**
3422
3462
  * Particle Emitter - Spawns particles with the given settings
@@ -3726,7 +3766,7 @@ class Particle extends EngineObject
3726
3766
  * @namespace Medals
3727
3767
  */
3728
3768
 
3729
- 'use strict';
3769
+
3730
3770
 
3731
3771
  /** List of all medals
3732
3772
  * @type {Array}
@@ -4050,7 +4090,7 @@ class Newgrounds
4050
4090
  * @namespace WebGL
4051
4091
  */
4052
4092
 
4053
- 'use strict';
4093
+
4054
4094
 
4055
4095
  /** The WebGL canvas which appears above the main canvas and below the overlay canvas
4056
4096
  * @type {HTMLCanvasElement}
@@ -4072,7 +4112,7 @@ let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBat
4072
4112
 
4073
4113
  ///////////////////////////////////////////////////////////////////////////////
4074
4114
 
4075
- // Init WebGL, called automatically by the engine
4115
+ // Initalize WebGL, called automatically by the engine
4076
4116
  function glInit()
4077
4117
  {
4078
4118
  // create the canvas and tile texture
@@ -4111,6 +4151,48 @@ function glInit()
4111
4151
  glBatchCount = 0;
4112
4152
  }
4113
4153
 
4154
+ // Setup render each frame, called automatically by engine
4155
+ function glPreRender()
4156
+ {
4157
+ // clear and set to same size as main canvas
4158
+ glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4159
+ glContext.clear(gl_COLOR_BUFFER_BIT);
4160
+
4161
+ // set up the shader
4162
+ glContext.useProgram(glShader);
4163
+ glContext.activeTexture(gl_TEXTURE0);
4164
+ glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
4165
+ glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
4166
+ glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
4167
+ glSetBlendMode();
4168
+
4169
+ // set vertex attributes
4170
+ let offset = 0;
4171
+ const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
4172
+ {
4173
+ const location = glContext.getAttribLocation(glShader, name);
4174
+ glContext.enableVertexAttribArray(location);
4175
+ glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
4176
+ offset += size*typeSize;
4177
+ }
4178
+ initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
4179
+ initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
4180
+ initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
4181
+ initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
4182
+
4183
+ // build the transform matrix
4184
+ const sx = 2 * cameraScale / mainCanvas.width;
4185
+ const sy = 2 * cameraScale / mainCanvas.height;
4186
+ glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
4187
+ new Float32Array([
4188
+ sx, 0, 0, 0,
4189
+ 0, sy, 0, 0,
4190
+ 1, 1, -1, 1,
4191
+ -1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
4192
+ ])
4193
+ );
4194
+ }
4195
+
4114
4196
  /** Set the WebGl blend mode, normally you should call setBlendMode instead
4115
4197
  * @param {Boolean} [additive=0]
4116
4198
  * @memberof WebGL */
@@ -4190,48 +4272,6 @@ function glCreateTexture(image)
4190
4272
  return texture;
4191
4273
  }
4192
4274
 
4193
- // called automatically by engine before render
4194
- function glPreRender()
4195
- {
4196
- // clear and set to same size as main canvas
4197
- glContext.viewport(0, 0, glCanvas.width = mainCanvas.width, glCanvas.height = mainCanvas.height);
4198
- glContext.clear(gl_COLOR_BUFFER_BIT);
4199
-
4200
- // set up the shader
4201
- glContext.useProgram(glShader);
4202
- glContext.activeTexture(gl_TEXTURE0);
4203
- glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
4204
- glContext.bindBuffer(gl_ARRAY_BUFFER, glArrayBuffer);
4205
- glContext.bufferData(gl_ARRAY_BUFFER, gl_VERTEX_BUFFER_SIZE, gl_DYNAMIC_DRAW);
4206
- glSetBlendMode();
4207
-
4208
- // set vertex attributes
4209
- let offset = 0;
4210
- const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
4211
- {
4212
- const location = glContext.getAttribLocation(glShader, name);
4213
- glContext.enableVertexAttribArray(location);
4214
- glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
4215
- offset += size*typeSize;
4216
- }
4217
- initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
4218
- initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
4219
- initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
4220
- initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
4221
-
4222
- // build the transform matrix
4223
- const sx = 2 * cameraScale / mainCanvas.width;
4224
- const sy = 2 * cameraScale / mainCanvas.height;
4225
- glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), 0,
4226
- new Float32Array([
4227
- sx, 0, 0, 0,
4228
- 0, sy, 0, 0,
4229
- 1, 1, -1, 1,
4230
- -1-sx*cameraPos.x, -1-sy*cameraPos.y, 0, 0
4231
- ])
4232
- );
4233
- }
4234
-
4235
4275
  /** Draw all sprites and clear out the buffer, called automatically by the system whenever necessary
4236
4276
  * @memberof WebGL */
4237
4277
  function glFlush()
@@ -4489,7 +4529,7 @@ gl_VERTEX_BUFFER_SIZE = gl_MAX_BATCH * gl_VERTEX_BYTE_STRIDE;
4489
4529
  * @namespace Engine
4490
4530
  */
4491
4531
 
4492
- 'use strict';
4532
+
4493
4533
 
4494
4534
  /** Name of engine
4495
4535
  * @type {String}
@@ -4501,7 +4541,7 @@ const engineName = 'LittleJS';
4501
4541
  * @type {String}
4502
4542
  * @default
4503
4543
  * @memberof Engine */
4504
- const engineVersion = '1.7.13';
4544
+ const engineVersion = '1.7.21';
4505
4545
 
4506
4546
  /** Frames per second to update objects
4507
4547
  * @type {Number}
@@ -4571,11 +4611,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4571
4611
  debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
4572
4612
 
4573
4613
  // setup html
4574
- const styleBody = 'margin:0;overflow:hidden;' + // fill the window
4575
- 'background:#000;' + // set background color
4576
- 'touch-action:none;' + // prevent mobile pinch to resize
4577
- 'user-select:none;' + // prevent mobile hold to select
4578
- '-webkit-user-select:none'; // compatibility for ios
4614
+ const styleBody =
4615
+ 'margin:0;overflow:hidden;' + // fill the window
4616
+ 'background:#000;' + // set background color
4617
+ 'touch-action:none;' + // prevent mobile pinch to resize
4618
+ 'user-select:none;' + // prevent mobile hold to select
4619
+ '-webkit-user-select:none;' + // compatibility for ios
4620
+ '-webkit-touch-callout:none'; // compatibility for ios
4579
4621
  document.body.style = styleBody;
4580
4622
  document.body.appendChild(mainCanvas = document.createElement('canvas'));
4581
4623
  mainContext = mainCanvas.getContext('2d');
@@ -4589,7 +4631,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
4589
4631
  overlayContext = overlayCanvas.getContext('2d');
4590
4632
 
4591
4633
  // set canvas style
4592
- const styleCanvas = 'position:absolute;' +
4634
+ const styleCanvas =
4635
+ 'position:absolute;' + // position canvas
4593
4636
  'top:50%;left:50%;transform:translate(-50%,-50%);' + // center the canvas
4594
4637
  (canvasPixelated?'image-rendering:pixelated':''); // set pixelated rendering
4595
4638
  (glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;