littlejsengine 1.1.18 → 1.2.3

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.
Files changed (39) hide show
  1. package/README.md +17 -11
  2. package/build.bat +1 -0
  3. package/engine/engine.all.js +495 -504
  4. package/engine/engine.all.min.js +1 -1
  5. package/engine/engine.all.release.js +418 -259
  6. package/engine/engine.js +31 -23
  7. package/engine/engineAudio.js +21 -7
  8. package/engine/engineBuild.bat +9 -1
  9. package/engine/engineBuildSetup.bat +2 -1
  10. package/engine/engineDebug.js +81 -247
  11. package/engine/engineDraw.js +109 -21
  12. package/engine/engineFont.png +0 -0
  13. package/engine/engineInput.js +103 -50
  14. package/engine/engineMedals.js +17 -20
  15. package/engine/engineObject.js +14 -23
  16. package/engine/engineParticles.js +8 -6
  17. package/engine/engineRelease.js +3 -1
  18. package/engine/engineSettings.js +20 -10
  19. package/engine/engineTileLayer.js +19 -18
  20. package/engine/engineUtilities.js +14 -12
  21. package/engine/engineWebGL.js +59 -68
  22. package/engine/index.d.ts +1617 -0
  23. package/examples/breakout/game.js +9 -7
  24. package/examples/breakout/gameObjects.js +3 -3
  25. package/examples/breakout/index.html +6 -4
  26. package/examples/platformer/game.js +5 -5
  27. package/examples/platformer/gameEffects.js +11 -14
  28. package/examples/platformer/gameLevel.js +14 -7
  29. package/examples/platformer/gameObjects.js +3 -3
  30. package/examples/platformer/gamePlayer.js +13 -8
  31. package/examples/platformer/index.html +9 -7
  32. package/examples/platformer/tiles.png +0 -0
  33. package/examples/puzzle/game.js +2 -2
  34. package/examples/puzzle/index.html +5 -3
  35. package/examples/screenshot.jpg +0 -0
  36. package/examples/stress/index.html +7 -5
  37. package/game.js +9 -8
  38. package/index.html +16 -14
  39. package/package.json +1 -1
@@ -92,17 +92,13 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
92
92
  {
93
93
  // calculate uvs and render
94
94
  const cols = tileImageSize.x / tileSize.x |0;
95
- const uvSizeX = tileSize.x * tileImageSizeInverse.x;
96
- const uvSizeY = tileSize.y * tileImageSizeInverse.y;
95
+ const uvSizeX = tileSize.x / tileImageSize.x;
96
+ const uvSizeY = tileSize.y / tileImageSize.y;
97
97
  const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
98
-
99
- // shrink uvs to prevent bleeding
100
- const shrinkTilesX = tileBleedShrinkFix * tileImageSizeInverse.x;
101
- const shrinkTilesY = tileBleedShrinkFix * tileImageSizeInverse.y;
102
98
 
103
99
  glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
104
- uvX + shrinkTilesX, uvY + shrinkTilesY,
105
- uvX - shrinkTilesX + uvSizeX, uvY - shrinkTilesX + uvSizeY,
100
+ uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
101
+ uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
106
102
  color.rgbaInt(), additiveColor.rgbaInt());
107
103
  }
108
104
  }
@@ -121,10 +117,10 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
121
117
  {
122
118
  // calculate uvs and render
123
119
  const cols = tileImageSize.x / tileSize.x |0;
124
- const sX = (tileIndex%cols)*tileSize.x + tileBleedShrinkFix;
125
- const sY = (tileIndex/cols|0)*tileSize.y + tileBleedShrinkFix;
126
- const sWidth = tileSize.x - 2*tileBleedShrinkFix;
127
- const sHeight = tileSize.y - 2*tileBleedShrinkFix;
120
+ const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
121
+ const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
122
+ const sWidth = tileSize.x - 2*tileFixBleedScale;
123
+ const sHeight = tileSize.y - 2*tileFixBleedScale;
128
124
  context.globalAlpha = color.a; // only alpha is supported
129
125
  context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
130
126
  }
@@ -207,6 +203,18 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
207
203
  context.restore();
208
204
  }
209
205
 
206
+ /** Enable normal or additive blend mode
207
+ * @param {Boolean} [additive=0]
208
+ * @param {Boolean} [useWebGL=glEnable]
209
+ * @memberof Draw */
210
+ function setBlendMode(additive, useWebGL=glEnable)
211
+ {
212
+ if (glEnable && useWebGL)
213
+ glSetBlendMode(additive);
214
+ else
215
+ mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
216
+ }
217
+
210
218
  /** Draw text on overlay canvas in world space
211
219
  * @param {String} text
212
220
  * @param {Vector2} pos
@@ -232,16 +240,96 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
232
240
  overlayContext.fillText(text, pos.x, pos.y);
233
241
  }
234
242
 
235
- /** Enable normal or additive blend mode
236
- * @param {Boolean} [additive=0]
237
- * @param {Boolean} [useWebGL=glEnable]
238
- * @memberof Draw */
239
- function setBlendMode(additive, useWebGL=glEnable)
243
+ ///////////////////////////////////////////////////////////////////////////////
244
+
245
+ /**
246
+ * Font Image Object - Draw text on a 2D canvas by using characters in an image
247
+ * <br> - 96 characters (from space to tilde) are stored in an image
248
+ * <br> - Uses a default 8x8 font if none is supplied
249
+ * <br> - You can also use fonts from the main tile sheet
250
+ * @example
251
+ * // use built in font
252
+ * const font = new ImageFont;
253
+ *
254
+ * // draw text
255
+ * font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
256
+ */
257
+
258
+ let engineFontImage;
259
+
260
+ class FontImage
240
261
  {
241
- if (glEnable && useWebGL)
242
- glSetBlendMode(additive);
243
- else
244
- mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
262
+ /** Create an image font
263
+ * @param {Image} [image] - The image the font is stored in, if undefined the default font is used
264
+ * @param {Vector2} [tileSize=vec2(8)] - The size of the font source tiles
265
+ * @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
266
+ * @param {Number} [startTileIndex=0] - Tile index in image where font starts
267
+ * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
268
+ */
269
+ constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
270
+ {
271
+ if (!image && !engineFontImage)
272
+ {
273
+ // load default font image
274
+ engineFontImage = new Image();
275
+ engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
276
+ }
277
+
278
+ this.image = image || engineFontImage;
279
+ this.tileSize = tileSize;
280
+ this.paddingSize = paddingSize;
281
+ this.startTileIndex = startTileIndex;
282
+ this.context = context;
283
+ }
284
+
285
+ /** Draw text in screen space using the image font
286
+ * @param {String} text
287
+ * @param {Vector2} pos
288
+ * @param {Number} [scale=4]
289
+ * @param {Boolean} [center]
290
+ */
291
+ drawTextScreen(text, pos, scale=4, center)
292
+ {
293
+ const context = this.context;
294
+ context.save();
295
+ context.imageSmoothingEnabled = !cavasPixelated;
296
+
297
+ const size = this.tileSize;
298
+ const drawSize = size.add(this.paddingSize).scale(scale);
299
+ const cols = this.image.width / this.tileSize.x |0;
300
+ text.split('\n').forEach((line, i)=>
301
+ {
302
+ const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
303
+ for(let j=line.length; j--;)
304
+ {
305
+ // draw each character
306
+ let charCode = line[j].charCodeAt();
307
+ if (charCode < 32 || charCode > 127)
308
+ charCode = 127; // unknown character
309
+
310
+ // get the character source location and draw it
311
+ const tile = this.startTileIndex + charCode - 32;
312
+ const x = tile % cols;
313
+ const y = tile / cols |0;
314
+ const drawPos = pos.add(vec2(j,i).multiply(drawSize));
315
+ context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
316
+ drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
317
+ }
318
+ });
319
+
320
+ context.restore();
321
+ }
322
+
323
+ /** Draw text in world space using the image font
324
+ * @param {String} text
325
+ * @param {Vector2} pos
326
+ * @param {Number} [scale=.25]
327
+ * @param {Boolean} [center]
328
+ */
329
+ drawText(text, pos, scale=1, center)
330
+ {
331
+ this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
332
+ }
245
333
  }
246
334
 
247
335
  ///////////////////////////////////////////////////////////////////////////////
Binary file
@@ -32,7 +32,7 @@ const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][
32
32
 
33
33
  /** Clears all input
34
34
  * @memberof Input */
35
- const clearInput = ()=> inputData[0] = [];
35
+ const clearInput = ()=> inputData = [[]];
36
36
 
37
37
  /** Returns true if mouse button is down
38
38
  * @param {Number} button
@@ -101,7 +101,9 @@ const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepa
101
101
  ///////////////////////////////////////////////////////////////////////////////
102
102
  // Input update called by engine
103
103
 
104
- const inputData = [[]];
104
+ // store input as a bit field for each key: 1 = isDown, 2 = wasPressed, 4 = wasReleased
105
+ // mouse and keyboard are stored together in device 0, gamepads are in devices > 0
106
+ let inputData = [[]];
105
107
 
106
108
  function inputUpdate()
107
109
  {
@@ -127,36 +129,38 @@ function inputUpdatePost()
127
129
  ///////////////////////////////////////////////////////////////////////////////
128
130
  // Keyboard event handlers
129
131
 
130
- onkeydown = e=>
132
+ onkeydown = (e)=>
131
133
  {
132
134
  if (debug && e.target != document.body) return;
133
135
  e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
134
136
  debug || e.preventDefault();
135
137
  }
136
- onkeyup = e=>
138
+ onkeyup = (e)=>
137
139
  {
138
140
  if (debug && e.target != document.body) return;
139
141
  inputData[0][remapKeyCode(e.keyCode)] = 4;
140
142
  }
141
- const remapKeyCode = c=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
143
+ const remapKeyCode = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
142
144
 
143
145
  ///////////////////////////////////////////////////////////////////////////////
144
146
  // Mouse event handlers
145
147
 
146
- onmousedown = e=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
147
- onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
148
- onmousemove = e=>
148
+ onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
149
+ onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
150
+ onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
151
+ onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
152
+ oncontextmenu = (e)=> !1; // prevent right click menu
153
+
154
+ // convert a mouse or touch event position to screen space
155
+ const mouseToScreen = (mousePos)=>
149
156
  {
150
157
  if (!mainCanvas)
151
- return; // fix bug that can occur if user clicks before page loads
158
+ return vec2(); // fix bug that can occur if user clicks before page loads
152
159
 
153
- // convert mouse pos to canvas space
154
160
  const rect = mainCanvas.getBoundingClientRect();
155
- mousePosScreen = mainCanvasSize.multiply(
156
- vec2(percent(e.x, rect.right, rect.left), percent(e.y, rect.bottom, rect.top)));
161
+ return mainCanvasSize.multiply(
162
+ vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
157
163
  }
158
- onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
159
- oncontextmenu = e=> !1; // prevent right click menu
160
164
 
161
165
  ///////////////////////////////////////////////////////////////////////////////
162
166
  // Gamepad input
@@ -164,6 +168,21 @@ oncontextmenu = e=> !1; // prevent right click menu
164
168
  const stickData = [];
165
169
  function gamepadsUpdate()
166
170
  {
171
+ if (touchGamepadEnable && touchGamepadTimer.isSet())
172
+ {
173
+ // read virtual analog stick
174
+ const sticks = stickData[0] || (stickData[0] = []);
175
+ sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
176
+
177
+ // read virtual gamepad buttons
178
+ const data = inputData[1] || (inputData[1] = []);
179
+ for (let i=10; i--;)
180
+ {
181
+ const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
182
+ data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
183
+ }
184
+ }
185
+
167
186
  if (!gamepadsEnable || !navigator.getGamepads || !document.hasFocus() && !debug)
168
187
  return;
169
188
 
@@ -181,8 +200,8 @@ function gamepadsUpdate()
181
200
  // read clamp dead zone of analog sticks
182
201
  const deadZone = .3, deadZoneMax = .8;
183
202
  const applyDeadZone = (v)=>
184
- v > deadZone ? percent( v, deadZoneMax, deadZone) :
185
- v < -deadZone ? -percent(-v, deadZoneMax, deadZone) : 0;
203
+ v > deadZone ? percent( v, deadZone, deadZoneMax) :
204
+ v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
186
205
 
187
206
  // read analog sticks
188
207
  for (let j = 0; j < gamepad.axes.length-1; j+=2)
@@ -194,6 +213,7 @@ function gamepadsUpdate()
194
213
  const button = gamepad.buttons[j];
195
214
  data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
196
215
  isUsingGamepad |= !i && button.pressed;
216
+ touchGamepadEnable && touchGamepadTimer.unset(); // disable touch gamepad if using real gamepad
197
217
  }
198
218
 
199
219
  if (gamepadDirectionEmulateStick)
@@ -207,6 +227,17 @@ function gamepadsUpdate()
207
227
  }
208
228
  }
209
229
 
230
+ ///////////////////////////////////////////////////////////////////////////////
231
+
232
+ /** Pulse the vibration hardware if it exists
233
+ * @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
234
+ * @memberof Input */
235
+ const vibrate = (pattern)=> vibrateEnable && Navigator.vibrate && Navigator.vibrate(pattern);
236
+
237
+ /** Cancel any ongoing vibration
238
+ * @memberof Input */
239
+ const vibrateStop = ()=> vibrate(0);
240
+
210
241
  ///////////////////////////////////////////////////////////////////////////////
211
242
  // Touch input
212
243
 
@@ -214,7 +245,9 @@ function gamepadsUpdate()
214
245
  * @const {boolean}
215
246
  * @memberof Input */
216
247
  const isTouchDevice = window.ontouchstart !== undefined;
217
- if (touchMouseEnable && isTouchDevice)
248
+
249
+ // try to enable touch mouse
250
+ if (isTouchDevice)
218
251
  {
219
252
  // handle all touch events the same way
220
253
  let wasTouching, hadTouchInput;
@@ -245,10 +278,12 @@ if (touchMouseEnable && isTouchDevice)
245
278
  }
246
279
 
247
280
  ///////////////////////////////////////////////////////////////////////////////
248
- // Touch gamepad - on screen virtual gamepad
281
+ // touch gamepad, virtual on screen gamepad emulator for touch devices
282
+
283
+ // touch input internal variables
284
+ let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
249
285
 
250
286
  // create the touch gamepad, called automatically by the engine
251
- const touchGamepadTimer = new Timer;
252
287
  function touchGamepadCreate()
253
288
  {
254
289
  if (!touchGamepadEnable || !isTouchDevice)
@@ -258,6 +293,10 @@ function touchGamepadCreate()
258
293
  {
259
294
  if (!touchGamepadEnable)
260
295
  return;
296
+
297
+ // clear touch gamepad input
298
+ touchGamepadStick = vec2();
299
+ touchGamepadButtons = [];
261
300
 
262
301
  const touching = e.touches.length;
263
302
  if (touching)
@@ -267,6 +306,13 @@ function touchGamepadCreate()
267
306
  // set that gamepad is active
268
307
  isUsingGamepad = 1;
269
308
  touchGamepadTimer.set();
309
+
310
+ if (paused)
311
+ {
312
+ // touch anywhere to press start when paused
313
+ touchGamepadButtons[9] = 1;
314
+ return;
315
+ }
270
316
  }
271
317
 
272
318
  // get center of left and right sides
@@ -274,38 +320,34 @@ function touchGamepadCreate()
274
320
  const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
275
321
  const startCenter = mainCanvasSize.scale(.5);
276
322
 
277
- // update virtual gamepad
278
- const data = inputData[1] || (inputData[1] = []);
279
- const sticks = stickData[0] || (stickData[0] = []);
280
- sticks[0] = vec2();
281
-
282
323
  // check each touch point
283
- const buttons = [];
284
324
  for (const touch of e.touches)
285
325
  {
286
- const touchPos = vec2(touch.clientX, touch.clientY);
326
+ const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
287
327
  if (touchPos.distance(stickCenter) < touchGamepadSize)
288
328
  {
289
329
  // virtual analog stick
290
- sticks[0] = touchPos.subtract(stickCenter).clampLength();
291
- sticks[0].y = -sticks[0].y; // flip vertical
330
+ if (touchGamepadAnalog)
331
+ touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
332
+ else
333
+ {
334
+ // 8 way dpad
335
+ const angle = touchPos.subtract(stickCenter).angle();
336
+ touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
337
+ }
292
338
  }
293
339
  else if (touchPos.distance(buttonCenter) < touchGamepadSize)
294
340
  {
295
341
  // virtual face buttons
296
342
  const button = touchPos.subtract(buttonCenter).direction();
297
- buttons[button > 2 ? 2 : button > 1 ? 3 : button] = 1; // fix button locations
343
+ touchGamepadButtons[button] = 1;
298
344
  }
299
345
  else if (touchPos.distance(startCenter) < touchGamepadSize)
300
346
  {
301
- // virtual start button
302
- buttons[9] = 1;
347
+ // virtual start button in center
348
+ touchGamepadButtons[9] = 1;
303
349
  }
304
350
  }
305
-
306
- // read virtual buttons
307
- for (let j=10; j--;)
308
- data[j] = buttons[j] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
309
351
  }
310
352
  }
311
353
 
@@ -315,37 +357,48 @@ function touchGamepadRender()
315
357
  if (!touchGamepadEnable || !touchGamepadTimer.isSet())
316
358
  return;
317
359
 
318
- // fade off when not touching
319
- const alpha = percent(touchGamepadTimer.get(), 4, 5);
320
- if (!alpha)
360
+ // fade off when not touching or paused
361
+ const alpha = percent(touchGamepadTimer.get(), 4, 3);
362
+ if (!alpha || paused)
321
363
  return;
322
364
 
323
365
  // setup the canvas
324
366
  overlayContext.save();
325
367
  overlayContext.globalAlpha = alpha*touchGamepadAlpha;
326
- overlayContext.fillStyle = '#fff6';
327
368
  overlayContext.strokeStyle = '#fff';
328
369
  overlayContext.lineWidth = 3;
329
370
 
330
- // draw start button
331
- //overlayContext.beginPath();
332
- //overlayContext.arc(mainCanvasSize.x/2, mainCanvasSize.y/2, touchGamepadSize*.9, 0,9);
333
- //overlayContext.fill();
334
- //overlayContext.stroke();
335
-
336
371
  // draw left analog stick
372
+ overlayContext.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
337
373
  overlayContext.beginPath();
338
- overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize*.9, 0,9);
339
- overlayContext.fill();
340
- overlayContext.stroke();
341
374
 
375
+ const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
376
+ if (touchGamepadAnalog)
377
+ {
378
+ overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
379
+ overlayContext.fill();
380
+ overlayContext.stroke();
381
+ }
382
+ else // draw cross shaped gamepad
383
+ {
384
+ for(let i=10; i--;)
385
+ {
386
+ const angle = i*PI/4;
387
+ overlayContext.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
388
+ i%2 && overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
389
+ i==1 && overlayContext.fill();
390
+ }
391
+ overlayContext.stroke();
392
+ }
393
+
342
394
  // draw right face buttons
343
395
  const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
344
396
  for (let i=4; i--;)
345
397
  {
346
- const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize*.6));
398
+ const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize/2));
399
+ overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
347
400
  overlayContext.beginPath();
348
- overlayContext.arc(pos.x, pos.y, touchGamepadSize*.3, 0,9);
401
+ overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
349
402
  overlayContext.fill();
350
403
  overlayContext.stroke();
351
404
  }
@@ -35,7 +35,7 @@ function medalsInit(saveName)
35
35
  {
36
36
  // check if medals are unlocked
37
37
  medalsSaveName = saveName;
38
- debugMedals || medals.forEach(medal=> medal.unlocked = localStorage[medal.storageKey()]);
38
+ debugMedals || medals.forEach(medal=> medal.unlocked = 1 || localStorage[medal.storageKey()]);
39
39
  }
40
40
 
41
41
  /**
@@ -68,13 +68,9 @@ class Medal
68
68
  this.name = name;
69
69
  this.description = description;
70
70
  this.icon = icon;
71
-
71
+ this.image = new Image();
72
72
  if (src)
73
- {
74
- // load image
75
- this.image = new Image();
76
73
  this.image.src = src;
77
- }
78
74
  }
79
75
 
80
76
  /** Unlocks a medal if not already unlocked */
@@ -104,21 +100,23 @@ class Medal
104
100
  const y = -medalDisplayHeight*hidePercent;
105
101
 
106
102
  // draw containing rect and clip to that region
107
- context.beginPath(context.save());
103
+ context.save();
104
+ context.beginPath();
108
105
  context.fillStyle = '#ddd'
109
106
  context.fill(context.rect(x, y, width, medalDisplayHeight));
110
- context.strokeStyle = context.fillStyle = '#000';
111
- context.lineWidth = 2;
107
+ context.strokeStyle = '#000';
108
+ context.lineWidth = 3;
112
109
  context.stroke();
113
110
  context.clip();
114
111
 
115
112
  // draw the icon and text
116
113
  this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
117
114
  context.textAlign = 'left';
118
- context.font = '40px '+ fontDefault;
119
- context.fillText(this.name, x+medalDisplayIconSize+25, y+35);
120
- context.font = '25px '+ fontDefault;
121
- context.restore(context.fillText(this.description, x+medalDisplayIconSize+25, y+70));
115
+ context.font = '38px '+ fontDefault;
116
+ context.fillText(this.name, x+medalDisplayIconSize+30, y+28);
117
+ context.font = '24px '+ fontDefault;
118
+ context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
119
+ context.restore();
122
120
  }
123
121
 
124
122
  /** Render the icon for a medal
@@ -130,11 +128,11 @@ class Medal
130
128
  {
131
129
  // draw the image or icon
132
130
  const context = overlayContext;
131
+ context.fillStyle = '#000';
133
132
  context.textAlign = 'center';
134
133
  context.textBaseline = 'middle';
135
- context.font = size*.6 + 'px '+ fontDefault;
136
- context.fillStyle = '#000';
137
- if (this.image)
134
+ context.font = size*.7 + 'px '+ fontDefault;
135
+ if (this.image.src)
138
136
  context.drawImage(this.image, x-size/2, y-size/2, size, size);
139
137
  else
140
138
  context.fillText(this.icon, x, y); // show icon if there is no image
@@ -210,7 +208,6 @@ class Newgrounds
210
208
  if (medal)
211
209
  {
212
210
  // copy newgrounds medal data
213
- medal.image = new Image();
214
211
  medal.image.src = newgroundsMedal['icon'];
215
212
  medal.name = newgroundsMedal['name'];
216
213
  medal.description = newgroundsMedal['description'];
@@ -238,9 +235,6 @@ class Newgrounds
238
235
  * @param {Number} value - The score value */
239
236
  postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
240
237
 
241
- /** Send message to log a view */
242
- logView() { return this.call('App.logView', {'host':this.host}, 1); }
243
-
244
238
  /** Get scores from a scoreboard
245
239
  * @param {Number} id - The scoreboard id
246
240
  * @param {String} [user=0] - A user's id or name
@@ -252,6 +246,9 @@ class Newgrounds
252
246
  getScores(id, user=0, social=0, skip=0, limit=10)
253
247
  { return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
254
248
 
249
+ /** Send message to log a view */
250
+ logView() { return this.call('App.logView', {'host':this.host}, 1); }
251
+
255
252
  /** Send a message to call a component of the Newgrounds API
256
253
  * @param {String} component - Name of the component
257
254
  * @param {Object} [parameters=0] - Parameters to use for call
@@ -32,7 +32,7 @@
32
32
  class EngineObject
33
33
  {
34
34
  /** Create an engine object and adds it to the list of objects
35
- * @param {Vector2} [position=new Vector2(0,0)] - World space position of the object
35
+ * @param {Vector2} [position=new Vector2()] - World space position of the object
36
36
  * @param {Vector2} [size=objectDefaultSize] - World space size of the object
37
37
  * @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
38
38
  * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
@@ -77,12 +77,15 @@ class EngineObject
77
77
  this.gravityScale = 1;
78
78
  /** @property {Number} [renderOrder=0] - Objects are sorted by render order */
79
79
  this.renderOrder = renderOrder;
80
+ /** @property {Vector2} [velocity=new Vector2()] - Velocity of the object */
81
+ this.velocity = new Vector2();
82
+ /** @property {Number} [angleVelocity=0] - Angular velocity of the object */
83
+ this.angleVelocity = 0;
80
84
 
81
85
  // init other internal object stuff
82
86
  this.spawnTime = time;
83
- this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
84
- this.collideTiles = 1;
85
87
  this.children = [];
88
+ this.collideTiles = 1;
86
89
 
87
90
  // add to list of objects
88
91
  engineObjects.push(this);
@@ -101,8 +104,8 @@ class EngineObject
101
104
  }
102
105
 
103
106
  // limit max speed to prevent missing collisions
104
- this.velocity.x = clamp(this.velocity.x, objectMaxSpeed, -objectMaxSpeed);
105
- this.velocity.y = clamp(this.velocity.y, objectMaxSpeed, -objectMaxSpeed);
107
+ this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
108
+ this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
106
109
 
107
110
  // apply physics
108
111
  const oldPos = this.pos.copy();
@@ -150,7 +153,7 @@ class EngineObject
150
153
  // if already was touching, try to push away
151
154
  const deltaPos = oldPos.subtract(o.pos);
152
155
  const length = deltaPos.length();
153
- const pushAwayAccel = .001; // push away if alread overlapping
156
+ const pushAwayAccel = .001; // push away if already overlapping
154
157
  const velocity = length < .01 ? randVector(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
155
158
  this.velocity = this.velocity.add(velocity);
156
159
  if (o.mass) // push away if not fixed
@@ -192,8 +195,8 @@ class EngineObject
192
195
 
193
196
  // lerp betwen elastic or inelastic based on elasticity
194
197
  const elasticity = max(this.elasticity, o.elasticity);
195
- this.velocity.y = lerp(elasticity, elastic0, inelastic);
196
- o.velocity.y = lerp(elasticity, elastic1, inelastic);
198
+ this.velocity.y = lerp(elasticity, inelastic, elastic0);
199
+ o.velocity.y = lerp(elasticity, inelastic, elastic1);
197
200
  }
198
201
  }
199
202
  if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
@@ -213,8 +216,8 @@ class EngineObject
213
216
 
214
217
  // lerp betwen elastic or inelastic based on elasticity
215
218
  const elasticity = max(this.elasticity, o.elasticity);
216
- this.velocity.x = lerp(elasticity, elastic0, inelastic);
217
- o.velocity.x = lerp(elasticity, elastic1, inelastic);
219
+ this.velocity.x = lerp(elasticity, inelastic, elastic0);
220
+ o.velocity.x = lerp(elasticity, inelastic, elastic1);
218
221
  }
219
222
  else // bounce if other object is fixed
220
223
  this.velocity.x *= -this.elasticity;
@@ -244,7 +247,7 @@ class EngineObject
244
247
 
245
248
  // adjust next velocity to settle on ground
246
249
  const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
247
- if (o < 0 && o > -1 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
250
+ if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
248
251
  this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
249
252
 
250
253
  // move to previous position
@@ -345,18 +348,6 @@ class EngineObject
345
348
  {
346
349
  ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
347
350
 
348
- // track collidable objects in separate list
349
- if (collideSolidObjects && !this.collideSolidObjects)
350
- {
351
- ASSERT(!engineObjectsCollide.includes(this));
352
- engineObjectsCollide.push(this);
353
- }
354
- else if (!collideSolidObjects && this.collideSolidObjects)
355
- {
356
- ASSERT(engineObjectsCollide.includes(this))
357
- engineObjectsCollide.splice(engineObjectsCollide.indexOf(this), 1);
358
- }
359
-
360
351
  this.collideSolidObjects = collideSolidObjects;
361
352
  this.isSolid = isSolid;
362
353
  this.collideTiles = collideTiles;