littlejsengine 1.14.9 → 1.14.11

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.
@@ -48,7 +48,6 @@ function gameInit()
48
48
 
49
49
  // stop button
50
50
  stopButton = new UIButton(vec2(90, 30), vec2(140, 50), 'Stop');
51
- musicPlayer.addChild(stopButton);
52
51
  stopButton.onClick = ()=>
53
52
  {
54
53
  if (!musicInstance)
@@ -57,10 +56,11 @@ function gameInit()
57
56
  // stop sound
58
57
  musicInstance.stop();
59
58
  };
59
+ musicPlayer.addChild(stopButton);
60
60
 
61
61
  // progress bar and scrollbar for seeking
62
62
  progressBar = new UIScrollbar(vec2(0, 100), vec2(400, 30), 0, 'Progress');
63
- musicPlayer.addChild(progressBar);
63
+ progressBar.disabledColor = RED;
64
64
  progressBar.onChange = ()=>
65
65
  {
66
66
  // control music seek position
@@ -73,6 +73,7 @@ function gameInit()
73
73
  if (!wasPlaying)
74
74
  musicInstance.pause();
75
75
  };
76
+ musicPlayer.addChild(progressBar);
76
77
  }
77
78
 
78
79
  function gameUpdate()
@@ -4,25 +4,25 @@ function gameInit()
4
4
 
5
5
  // fire
6
6
  new ParticleEmitter(
7
- vec2(-5,-2), 0, // pos, angle
7
+ vec2(-5,-2), 0, // pos, angle
8
8
  2, 0, 200, PI, // emitSize, emitTime, emitRate, emitCone
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
@@ -31,7 +31,10 @@ function gameUpdate()
31
31
  playerAngle += mouseDelta.x * .003;
32
32
 
33
33
  // update player movement, prevent walking through walls
34
- const velocity = keyDirection().scale(.1).rotate(playerAngle);
34
+ let velocity = keyDirection();
35
+ if (velocity.y && mouseIsDown(0))
36
+ velocity.y = 1; // forward movement with mouse
37
+ velocity = velocity.rotate(playerAngle).scale(.1);
35
38
  const newPos = playerPos.add(velocity);
36
39
  if (!levelTest(newPos.x, newPos.y))
37
40
  playerPos = newPos;
@@ -6,27 +6,35 @@ class PuzzlePiece extends EngineObject
6
6
  {
7
7
  constructor(gridPos, size)
8
8
  {
9
- const color = rgb(gridPos.x/(gridSize.x-1), gridPos.y/(gridSize.y-1), 1);
9
+ const x = gridPos.x, y = gridPos.y;
10
+ const color = rgb(x/(gridSize.x-1), y/(gridSize.y-1), 1);
10
11
  super(gridPos.multiply(size), size, 0, 0, color);
11
12
  this.gridPos = gridPos;
12
- this.text = gridPos.x + gridPos.y*gridSize.x;
13
+ this.text = x + y*gridSize.x;
14
+ this.moveTimer = new Timer;
15
+ this.lastPos = this.pos;
13
16
  }
14
17
 
15
18
  update()
16
19
  {
20
+ const deltaX = emptyGridPos.x - this.gridPos.x;
21
+ const deltaY = emptyGridPos.y - this.gridPos.y;
17
22
  if (mouseWasPressed(0) && isOverlapping(this.pos, this.size, mousePos))
18
- if (abs(this.gridPos.x-emptyGridPos.x) + abs(this.gridPos.y-emptyGridPos.y) == 1)
23
+ if (abs(deltaX) + abs(deltaY) === 1)
19
24
  {
20
25
  // swap with empty space when clicked on
26
+ this.lastPos = this.pos;
21
27
  this.pos = emptyGridPos.multiply(this.size);
22
28
  [emptyGridPos, this.gridPos] = [this.gridPos, emptyGridPos];
29
+ this.moveTimer.set(.2);
23
30
  }
24
31
  }
25
32
 
26
33
  render()
27
34
  {
28
- super.render();
29
- drawTextOverlay(this.text, this.pos, 2.5, BLACK);
35
+ const pos = this.lastPos.lerp(this.pos, this.moveTimer.getPercent());
36
+ drawRect(pos, this.size, this.color);
37
+ drawTextOverlay(this.text, pos, 2.5, BLACK);
30
38
  }
31
39
  }
32
40
 
@@ -1,7 +1,7 @@
1
1
  function gameRender()
2
2
  {
3
3
  // precreate variables to avoid overhead
4
- const pos = vec2(), size = vec2(), color = WHITE;
4
+ const pos = vec2(), size = vec2(), color = new Color;
5
5
  for (let i=2e3; i--;)
6
6
  {
7
7
  // use math to generate random star positions
@@ -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
@@ -41,14 +41,14 @@ function createUI()
41
41
  uiMenu.addChild(uiBackground);
42
42
 
43
43
  // example large text
44
- const textTitle = new LJS.UIText(vec2(0,-220), vec2(500, 90), 'Test Title');
44
+ const textTitle = new LJS.UIText(vec2(0,-220), vec2(500, 120), 'Test Title');
45
45
  uiMenu.addChild(textTitle);
46
46
  textTitle.textColor = LJS.RED;
47
47
  textTitle.lineWidth = 4;
48
48
  textTitle.lineColor = LJS.BLUE;
49
49
 
50
50
  // example multiline text
51
- const textTest = new LJS.UIText(vec2(-60,-120), vec2(300, 50), 'Test Text\nSecond text line.')
51
+ const textTest = new LJS.UIText(vec2(-60,-120), vec2(350, 60), 'Test Text\nSecond text line.')
52
52
  uiMenu.addChild(textTest);
53
53
 
54
54
  // example tile image
@@ -63,7 +63,7 @@ function createUI()
63
63
  // example button
64
64
  const button1 = new LJS.UIButton(vec2(0,140), vec2(350, 50), 'Test Button');
65
65
  uiMenu.addChild(button1);
66
- button1.onClick = ()=> console.log('Button 1 clicked');
66
+ button1.onClick = ()=> LOG('Button 1 clicked');
67
67
 
68
68
  // example checkbox
69
69
  const checkbox = new LJS.UICheckbox(vec2(-140,-20), vec2(40));
@@ -71,7 +71,7 @@ function createUI()
71
71
  checkbox.onChange = ()=> button1.disabled = checkbox.checked;
72
72
 
73
73
  // text attached to checkbox
74
- const checkboxText = new LJS.UIText(vec2(170,0), vec2(300, 40), 'Test Checkbox');
74
+ const checkboxText = new LJS.UIText(vec2(170,0), vec2(300, 60), 'Test Checkbox');
75
75
  checkbox.addChild(checkboxText);
76
76
 
77
77
  // exit button
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlejsengine",
3
- "version": "1.14.9",
3
+ "version": "1.14.11",
4
4
  "description": "LittleJS - Tiny and Fast HTML5 Game Engine",
5
5
  "main": "dist/littlejs.esm.js",
6
6
  "types": "dist/littlejs.d.ts",
package/plugins/box2d.js CHANGED
@@ -1715,7 +1715,7 @@ async function box2dInit()
1715
1715
  }
1716
1716
  function box2dRender()
1717
1717
  {
1718
- if (box2dDebug || debugPhysics && debugOverlay)
1718
+ if (box2dDebug || debugPhysics)
1719
1719
  box2d.world.DrawDebugData();
1720
1720
  }
1721
1721
 
@@ -76,7 +76,7 @@ class NewgroundsPlugin
76
76
  // get medals
77
77
  const medalsResult = this.call('Medal.getList');
78
78
  this.medals = medalsResult ? medalsResult.result.data['medals'] : [];
79
- debugMedals && console.log(this.medals);
79
+ debugMedals && LOG(this.medals);
80
80
  for (const newgroundsMedal of this.medals)
81
81
  {
82
82
  const medal = medals[newgroundsMedal['id']];
@@ -99,7 +99,7 @@ class NewgroundsPlugin
99
99
  // get scoreboards
100
100
  const scoreboardResult = this.call('ScoreBoard.getBoards');
101
101
  this.scoreboards = scoreboardResult ? scoreboardResult.result.data.scoreboards : [];
102
- debugMedals && console.log(this.scoreboards);
102
+ debugMedals && LOG(this.scoreboards);
103
103
 
104
104
  // keep the session alive with a ping every minute
105
105
  const keepAliveMS = 60 * 1e3;
@@ -168,10 +168,10 @@ class NewgroundsPlugin
168
168
  try { xmlHttp.send(formData); }
169
169
  catch(e)
170
170
  {
171
- debugMedals && console.log('newgrounds call failed', e);
171
+ debugMedals && LOG('newgrounds call failed', e);
172
172
  return;
173
173
  }
174
- debugMedals && console.log(xmlHttp.responseText);
174
+ debugMedals && LOG(xmlHttp.responseText);
175
175
  return xmlHttp.responseText && JSON.parse(xmlHttp.responseText);
176
176
  }
177
177
  }
@@ -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))
package/reference.md CHANGED
@@ -77,7 +77,6 @@ Vector2.normalize(length=1) // Normalize this vector to length
77
77
  Vector2.clampLength(length=1) // Clamp this vector to length
78
78
  Vector2.dot(v) // Dot product with vector
79
79
  Vector2.cross(v) // Cross product with vector
80
- Vector2.invert() // Invert this vector
81
80
  Vector2.floor() // Floor this vector
82
81
  Vector2.area() // Get area covered by this vector as a rectangle
83
82
  Vector2.lerp(v, percent) // Interpolate between vectors
@@ -442,7 +441,6 @@ debug // Is debug enabled?
442
441
  debugPointSize = .5 // Size to render debug points by default
443
442
  debugKey = 'Escape' // Key code used to toggle debug mode
444
443
  debugOverlay // True if the debug overlay is active
445
- enableAsserts // True if asserts are enabled
446
444
  showWatermark // True if watermark with FPS should be show
447
445
  ```
448
446
 
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.9';
33
+ const engineVersion = '1.14.11';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {number}
@@ -119,12 +119,12 @@ function engineAddPlugin(updateFunction, renderFunction)
119
119
  * @example
120
120
  * // Basic engine startup
121
121
  * engineInit(
122
- * () => { console.log('Game initialized!'); }, // gameInit
123
- * () => { updateGameLogic(); }, // gameUpdate
124
- * () => { updateUI(); }, // gameUpdatePost
125
- * () => { drawBackground(); }, // gameRender
126
- * () => { drawHUD(); }, // gameRenderPost
127
- * ['tiles.png', 'tilesLevel.png'] // images to load
122
+ * () => { LOG('Game initialized!'); }, // gameInit
123
+ * () => { updateGameLogic(); }, // gameUpdate
124
+ * () => { updateUI(); }, // gameUpdatePost
125
+ * () => { drawBackground(); }, // gameRender
126
+ * () => { drawHUD(); }, // gameRenderPost
127
+ * ['tiles.png', 'tilesLevel.png'] // images to load
128
128
  * );
129
129
  * @memberof Engine */
130
130
  async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
@@ -170,16 +170,20 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
170
170
  if (!debugSpeedUp)
171
171
  frameTimeBufferMS = min(frameTimeBufferMS, 50); // clamp min framerate
172
172
 
173
+ let wasUpdated = false;
173
174
  if (paused)
174
175
  {
176
+ // update everything except the game and objects
177
+ wasUpdated = true;
175
178
  updateCanvas();
179
+ inputUpdate();
180
+ pluginUpdateList.forEach(f=>f());
176
181
 
177
182
  // update object transforms even when paused
178
183
  for (const o of engineObjects)
179
184
  o.parent || o.updateTransforms();
180
185
 
181
- inputUpdate();
182
- pluginUpdateList.forEach(f=>f());
186
+ // do post update
183
187
  debugUpdate();
184
188
  gameUpdatePost();
185
189
  inputUpdatePost();
@@ -196,12 +200,13 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
196
200
  }
197
201
 
198
202
  // update multiple frames if necessary in case of slow framerate
199
- for (;frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
203
+ for (; frameTimeBufferMS >= 0; frameTimeBufferMS -= 1e3 / frameRate)
200
204
  {
201
205
  // increment frame and update time
202
206
  time = frame++ / frameRate;
203
207
 
204
208
  // update game and objects
209
+ wasUpdated = true;
205
210
  updateCanvas();
206
211
  inputUpdate();
207
212
  gameUpdate();
@@ -212,7 +217,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
212
217
  debugUpdate();
213
218
  gameUpdatePost();
214
219
  inputUpdatePost();
215
-
216
220
  if (debugVideoCaptureIsActive())
217
221
  renderFrame();
218
222
  }
@@ -229,6 +233,10 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
229
233
  {
230
234
  if (headlessMode) return;
231
235
 
236
+ // canvas must be updated before rendering
237
+ if (!wasUpdated)
238
+ updateCanvas();
239
+
232
240
  // render sort then render while removing destroyed objects
233
241
  enginePreRender();
234
242
  gameRender();
@@ -388,7 +396,7 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
388
396
  promises.push(new Promise(resolve =>
389
397
  {
390
398
  let t = 0;
391
- console.log(`${engineName} Engine v${engineVersion}`);
399
+ LOG(`${engineName} Engine v${engineVersion}`);
392
400
  updateSplash();
393
401
  function updateSplash()
394
402
  {
@@ -16,12 +16,6 @@
16
16
  * @memberof Debug */
17
17
  const debug = true;
18
18
 
19
- /** True if asserts are enabled
20
- * @type {boolean}
21
- * @default
22
- * @memberof Debug */
23
- const enableAsserts = true;
24
-
25
19
  /** Size to render debug points by default
26
20
  * @type {number}
27
21
  * @default
@@ -52,15 +46,16 @@ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParti
52
46
  ///////////////////////////////////////////////////////////////////////////////
53
47
  // Debug helper functions
54
48
 
55
- /** Asserts if the expression is false, does not do anything in release builds
49
+ /** Asserts if the expression is false, does nothing in release builds
56
50
  * @param {boolean} assert
57
51
  * @param {...Object} [output] - error message output
58
52
  * @memberof Debug */
59
- function ASSERT(assert, ...output)
60
- {
61
- if (enableAsserts)
62
- console.assert(assert, ...output);
63
- }
53
+ function ASSERT(assert, ...output) { console.assert(assert, ...output); }
54
+
55
+ /** Log to console if debug is enabled, does nothing in release builds
56
+ * @param {...Object} [output] - message output
57
+ * @memberof Debug */
58
+ function LOG(...output) { console.log(...output); }
64
59
 
65
60
  /** Draw a debug rectangle in world space
66
61
  * @param {Vector2} pos
@@ -455,7 +450,8 @@ function debugRender()
455
450
  {
456
451
  overlayContext.fillText(`${engineName} v${engineVersion}`, x, y += h/2 );
457
452
  overlayContext.fillText('Time: ' + formatTime(time), x, y += h);
458
- overlayContext.fillText('FPS: ' + averageFPS.toFixed(1), x, y += h);
453
+ overlayContext.fillText('FPS: ' + averageFPS.toFixed(1) + (glEnable?' WebGL':' Canvas2D'),
454
+ x, y += h);
459
455
  overlayContext.fillText('Objects: ' + engineObjects.length, x, y += h);
460
456
  overlayContext.fillText('Draw Count: ' + drawCount, x, y += h);
461
457
  overlayContext.fillText('---------', x, y += h);
@@ -474,12 +470,18 @@ function debugRender()
474
470
  overlayContext.fillText('6: Toggle Video Capture', x, y += h);
475
471
 
476
472
  let keysPressed = '';
473
+ let mousePressed = '';
477
474
  for (const i in inputData[0])
478
475
  {
479
- if (keyIsDown(i, 0))
476
+ if (!keyIsDown(i, 0))
477
+ continue;
478
+ if (parseInt(i) < 3)
479
+ mousePressed += i + ' ' ;
480
+ else if (keyIsDown(i, 0))
480
481
  keysPressed += i + ' ' ;
481
482
  }
482
- keysPressed && overlayContext.fillText('Keys Down: ' + keysPressed, x, y += h);
483
+ mousePressed && overlayContext.fillText('Mouse: ' + mousePressed, x, y += h);
484
+ keysPressed && overlayContext.fillText('Keys: ' + keysPressed, x, y += h);
483
485
 
484
486
  let buttonsPressed = '';
485
487
  if (inputData[1])
@@ -547,7 +549,7 @@ function debugVideoCaptureStart()
547
549
  }
548
550
 
549
551
  // start recording
550
- console.log('Video capture started.');
552
+ LOG('Video capture started.');
551
553
  debugVideoCapture.start();
552
554
  debugVideoCaptureTimer = new Timer(0);
553
555
 
@@ -574,7 +576,7 @@ function debugVideoCaptureStop()
574
576
  return; // not recording
575
577
 
576
578
  // stop recording
577
- console.log(`Video capture ended. ${debugVideoCaptureTimer.get().toFixed(2)} seconds recorded.`);
579
+ LOG(`Video capture ended. ${debugVideoCaptureTimer.get().toFixed(2)} seconds recorded.`);
578
580
  debugVideoCapture.stop();
579
581
  debugVideoCapture = 0;
580
582
  debugVideoCaptureIcon.style.display = 'none';
@@ -31,6 +31,7 @@ export
31
31
 
32
32
  // Debug
33
33
  ASSERT,
34
+ LOG,
34
35
  debugRect,
35
36
  debugPoly,
36
37
  debugCircle,
@@ -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
  }
@@ -18,6 +18,7 @@ const debugMedals = 0;
18
18
 
19
19
  // debug commands are automatically removed from the final build
20
20
  function ASSERT (){}
21
+ function LOG (){}
21
22
  function debugInit (){}
22
23
  function debugUpdate (){}
23
24
  function debugRender (){}
@@ -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
@@ -108,6 +108,12 @@ function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisio
108
108
  const tileLayer = new TileCollisionLayer(vec2(), levelSize, tileInfo, layerRenderOrder);
109
109
  tileLayers[layerIndex] = tileLayer;
110
110
 
111
+ // apply layer color
112
+ const layerColor = dataLayer.color || WHITE;
113
+ if (dataLayer.tintcolor)
114
+ layerColor.setHex(dataLayer.tintcolor);
115
+ ASSERT(isColor(layerColor), 'layer color is not a color');
116
+
111
117
  for (let x=levelSize.x; x--;)
112
118
  for (let y=levelSize.y; y--;)
113
119
  {
@@ -115,7 +121,7 @@ function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisio
115
121
  const data = dataLayer.data[x + y*levelSize.x];
116
122
  if (data)
117
123
  {
118
- const layerData = new TileLayerData(data-1);
124
+ const layerData = new TileLayerData(data-1, 0, false, layerColor);
119
125
  tileLayer.setData(pos, layerData);
120
126
 
121
127
  // set collision for top layer