littlejsengine 1.11.17 → 1.12.6

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 (73) hide show
  1. package/dist/box2d.wasm.js +630 -0
  2. package/dist/box2d.wasm.wasm +0 -0
  3. package/dist/littlejs.d.ts +158 -81
  4. package/dist/littlejs.esm.js +882 -793
  5. package/dist/littlejs.esm.min.js +1 -1
  6. package/dist/littlejs.js +3048 -175
  7. package/dist/littlejs.min.js +1 -1
  8. package/dist/littlejs.release.js +3047 -172
  9. package/examples/box2d/game.js +82 -56
  10. package/examples/box2d/gameObjects.js +114 -107
  11. package/examples/box2d/index.html +2 -7
  12. package/examples/box2d/scenes.js +72 -78
  13. package/examples/box2d/tiles.png +0 -0
  14. package/examples/breakout/game.js +63 -28
  15. package/examples/breakout/gameObjects.js +45 -47
  16. package/examples/breakout/index.html +1 -5
  17. package/examples/breakoutTutorial/game.js +36 -29
  18. package/examples/breakoutTutorial/index.html +1 -3
  19. package/examples/empty/game.js +5 -2
  20. package/examples/empty/index.html +1 -3
  21. package/examples/htmlMenu/game.js +24 -15
  22. package/examples/htmlMenu/index.html +5 -7
  23. package/examples/index.html +24 -17
  24. package/examples/module/game.js +32 -34
  25. package/examples/module/index.html +1 -1
  26. package/examples/particles/index.html +27 -22
  27. package/examples/platformer/game.js +62 -45
  28. package/examples/platformer/gameCharacter.js +42 -34
  29. package/examples/platformer/gameEffects.js +82 -75
  30. package/examples/platformer/gameLevel.js +67 -38
  31. package/examples/platformer/{gameLevelData.js → gameLevelData.json} +1 -11
  32. package/examples/platformer/gameObjects.js +70 -64
  33. package/examples/platformer/gamePlayer.js +12 -7
  34. package/examples/platformer/index.html +1 -9
  35. package/examples/puzzle/game.js +59 -40
  36. package/examples/puzzle/index.html +1 -3
  37. package/examples/shorts/base.html +3 -2
  38. package/examples/shorts/box2d.js +46 -0
  39. package/examples/shorts/box2dCar.js +49 -0
  40. package/examples/shorts/particles.js +1 -1
  41. package/examples/shorts/platformer.js +1 -1
  42. package/examples/shorts/postProcess.js +45 -0
  43. package/examples/shorts/tileLayer.js +5 -6
  44. package/examples/shorts/tiles.png +0 -0
  45. package/examples/shorts/uiSystem.js +39 -0
  46. package/examples/starter/game.js +9 -10
  47. package/examples/starter/index.html +13 -13
  48. package/examples/stress/index.html +44 -44
  49. package/examples/typescript/build.js +17 -18
  50. package/examples/typescript/game.js +32 -34
  51. package/examples/typescript/game.ts +32 -34
  52. package/examples/typescript/index.html +1 -1
  53. package/examples/uiSystem/game.js +34 -31
  54. package/examples/uiSystem/index.html +1 -5
  55. package/package.json +1 -1
  56. package/plugins/box2d.js +28 -30
  57. package/plugins/pluginExport.js +2 -3
  58. package/plugins/uiSystem.js +9 -3
  59. package/reference.md +29 -27
  60. package/src/engine.js +10 -11
  61. package/src/engineAudio.js +24 -15
  62. package/src/engineBuild.js +25 -8
  63. package/src/engineDebug.js +1 -3
  64. package/src/engineExport.js +3 -6
  65. package/src/engineInput.js +6 -4
  66. package/src/engineObject.js +18 -14
  67. package/src/engineSettings.js +6 -6
  68. package/src/engineTileLayer.js +179 -96
  69. package/src/engineUtilities.js +16 -0
  70. package/src/engineWebGL.js +13 -1
  71. package/examples/typescript/build/dist/littlejs.esm.js +0 -4834
  72. package/examples/typescript/build/examples/typescript/build.js +0 -24
  73. package/examples/typescript/build/examples/typescript/game.js +0 -102
package/reference.md CHANGED
@@ -13,7 +13,7 @@ To start LittleJS, you need to create a few functions and pass them to engineIni
13
13
 
14
14
  ```javascript
15
15
  // Start up LittleJS engine with your callback functions
16
- engineInit(init, update, updatePost, render, renderPost, imageSources=['tiles.png']);
16
+ engineInit(init, update, updatePost, render, renderPost, imageSources=[]);
17
17
  ```
18
18
 
19
19
  ## LittleJS Utilities Classes and Functions
@@ -29,7 +29,7 @@ engineInit(init, update, updatePost, render, renderPost, imageSources=['tiles.pn
29
29
  vec2(x=0, y=x) // Create a 2D vector with Vector or floats
30
30
  rgb(r=1, g=1, b=1, a=1) // Create a color object with RGBA values
31
31
  hsl(h=0, s=0, l=1, a=1) // Create a color object with HSLA values
32
- tile(pos=(0,0), size, textureIndex=0) // Create a tile info object
32
+ tile(pos, size=(16,16), texture=0, padding=0) // Create a tile info object
33
33
 
34
34
  // Helper functions
35
35
  abs(value) // Get absolute value
@@ -131,12 +131,12 @@ Timer.valueOf() // Get how long since elapsed, 0 if not set
131
131
 
132
132
  ```javascript
133
133
  // Drawing functions
134
- drawTile(pos, size=(1,1), tileInfo, color, angle=0, mirror, additiveColor)
135
- drawRect(pos, size=(1,1), color=(1,1,1,1), angle=0)
136
- drawLine(posA, posB, thickness=.1, color=(1,1,1,1))
134
+ drawTile(pos, size=(1,1), tileInfo, color=WHITE, angle=0, mirror, additiveColor)
135
+ drawRect(pos, size=(1,1), color=WHITE, angle=0)
136
+ drawLine(posA, posB, thickness=.1, color=WHITE)
137
137
  drawCanvas2D(pos, size, angle, mirror, drawFunction)
138
- drawText(text, pos, size=1, color=(1,1,1,1), lineWidth, lineColor)
139
- drawTextScreen(text, pos, size=1, color=(1,1,1,1), lineWidth, lineColor)
138
+ drawText(text, pos, size=1, color=WHITE, lineWidth, lineColor)
139
+ drawTextScreen(text, pos, size=1, color=WHITE, lineWidth, lineColor)
140
140
  setBlendMode(additive)
141
141
  toggleFullscreen()
142
142
  isFullscreen()
@@ -172,10 +172,15 @@ getCameraSize() // Get the camera's visible area in world space
172
172
  canvasMaxSize = (1920, 1200) // The max size of the canvas
173
173
  canvasFixedSize = (0, 0) // Fixed size of the canvas
174
174
  fontDefault = 'arial' // Default font used for text rendering
175
- canvasPixelated = true // Disable filtering for crisper pixel art?
176
- showSplashScreen = false // Show the LittleJS splash screen on startup?
177
- glEnable = true // Enable fast WebGL rendering?
178
- glOverlay = true // Prevent compositing the WebGL canvas?
175
+ canvasPixelated = true // Use nearest neighbor canvas scaling for more pixelated look
176
+ tilesPixelated = true // Disable filtering for crisper pixel art
177
+ showSplashScreen = false // Show the LittleJS splash screen on startup
178
+ glEnable = true // Enable fast WebGL rendering
179
+ glOverlay = true // Prevent compositing the WebGL canvas
180
+
181
+ // Tile sheet settings
182
+ tileSizeDefault = (16,16) // Default size of tiles in pixels
183
+ tileFixBleedScale = .3 // How much smaller to draw tiles to prevent bleeding
179
184
  ```
180
185
 
181
186
  ## LittleJS Audio System
@@ -224,7 +229,7 @@ soundDefaultTaper = .7 // Default range percent to taper off sound (0-1)
224
229
  keyIsDown(key) // Is key down?
225
230
  keyWasPressed(key) // Was key pressed this frame?
226
231
  keyWasReleased(key) // Was key released this frame?
227
- keyDirection() // Get input vector from arrow keys or wasd
232
+ keyDirection(up, down, left, right) // Get input vector from arrow keys or wasd
228
233
 
229
234
  // Mouse / Touch
230
235
  mousePos // World space mouse position
@@ -276,7 +281,7 @@ touchGamepadAlpha = .3 // Transparency of touch gamepad overlay
276
281
 
277
282
  ```javascript
278
283
  // Engine Object
279
- EngineObject(pos=(0,0), size=(1,1), tileInfo, angle=0, color, renderOrder=0)
284
+ EngineObject(pos, size=(1,1), tileInfo, angle=0, color, renderOrder=0)
280
285
  EngineObject.update() // Update object, called automatically
281
286
  EngineObject.render() // Render object, called automatically
282
287
  EngineObject.destroy() // Destroy this object and children
@@ -317,7 +322,7 @@ objectDefaultAngleDamping = 1 // How much to slow angular velocity each frame (0
317
322
  objectDefaultElasticity = 0 // How much to bounce when a collision occurs (0-1)
318
323
  objectDefaultFriction = .8 // How much to slow when touching (0-1)
319
324
  objectMaxSpeed = 1 // Clamp max speed to avoid fast objects missing collisions
320
- gravity = 0 // How much gravity to apply to objects
325
+ gravity = (0,0) // How much gravity to apply to objects
321
326
 
322
327
  // Engine Object functions
323
328
  engineObjectsCollect(pos, size, objects=engineObjects)k
@@ -336,15 +341,8 @@ engineObjectsDestroy()
336
341
  - Drawn directly to the main canvas without using WebGL
337
342
 
338
343
  ```javascript
339
- // Tile Collision System
340
- tileCollisionSize // Size of the tile collision layer
341
- initTileCollision(size) // Clear and initialize tile collision
342
- setTileCollisionData(pos, data=0) // Set tile collision data at pos
343
- getTileCollisionData(pos) // Get tile collision data at pos
344
- tileCollisionTest(pos, size=(0,0), object) // Check if collision should occur
345
- tileCollisionRaycast(posStart, posEnd, object) // Return the center of tile if hit
346
-
347
- // Tile Layer Object
344
+
345
+ // LittleJS Layer System
348
346
  TileLayer(position, size, tileInfo, scale) // Create a tile layer object
349
347
  TileLayer.setData(layerPos, data, redraw) // Set data at position
350
348
  TileLayer.getData(layerPos) // Get data at position
@@ -355,12 +353,16 @@ TileLayer.drawTile(pos, size=(1,1), tileInfo, color, angle, mirror) // Draw tile
355
353
  TileLayer.drawCanvas2D(pos, size, angle, mirror, drawFunction) // Draw to 2D canvas
356
354
 
357
355
  // Tile Layer Data Object
358
- TileLayerData(tile, direction=0, mirror=false, color=(1,1,1,1)) // Create tile data object
356
+ TileLayerData(tile, direction=0, mirror=false, color=WHITE) // Create tile data object
359
357
  TileLayerData.clear() // Clear this tile data
360
358
 
361
- // Tile sheet settings
362
- tileSizeDefault = (16,16) // Default size of tiles in pixels
363
- tileFixBleedScale = .3 // How much smaller to draw tiles to prevent bleeding
359
+ // Tile Collision Layer
360
+ TileCollisionLayer(position, size, tileInfo=tile()) // Create a tile collision layer object
361
+ TileCollisionLayer.size // Size of the tile collision layer
362
+ TileCollisionLayer.setCollisionData(pos, data=1) // Set tile collision data at pos
363
+ getTileCollisionData(pos) // Get tile collision data at pos
364
+ tileCollisionTest(pos, size=(0,0), object) // Check if collision should occur
365
+ tileCollisionRaycast(posStart, posEnd, object) // Return the center of tile if hit
364
366
  ```
365
367
 
366
368
  ## LittleJS Particle System
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.11.17';
33
+ const engineVersion = '1.12.6';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {number}
@@ -112,7 +112,7 @@ function engineAddPlugin(updateFunction, renderFunction)
112
112
  * @param {Array<string>} [imageSources=[]] - List of images to load
113
113
  * @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
114
114
  * @memberof Engine */
115
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
115
+ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
116
116
  {
117
117
  ASSERT(!mainContext, 'engine already initialized');
118
118
  ASSERT(Array.isArray(imageSources), 'pass in images as array');
@@ -269,16 +269,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
269
269
  mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
270
270
  }
271
271
 
272
- function startEngine()
272
+ // wait for gameInit to load
273
+ async function startEngine()
273
274
  {
274
- new Promise((resolve) => resolve(gameInit())).then(engineUpdate);
275
+ await gameInit();
276
+ engineUpdate();
275
277
  }
276
-
277
278
  if (headlessMode)
278
- {
279
- startEngine();
280
- return;
281
- }
279
+ return startEngine();
282
280
 
283
281
  // setup html
284
282
  const styleRoot =
@@ -354,8 +352,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
354
352
  }));
355
353
  }
356
354
 
357
- // load all of the images
358
- Promise.all(promises).then(startEngine);
355
+ // wait for all the promises to finish
356
+ await Promise.all(promises);
357
+ return startEngine();
359
358
  }
360
359
 
361
360
  /** Update each engine object, remove destroyed objects, and update time
@@ -47,17 +47,17 @@ class Sound
47
47
  {
48
48
  /** Create a sound object and cache the zzfx samples for later use
49
49
  * @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
50
- * @param {number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
50
+ * @param {number} [range=soundDefaultRange] - World space max range of sound
51
51
  * @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
52
52
  */
53
53
  constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
54
54
  {
55
55
  if (!soundEnable || headlessMode) return;
56
56
 
57
- /** @property {number} - World space max range of sound, will not play if camera is farther away */
57
+ /** @property {number} - World space max range of sound */
58
58
  this.range = range;
59
59
 
60
- /** @property {number} - At what percentage of range should it start tapering off */
60
+ /** @property {number} - At what percentage of range should it start tapering */
61
61
  this.taper = taper;
62
62
 
63
63
  /** @property {number} - How much to randomize frequency each time sound plays */
@@ -173,8 +173,8 @@ class SoundWave extends Sound
173
173
  /** Create a sound object and cache the wave file for later use
174
174
  * @param {string} filename - Filename of audio file to load
175
175
  * @param {number} [randomness] - How much to randomize frequency each time sound plays
176
- * @param {number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
177
- * @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
176
+ * @param {number} [range=soundDefaultRange] - World space max range of sound
177
+ * @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
178
178
  * @param {Function} [onloadCallback] - callback function to call when sound is loaded
179
179
  */
180
180
  constructor(filename, randomness=0, range, taper, onloadCallback)
@@ -182,17 +182,26 @@ class SoundWave extends Sound
182
182
  super(undefined, range, taper);
183
183
  if (!soundEnable || headlessMode) return;
184
184
 
185
+ /** @property {Function} - callback function to call when sound is loaded */
186
+ this.onloadCallback = onloadCallback;
185
187
  this.randomness = randomness;
186
- fetch(filename)
187
- .then(response => response.arrayBuffer())
188
- .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
189
- .then(audioBuffer =>
190
- {
191
- this.sampleChannels = [];
192
- for (let i = audioBuffer.numberOfChannels; i--;)
193
- this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
194
- this.sampleRate = audioBuffer.sampleRate;
195
- }).then(() => onloadCallback && onloadCallback(this));
188
+ this.loadSound(filename);
189
+ }
190
+
191
+ /** Loads a sound from a URL and decodes it into sample data. Must be used with await!
192
+ * @param {string} filename
193
+ * @return {Promise<void>} */
194
+ async loadSound(filename)
195
+ {
196
+ const response = await fetch(filename);
197
+ const arrayBuffer = await response.arrayBuffer();
198
+ const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
199
+ this.sampleChannels = [];
200
+ for (let i = audioBuffer.numberOfChannels; i--;)
201
+ this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
202
+ this.sampleRate = audioBuffer.sampleRate;
203
+ if (this.onloadCallback)
204
+ this.onloadCallback();
196
205
  }
197
206
  }
198
207
 
@@ -35,7 +35,11 @@ const enginePluginFiles =
35
35
  `${PLUGIN_FOLDER}/zzfxm.js`,
36
36
  `${PLUGIN_FOLDER}/uiSystem.js`,
37
37
  `${PLUGIN_FOLDER}/box2d.js`,
38
- `${PLUGIN_FOLDER}/pluginExport.js`,
38
+ ];
39
+ const engineExtraFiles =
40
+ [
41
+ `${PLUGIN_FOLDER}/box2d.wasm.js`,
42
+ `${PLUGIN_FOLDER}/box2d.wasm.wasm`,
39
43
  ];
40
44
  const asciiArt =`
41
45
  ~~~~°°°°ooo°oOo°ooOooOooOo.
@@ -58,6 +62,11 @@ try
58
62
  // Setup build folder
59
63
  fs.rmSync(BUILD_FOLDER, { recursive: true, force: true });
60
64
  fs.mkdirSync(BUILD_FOLDER);
65
+
66
+ // copy extra files to build folder
67
+ for (const file of engineExtraFiles)
68
+ fs.copyFileSync(file, `${BUILD_FOLDER}/${file.substring(file.lastIndexOf('/')+1)}`);
69
+
61
70
  }
62
71
  catch (e) { handleError(e, 'Failed to create build folder!'); }
63
72
 
@@ -65,7 +74,11 @@ Build
65
74
  (
66
75
  'Build Engine -- all',
67
76
  `${BUILD_FOLDER}/${ENGINE_NAME}.js`,
68
- [`${SOURCE_FOLDER}/engineDebug.js`, ...engineSourceFiles],
77
+ [
78
+ `${SOURCE_FOLDER}/engineDebug.js`,
79
+ ...engineSourceFiles,
80
+ ...enginePluginFiles
81
+ ],
69
82
  [], true
70
83
  );
71
84
 
@@ -73,7 +86,11 @@ Build
73
86
  (
74
87
  'Build Engine -- release',
75
88
  `${BUILD_FOLDER}/${ENGINE_NAME}.release.js`,
76
- [`${SOURCE_FOLDER}/engineRelease.js`, ...engineSourceFiles],
89
+ [
90
+ `${SOURCE_FOLDER}/engineRelease.js`,
91
+ ...engineSourceFiles,
92
+ ...enginePluginFiles
93
+ ],
77
94
  [], true
78
95
  );
79
96
 
@@ -92,7 +109,7 @@ Build
92
109
  [
93
110
  `${BUILD_FOLDER}/${ENGINE_NAME}.js`,
94
111
  `${SOURCE_FOLDER}/engineExport.js`,
95
- ...enginePluginFiles
112
+ `${PLUGIN_FOLDER}/pluginExport.js`
96
113
  ],
97
114
  [typeScriptBuildStep]
98
115
  );
@@ -104,9 +121,9 @@ Build
104
121
  [
105
122
  `${BUILD_FOLDER}/${ENGINE_NAME}.release.js`,
106
123
  `${SOURCE_FOLDER}/engineExport.js`,
107
- ...enginePluginFiles
124
+ `${PLUGIN_FOLDER}/pluginExport.js`
108
125
  ],
109
- [closureCompilerStep, uglifyBuildStep]
126
+ [uglifyBuildStep]
110
127
  );
111
128
 
112
129
  console.log(`Engine built in ${((Date.now() - startTime)/1e3).toFixed(2)} seconds!`);
@@ -133,9 +150,9 @@ function Build(message, outputFile, files=[], buildSteps=[], isPrimaryBuild)
133
150
  // get file content
134
151
  let fileContent = fs.readFileSync(file) + '\n';
135
152
 
136
- // remove first 'use strict' from each file
153
+ // remove first 'use strict' and surrounding new lines
137
154
  if (isPrimaryBuild)
138
- fileContent = fileContent.replace("'use strict';", '');
155
+ fileContent = fileContent.replace(/'use strict';\s*\n/g, '');
139
156
 
140
157
  // add it to the buffer
141
158
  buffer += fileContent;
@@ -329,11 +329,9 @@ function debugRender()
329
329
  }
330
330
  }
331
331
 
332
- if (tileCollisionSize.x > 0 && tileCollisionSize.y > 0)
332
+ if (tileCollisionLayers.length) // show floored tile pick if there is tile collision
333
333
  drawRect(mousePos.floor().add(vec2(.5)), vec2(1), rgb(0,0,1,.5), 0, false);
334
334
  mainContext = saveContext;
335
-
336
- //glCopyToContext(mainContext = saveContext);
337
335
  }
338
336
 
339
337
  {
@@ -2,8 +2,6 @@
2
2
  * LittleJS Module Export
3
3
  */
4
4
 
5
- 'use strict';
6
-
7
5
  export
8
6
  {
9
7
  // Engine
@@ -151,6 +149,7 @@ export
151
149
  isIntersecting,
152
150
  wave,
153
151
  formatTime,
152
+ fetchJSON,
154
153
 
155
154
  // Random
156
155
  rand,
@@ -279,15 +278,13 @@ export
279
278
  EngineObject,
280
279
 
281
280
  // Tiles
282
- tileCollision,
283
- tileCollisionSize,
284
- initTileCollision,
285
- setTileCollisionData,
281
+ tileCollisionLayers,
286
282
  getTileCollisionData,
287
283
  tileCollisionTest,
288
284
  tileCollisionRaycast,
289
285
  TileLayerData,
290
286
  TileLayer,
287
+ TileCollisionLayer,
291
288
 
292
289
  // Particles
293
290
  ParticleEmitter,
@@ -61,21 +61,21 @@ function clearInput() { inputData = [[]]; touchGamepadButtons = []; }
61
61
  * @param {number} button
62
62
  * @return {boolean}
63
63
  * @memberof Input */
64
- const mouseIsDown = keyIsDown;
64
+ function mouseIsDown(button) { return keyIsDown(button); }
65
65
 
66
66
  /** Returns true if mouse button was pressed
67
67
  * @function
68
68
  * @param {number} button
69
69
  * @return {boolean}
70
70
  * @memberof Input */
71
- const mouseWasPressed = keyWasPressed;
71
+ function mouseWasPressed(button) { return keyWasPressed(button); }
72
72
 
73
73
  /** Returns true if mouse button was released
74
74
  * @function
75
75
  * @param {number} button
76
76
  * @return {boolean}
77
77
  * @memberof Input */
78
- const mouseWasReleased = keyWasReleased;
78
+ function mouseWasReleased(button) { return keyWasReleased(button); }
79
79
 
80
80
  /** Mouse pos in world space
81
81
  * @type {Vector2}
@@ -314,7 +314,9 @@ function gamepadsUpdate()
314
314
  const button = gamepad.buttons[j];
315
315
  const wasDown = gamepadIsDown(j,i);
316
316
  data[j] = button.pressed ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
317
- isUsingGamepad ||= !i && button.pressed;
317
+ if (!button.value || button.value > .9) // must be a full press
318
+ if (!i && button.pressed)
319
+ isUsingGamepad = true;
318
320
  }
319
321
 
320
322
  if (gamepadDirectionEmulateStick)
@@ -158,7 +158,10 @@ class EngineObject
158
158
  this.velocity.x *= this.damping;
159
159
  this.velocity.y *= this.damping;
160
160
  if (this.mass) // don't apply gravity to static objects
161
- this.velocity.y += gravity * this.gravityScale;
161
+ {
162
+ this.velocity.x += gravity.x * this.gravityScale;
163
+ this.velocity.y += gravity.y * this.gravityScale;
164
+ }
162
165
  this.pos.x += this.velocity.x;
163
166
  this.pos.y += this.velocity.y;
164
167
  this.angle += this.angleVelocity *= this.angleDamping;
@@ -173,9 +176,9 @@ class EngineObject
173
176
  if (this.groundObject)
174
177
  {
175
178
  // apply friction in local space of ground object
176
- const groundSpeed = this.groundObject != this && this.groundObject.velocity ?
177
- this.groundObject.velocity.x : 0;
178
- this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * this.friction;
179
+ const friction = max(this.friction, this.groundObject.friction);
180
+ const groundSpeed = this.groundObject.velocity ? this.groundObject.velocity.x : 0;
181
+ this.velocity.x = groundSpeed + (this.velocity.x - groundSpeed) * friction;
179
182
  this.groundObject = undefined;
180
183
  //debugOverlay && debugPhysics && debugPoint(this.pos.subtract(vec2(0,this.size.y/2)), '#0f0');
181
184
  }
@@ -217,7 +220,7 @@ class EngineObject
217
220
 
218
221
  // check for collision
219
222
  const sizeBoth = this.size.add(o.size);
220
- const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity; // prefer to push up if small delta
223
+ const smallStepUp = (oldPos.y - o.pos.y)*2 > sizeBoth.y + gravity.y; // prefer to push up if small delta
221
224
  const isBlockedX = abs(oldPos.y - o.pos.y)*2 < sizeBoth.y;
222
225
  const isBlockedY = abs(oldPos.x - o.pos.x)*2 < sizeBoth.x;
223
226
  const elasticity = max(this.elasticity, o.elasticity);
@@ -279,19 +282,21 @@ class EngineObject
279
282
  if (this.collideTiles)
280
283
  {
281
284
  // check collision against tiles
282
- if (tileCollisionTest(this.pos, this.size, this))
285
+ const hitLayer = tileCollisionTest(this.pos, this.size, this)
286
+ if (hitLayer)
283
287
  {
284
288
  // if already was stuck in collision, don't do anything
285
289
  // this should not happen unless something starts in collision
286
290
  if (!tileCollisionTest(oldPos, this.size, this))
287
291
  {
288
292
  // test which side we bounced off (or both if a corner)
289
- const isBlockedY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
290
- const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
291
- if (isBlockedY || !isBlockedX)
293
+ const blockedLayerY = tileCollisionTest(vec2(oldPos.x, this.pos.y), this.size, this);
294
+ const blockedLayerX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
295
+ if (blockedLayerY || !blockedLayerX)
292
296
  {
293
297
  // bounce velocity
294
- this.velocity.y *= -this.elasticity;
298
+ const elasticity = max(this.elasticity, hitLayer.elasticity);
299
+ this.velocity.y *= -elasticity;
295
300
 
296
301
  if (wasMovingDown)
297
302
  {
@@ -300,9 +305,8 @@ class EngineObject
300
305
  const epsilon = .0001;
301
306
  this.pos.y = (oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;
302
307
 
303
- // set ground object to self for tile collision
304
- // TODO: rework system so tile collision is its own object
305
- this.groundObject = this;
308
+ // set ground object for tile collision
309
+ this.groundObject = hitLayer;
306
310
  }
307
311
  else
308
312
  {
@@ -311,7 +315,7 @@ class EngineObject
311
315
  this.groundObject = undefined;
312
316
  }
313
317
  }
314
- if (isBlockedX)
318
+ if (blockedLayerX)
315
319
  {
316
320
  // move to previous position and bounce
317
321
  this.pos.x = oldPos.x;
@@ -37,7 +37,7 @@ let canvasMaxSize = vec2(1920, 1080);
37
37
  * @memberof Settings */
38
38
  let canvasFixedSize = vec2();
39
39
 
40
- /** Use nearest neighbor scaling algorithm for canvas for more pixelated look
40
+ /** Use nearest neighbor canvas scaling for more pixelated look
41
41
  * - Must be set before startup to take effect
42
42
  * - If enabled sets css image-rendering:pixelated
43
43
  * @type {boolean}
@@ -147,11 +147,11 @@ let objectDefaultFriction = .8;
147
147
  * @memberof Settings */
148
148
  let objectMaxSpeed = 1;
149
149
 
150
- /** How much gravity to apply to objects along the Y axis, negative is down
151
- * @type {number}
150
+ /** How much gravity to apply to objects, negative Y is down
151
+ * @type {Vector2}
152
152
  * @default
153
153
  * @memberof Settings */
154
- let gravity = 0;
154
+ let gravity = vec2();
155
155
 
156
156
  /** Scales emit rate of particles, useful for low graphics mode (0 disables particle emitters)
157
157
  * @type {number}
@@ -377,8 +377,8 @@ function setObjectDefaultFriction(friction) { objectDefaultFriction = friction;
377
377
  * @memberof Settings */
378
378
  function setObjectMaxSpeed(speed) { objectMaxSpeed = speed; }
379
379
 
380
- /** Set how much gravity to apply to objects along the Y axis
381
- * @param {number} newGravity
380
+ /** Set how much gravity to apply to objects
381
+ * @param {Vector2} newGravity
382
382
  * @memberof Settings */
383
383
  function setGravity(newGravity) { gravity = newGravity; }
384
384