littlejsengine 1.14.8 → 1.14.10

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.
@@ -33,7 +33,7 @@ const engineName = 'LittleJS';
33
33
  * @type {string}
34
34
  * @default
35
35
  * @memberof Engine */
36
- const engineVersion = '1.14.8';
36
+ const engineVersion = '1.14.10';
37
37
 
38
38
  /** Frames per second to update
39
39
  * @type {number}
@@ -1932,6 +1932,12 @@ let touchGamepadEnable = false;
1932
1932
  * @memberof Settings */
1933
1933
  let touchGamepadAnalog = true;
1934
1934
 
1935
+ /** Number of buttons on touch gamepad
1936
+ * @type {number}
1937
+ * @default
1938
+ * @memberof Settings */
1939
+ let touchGamepadButtonCount = 4;
1940
+
1935
1941
  /** Size of virtual gamepad for touch devices in pixels
1936
1942
  * @type {number}
1937
1943
  * @default
@@ -3962,7 +3968,7 @@ function inputInit()
3962
3968
  function onMouseLeave()
3963
3969
  {
3964
3970
  // set mouse position and delta when leaving canvas
3965
- mousePosScreen = vec2(Infinity);
3971
+ mousePosScreen = vec2(-1);
3966
3972
  mouseDeltaScreen = vec2(0);
3967
3973
  }
3968
3974
  }
@@ -4017,10 +4023,12 @@ function gamepadsUpdate()
4017
4023
  const data = inputData[1] || (inputData[1] = []);
4018
4024
  for (let i=10; i--;)
4019
4025
  {
4020
- const j = i === 3 ? 2 : i === 2 ? 3 : i; // fix button locations
4021
- const wasDown = gamepadIsDown(j,0);
4022
- data[j] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
4026
+ const wasDown = gamepadIsDown(i,0);
4027
+ data[i] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
4023
4028
  }
4029
+
4030
+ // disable normal gamepads when touch gamepad is active
4031
+ return;
4024
4032
  }
4025
4033
 
4026
4034
  // return if gamepads are disabled or not supported
@@ -4095,6 +4103,15 @@ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
4095
4103
  // touch gamepad internal variables
4096
4104
  let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
4097
4105
 
4106
+ function touchGamepadButtonCenter()
4107
+ {
4108
+ // draw right face buttons
4109
+ let center = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
4110
+ if (touchGamepadButtonCount <= 2)
4111
+ center.x += touchGamepadSize/2;
4112
+ return center;
4113
+ }
4114
+
4098
4115
  // enable touch input mouse passthrough
4099
4116
  function touchInputInit()
4100
4117
  {
@@ -4142,8 +4159,8 @@ function touchInputInit()
4142
4159
  // set was touching
4143
4160
  wasTouching = touching;
4144
4161
 
4145
- // prevent default handling like copy and magnifier lens
4146
- if (inputPreventDefault && document.hasFocus()) // allow document to get focus
4162
+ // prevent default handling like copy, magnifier lens, and scrolling
4163
+ if (inputPreventDefault && document.hasFocus() && e.cancelable)
4147
4164
  e.preventDefault();
4148
4165
 
4149
4166
  // must return true so the document will get focus
@@ -4172,25 +4189,36 @@ function touchInputInit()
4172
4189
 
4173
4190
  // get center of left and right sides
4174
4191
  const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
4175
- const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
4192
+ const buttonCenter = touchGamepadButtonCenter();
4176
4193
  const startCenter = mainCanvasSize.scale(.5);
4177
4194
 
4178
4195
  // check each touch point
4179
4196
  for (const touch of e.touches)
4180
4197
  {
4181
4198
  const touchPos = mouseEventToScreen(vec2(touch.clientX, touch.clientY));
4182
- if (touchPos.distance(stickCenter) < touchGamepadSize)
4199
+ if (stickCenter.distance(touchPos) < touchGamepadSize)
4183
4200
  {
4184
4201
  // virtual analog stick
4185
4202
  touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
4186
4203
  }
4187
- else if (touchPos.distance(buttonCenter) < touchGamepadSize)
4204
+ else if (buttonCenter.distance(touchPos) < touchGamepadSize)
4188
4205
  {
4189
4206
  // virtual face buttons
4190
- const button = touchPos.subtract(buttonCenter).direction();
4191
- touchGamepadButtons[button] = 1;
4207
+ let button = buttonCenter.subtract(touchPos).direction();
4208
+ button = mod(button+2, 4);
4209
+ if (touchGamepadButtonCount === 1)
4210
+ button = 0;
4211
+ else if (touchGamepadButtonCount === 2)
4212
+ {
4213
+ const delta = buttonCenter.subtract(touchPos);
4214
+ button = -delta.x < delta.y ? 1 : 0;
4215
+ }
4216
+ // fix button locations (swap 2 and 3 to match gamepad layout)
4217
+ button = button === 3 ? 2 : button === 2 ? 3 : button;
4218
+ if (button < touchGamepadButtonCount)
4219
+ touchGamepadButtons[button] = 1;
4192
4220
  }
4193
- else if (touchPos.distance(startCenter) < touchGamepadSize && !wasTouching)
4221
+ else if (startCenter.distance(touchPos) < touchGamepadSize && !wasTouching)
4194
4222
  {
4195
4223
  // virtual start button in center
4196
4224
  touchGamepadButtons[9] = 1;
@@ -4221,11 +4249,10 @@ function touchGamepadRender()
4221
4249
  // draw left analog stick
4222
4250
  context.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
4223
4251
  context.beginPath();
4224
-
4225
- const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
4252
+ const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
4226
4253
  if (touchGamepadAnalog) // draw circle shaped gamepad
4227
4254
  {
4228
- context.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
4255
+ context.arc(stickCenter.x, stickCenter.y, touchGamepadSize/2, 0, 9);
4229
4256
  context.fill();
4230
4257
  context.stroke();
4231
4258
  }
@@ -4234,21 +4261,27 @@ function touchGamepadRender()
4234
4261
  for (let i=10; i--;)
4235
4262
  {
4236
4263
  const angle = i*PI/4;
4237
- context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
4238
- i%2 && context.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
4264
+ context.arc(stickCenter.x, stickCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
4265
+ i%2 && context.arc(stickCenter.x, stickCenter.y, touchGamepadSize*.33, angle, angle);
4239
4266
  i===1 && context.fill();
4240
4267
  }
4241
4268
  context.stroke();
4242
4269
  }
4243
4270
 
4244
4271
  // draw right face buttons
4245
- const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
4246
- for (let i=4; i--;)
4247
- {
4248
- const pos = rightCenter.add(vec2().setDirection(i, touchGamepadSize/2));
4249
- context.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
4272
+ const buttonCenter = touchGamepadButtonCenter();
4273
+ const buttonSize = touchGamepadButtonCount > 1 ? touchGamepadSize/4 : touchGamepadSize/2;
4274
+ for (let i=0; i<touchGamepadButtonCount; i++)
4275
+ {
4276
+ let j = mod(i-1, 4);
4277
+ let button = touchGamepadButtonCount > 2 ?
4278
+ j : min(j, touchGamepadButtonCount-1);
4279
+ // fix button locations (swap 2 and 3 to match gamepad layout)
4280
+ button = button === 3 ? 2 : button === 2 ? 3 : button;
4281
+ const pos = buttonCenter.add(vec2().setDirection(j, touchGamepadSize/2));
4282
+ context.fillStyle = touchGamepadButtons[button] ? '#fff' : '#000';
4250
4283
  context.beginPath();
4251
- context.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
4284
+ context.arc(pos.x, pos.y, buttonSize, 0,9);
4252
4285
  context.fill();
4253
4286
  context.stroke();
4254
4287
  }
@@ -7604,7 +7637,7 @@ class UIObject
7604
7637
  const mouseDown = mouseIsDown(0);
7605
7638
  const mousePress = this.dragActivate ? mouseDown : mouseWasPressed(0);
7606
7639
  if (!uiSystem.hoverObject)
7607
- if (mousePress || !mouseDown || isActive)
7640
+ if (mousePress || isActive || (!mouseDown && !isTouchDevice))
7608
7641
  {
7609
7642
  const size = this.size.add(vec2(isTouchDevice && this.extraTouchSize || 0));
7610
7643
  if (isOverlapping(this.pos, size, mousePosScreen))
@@ -7636,7 +7669,8 @@ class UIObject
7636
7669
  }
7637
7670
  }
7638
7671
  }
7639
- if (isActive && (!mouseDown || !this.isHoverObject()))
7672
+ if (isActive)
7673
+ if (!mouseDown || (this.dragActivate && !this.isHoverObject()))
7640
7674
  {
7641
7675
  this.onRelease();
7642
7676
  if (this.soundRelease)
@@ -36,7 +36,11 @@ export const persistentParticleDestroyCallback = (particle)=>
36
36
  // copy particle to tile layer on death
37
37
  LJS.ASSERT(!particle.tileInfo, 'quick draw to tile layer uses canvas 2d so must be untextured');
38
38
  if (particle.groundObject)
39
+ {
39
40
  GameLevel.foregroundTileLayer.drawTile(particle.pos, particle.size, particle.tileInfo, particle.color, particle.angle, particle.mirror);
41
+ // update WebGL texture
42
+ GameLevel.foregroundTileLayer.useWebGL();
43
+ }
40
44
  }
41
45
 
42
46
  export function makeBlood(pos, amount) { makeDebris(pos, hsl(0,1,.5), amount, .1, 0); }
@@ -83,7 +87,7 @@ export function explosion(pos, radius=3)
83
87
  }
84
88
 
85
89
  // update WebGL texture
86
- GameLevel.updateWebGL(GameLevel.foregroundTileLayer);
90
+ GameLevel.foregroundTileLayer.useWebGL();
87
91
 
88
92
  // kill/push objects
89
93
  LJS.engineObjectsCallback(pos, radius*3, (o)=>
@@ -159,7 +163,7 @@ export function destroyTile(pos, makeSound = 1, cleanup = 1)
159
163
  for (let i=-1;i<=1;++i)
160
164
  for (let j=-1;j<=1;++j)
161
165
  GameLevel.decorateTile(pos.add(vec2(i,j)));
162
- GameLevel.updateWebGL(layer);
166
+ layer.useWebGL();
163
167
  }
164
168
 
165
169
  return true;
@@ -14,7 +14,6 @@ import * as GameEffects from './gameEffects.js';
14
14
  import * as Game from './game.js';
15
15
  const {vec2, hsl, tile} = LJS;
16
16
 
17
- export const useWebGLTileLayers = true;
18
17
  export const tileType_ladder = -1;
19
18
  export const tileType_empty = 0;
20
19
  export const tileType_solid = 1;
@@ -138,7 +137,7 @@ function loadLevelData()
138
137
  for (pos.x=levelSize.x; pos.x--;)
139
138
  for (pos.y=levelSize.y; pos.y--;)
140
139
  decorateTile(pos, layer);
141
- updateWebGL(tileLayers[layer]);
140
+ tileLayers[layer].useWebGL();
142
141
  }
143
142
  }
144
143
 
@@ -204,9 +203,4 @@ export function getCameraTarget()
204
203
  // camera is above player
205
204
  const offset = 100/LJS.cameraScale*LJS.percent(LJS.mainCanvasSize.y, 300, 600);
206
205
  return Game.player.pos.add(vec2(0, offset));
207
- }
208
-
209
- export function updateWebGL(layer)
210
- {
211
- layer.useWebGL(useWebGLTileLayers);
212
206
  }
@@ -1,4 +1,4 @@
1
- // use smoother textures
1
+ // use smooth blending
2
2
  setCanvasPixelated(false);
3
3
  setTilesPixelated(false);
4
4
 
@@ -38,7 +38,8 @@ class CarObject extends Box2dObject
38
38
 
39
39
  // car controls - use mouse, arrow keys, or A/D to drive
40
40
  const maxSpeed = 40;
41
- const input = keyDirection().x || mouseIsDown(0);
41
+ const input = mouseIsDown(0) ? 1 :
42
+ mouseIsDown(2) ? -1 : keyDirection().x ;
42
43
  let s = this.wheels[0].motorJoint.getMotorSpeed();
43
44
  s = input ? clamp(s - input, -maxSpeed, maxSpeed) : 0;
44
45
  this.wheels[0].motorJoint.setMotorSpeed(s);
@@ -9,20 +9,20 @@ function gameInit()
9
9
  tile(0), // tileInfo
10
10
  rgb(1,.5,.1), rgb(1,.1,.1), // colorStartA, colorStartB
11
11
  rgb(1,.5,.1,0), rgb(1,.1,.1,0), // colorEndA, colorEndB
12
- .7, 2, 0, .2, .05, // time, sizeStart, sizeEnd, speed, angleSpeed
13
- .9, 1, -1, PI, .05, // damp, angleDamp, gravity, particleCone, fade
14
- .5, 0, 1, 0, 1e9 // randomness, collide, additive, colorLinear, renderOrder
12
+ .7, 2, 0, .2, .05, // time, sizeStart, sizeEnd, speed, angleSpeed
13
+ .9, 1, -1, PI, .05, // damp, angleDamp, gravity, particleCone, fade
14
+ .5, 0, 1, 0 // randomness, collide, additive, colorLinear
15
15
  );
16
16
 
17
17
  // smoke
18
18
  new ParticleEmitter(
19
- vec2(5,-2), 0, // pos, angle
20
- 3, 0, 100, PI, // emitSize, emitTime, emitRate, emitCone
21
- tile(0), // tileInfo
22
- hsl(0,0,0,.5), hsl(0,0,1,.5), // colorStartA, colorStartB
19
+ vec2(-5,-2), 0, // pos, angle
20
+ 3, 0, 100, PI, // emitSize, emitTime, emitRate, emitCone
21
+ tile(0), // tileInfo
22
+ hsl(0,0,0,.5), hsl(0,0,1,.5), // colorStartA, colorStartB
23
23
  hsl(0,0,0,0), hsl(0,0,1,0), // colorEndA, colorEndB
24
- 1, 1, 5, .2, .01, // time, sizeStart, sizeEnd, speed, angleSpeed
25
- .85, 1, -1, PI, .3, // damp, angleDamp, gravity, particleCone, fade
26
- .5, 0, 0, 1, 1e8 // randomness, collide, additive, colorLinear, renderOrder
24
+ 1, 1, 5, .2, .01, // time, sizeStart, sizeEnd, speed, angleSpeed
25
+ .85, 1, -1, PI, .3, // damp, angleDamp, gravity, particleCone, fade
26
+ .5, 0, 0, 1 // randomness, collide, additive, colorLinear
27
27
  );
28
28
  }
@@ -1,4 +1,3 @@
1
- // globals objects
2
1
  let paddle, ball;
3
2
 
4
3
  class PhysicsObject extends EngineObject
@@ -1,7 +1,3 @@
1
- // globals objects
2
- const sound_ui = new Sound([1,0]);
3
- let uiMenu;
4
-
5
1
  function gameInit()
6
2
  {
7
3
  // setup ui system plugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlejsengine",
3
- "version": "1.14.8",
3
+ "version": "1.14.10",
4
4
  "description": "LittleJS - Tiny and Fast HTML5 Game Engine",
5
5
  "main": "dist/littlejs.esm.js",
6
6
  "types": "dist/littlejs.d.ts",
@@ -284,7 +284,7 @@ class UIObject
284
284
  const mouseDown = mouseIsDown(0);
285
285
  const mousePress = this.dragActivate ? mouseDown : mouseWasPressed(0);
286
286
  if (!uiSystem.hoverObject)
287
- if (mousePress || !mouseDown || isActive)
287
+ if (mousePress || isActive || (!mouseDown && !isTouchDevice))
288
288
  {
289
289
  const size = this.size.add(vec2(isTouchDevice && this.extraTouchSize || 0));
290
290
  if (isOverlapping(this.pos, size, mousePosScreen))
@@ -316,7 +316,8 @@ class UIObject
316
316
  }
317
317
  }
318
318
  }
319
- if (isActive && (!mouseDown || !this.isHoverObject()))
319
+ if (isActive)
320
+ if (!mouseDown || (this.dragActivate && !this.isHoverObject()))
320
321
  {
321
322
  this.onRelease();
322
323
  if (this.soundRelease)
package/src/engine.js CHANGED
@@ -30,7 +30,7 @@ const engineName = 'LittleJS';
30
30
  * @type {string}
31
31
  * @default
32
32
  * @memberof Engine */
33
- const engineVersion = '1.14.8';
33
+ const engineVersion = '1.14.10';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {number}
@@ -455,7 +455,8 @@ function debugRender()
455
455
  {
456
456
  overlayContext.fillText(`${engineName} v${engineVersion}`, x, y += h/2 );
457
457
  overlayContext.fillText('Time: ' + formatTime(time), x, y += h);
458
- overlayContext.fillText('FPS: ' + averageFPS.toFixed(1), x, y += h);
458
+ overlayContext.fillText('FPS: ' + averageFPS.toFixed(1) + (glEnable?' WebGL':' Canvas2D'),
459
+ x, y += h);
459
460
  overlayContext.fillText('Objects: ' + engineObjects.length, x, y += h);
460
461
  overlayContext.fillText('Draw Count: ' + drawCount, x, y += h);
461
462
  overlayContext.fillText('---------', x, y += h);
@@ -474,12 +475,18 @@ function debugRender()
474
475
  overlayContext.fillText('6: Toggle Video Capture', x, y += h);
475
476
 
476
477
  let keysPressed = '';
478
+ let mousePressed = '';
477
479
  for (const i in inputData[0])
478
480
  {
479
- if (keyIsDown(i, 0))
481
+ if (!keyIsDown(i, 0))
482
+ continue;
483
+ if (parseInt(i) < 3)
484
+ mousePressed += i + ' ' ;
485
+ else if (keyIsDown(i, 0))
480
486
  keysPressed += i + ' ' ;
481
487
  }
482
- keysPressed && overlayContext.fillText('Keys Down: ' + keysPressed, x, y += h);
488
+ mousePressed && overlayContext.fillText('Mouse: ' + mousePressed, x, y += h);
489
+ keysPressed && overlayContext.fillText('Keys: ' + keysPressed, x, y += h);
483
490
 
484
491
  let buttonsPressed = '';
485
492
  if (inputData[1])
@@ -278,7 +278,7 @@ function inputInit()
278
278
  function onMouseLeave()
279
279
  {
280
280
  // set mouse position and delta when leaving canvas
281
- mousePosScreen = vec2(Infinity);
281
+ mousePosScreen = vec2(-1);
282
282
  mouseDeltaScreen = vec2(0);
283
283
  }
284
284
  }
@@ -333,10 +333,12 @@ function gamepadsUpdate()
333
333
  const data = inputData[1] || (inputData[1] = []);
334
334
  for (let i=10; i--;)
335
335
  {
336
- const j = i === 3 ? 2 : i === 2 ? 3 : i; // fix button locations
337
- const wasDown = gamepadIsDown(j,0);
338
- data[j] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
336
+ const wasDown = gamepadIsDown(i,0);
337
+ data[i] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
339
338
  }
339
+
340
+ // disable normal gamepads when touch gamepad is active
341
+ return;
340
342
  }
341
343
 
342
344
  // return if gamepads are disabled or not supported
@@ -411,6 +413,15 @@ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
411
413
  // touch gamepad internal variables
412
414
  let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
413
415
 
416
+ function touchGamepadButtonCenter()
417
+ {
418
+ // draw right face buttons
419
+ let center = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
420
+ if (touchGamepadButtonCount <= 2)
421
+ center.x += touchGamepadSize/2;
422
+ return center;
423
+ }
424
+
414
425
  // enable touch input mouse passthrough
415
426
  function touchInputInit()
416
427
  {
@@ -458,8 +469,8 @@ function touchInputInit()
458
469
  // set was touching
459
470
  wasTouching = touching;
460
471
 
461
- // prevent default handling like copy and magnifier lens
462
- if (inputPreventDefault && document.hasFocus()) // allow document to get focus
472
+ // prevent default handling like copy, magnifier lens, and scrolling
473
+ if (inputPreventDefault && document.hasFocus() && e.cancelable)
463
474
  e.preventDefault();
464
475
 
465
476
  // must return true so the document will get focus
@@ -488,25 +499,36 @@ function touchInputInit()
488
499
 
489
500
  // get center of left and right sides
490
501
  const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
491
- const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
502
+ const buttonCenter = touchGamepadButtonCenter();
492
503
  const startCenter = mainCanvasSize.scale(.5);
493
504
 
494
505
  // check each touch point
495
506
  for (const touch of e.touches)
496
507
  {
497
508
  const touchPos = mouseEventToScreen(vec2(touch.clientX, touch.clientY));
498
- if (touchPos.distance(stickCenter) < touchGamepadSize)
509
+ if (stickCenter.distance(touchPos) < touchGamepadSize)
499
510
  {
500
511
  // virtual analog stick
501
512
  touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
502
513
  }
503
- else if (touchPos.distance(buttonCenter) < touchGamepadSize)
514
+ else if (buttonCenter.distance(touchPos) < touchGamepadSize)
504
515
  {
505
516
  // virtual face buttons
506
- const button = touchPos.subtract(buttonCenter).direction();
507
- touchGamepadButtons[button] = 1;
517
+ let button = buttonCenter.subtract(touchPos).direction();
518
+ button = mod(button+2, 4);
519
+ if (touchGamepadButtonCount === 1)
520
+ button = 0;
521
+ else if (touchGamepadButtonCount === 2)
522
+ {
523
+ const delta = buttonCenter.subtract(touchPos);
524
+ button = -delta.x < delta.y ? 1 : 0;
525
+ }
526
+ // fix button locations (swap 2 and 3 to match gamepad layout)
527
+ button = button === 3 ? 2 : button === 2 ? 3 : button;
528
+ if (button < touchGamepadButtonCount)
529
+ touchGamepadButtons[button] = 1;
508
530
  }
509
- else if (touchPos.distance(startCenter) < touchGamepadSize && !wasTouching)
531
+ else if (startCenter.distance(touchPos) < touchGamepadSize && !wasTouching)
510
532
  {
511
533
  // virtual start button in center
512
534
  touchGamepadButtons[9] = 1;
@@ -537,11 +559,10 @@ function touchGamepadRender()
537
559
  // draw left analog stick
538
560
  context.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
539
561
  context.beginPath();
540
-
541
- const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
562
+ const stickCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
542
563
  if (touchGamepadAnalog) // draw circle shaped gamepad
543
564
  {
544
- context.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
565
+ context.arc(stickCenter.x, stickCenter.y, touchGamepadSize/2, 0, 9);
545
566
  context.fill();
546
567
  context.stroke();
547
568
  }
@@ -550,21 +571,27 @@ function touchGamepadRender()
550
571
  for (let i=10; i--;)
551
572
  {
552
573
  const angle = i*PI/4;
553
- context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
554
- i%2 && context.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
574
+ context.arc(stickCenter.x, stickCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
575
+ i%2 && context.arc(stickCenter.x, stickCenter.y, touchGamepadSize*.33, angle, angle);
555
576
  i===1 && context.fill();
556
577
  }
557
578
  context.stroke();
558
579
  }
559
580
 
560
581
  // draw right face buttons
561
- const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
562
- for (let i=4; i--;)
582
+ const buttonCenter = touchGamepadButtonCenter();
583
+ const buttonSize = touchGamepadButtonCount > 1 ? touchGamepadSize/4 : touchGamepadSize/2;
584
+ for (let i=0; i<touchGamepadButtonCount; i++)
563
585
  {
564
- const pos = rightCenter.add(vec2().setDirection(i, touchGamepadSize/2));
565
- context.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
586
+ let j = mod(i-1, 4);
587
+ let button = touchGamepadButtonCount > 2 ?
588
+ j : min(j, touchGamepadButtonCount-1);
589
+ // fix button locations (swap 2 and 3 to match gamepad layout)
590
+ button = button === 3 ? 2 : button === 2 ? 3 : button;
591
+ const pos = buttonCenter.add(vec2().setDirection(j, touchGamepadSize/2));
592
+ context.fillStyle = touchGamepadButtons[button] ? '#fff' : '#000';
566
593
  context.beginPath();
567
- context.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
594
+ context.arc(pos.x, pos.y, buttonSize, 0,9);
568
595
  context.fill();
569
596
  context.stroke();
570
597
  }
@@ -222,6 +222,12 @@ let touchGamepadEnable = false;
222
222
  * @memberof Settings */
223
223
  let touchGamepadAnalog = true;
224
224
 
225
+ /** Number of buttons on touch gamepad
226
+ * @type {number}
227
+ * @default
228
+ * @memberof Settings */
229
+ let touchGamepadButtonCount = 4;
230
+
225
231
  /** Size of virtual gamepad for touch devices in pixels
226
232
  * @type {number}
227
233
  * @default