littlejsengine 1.12.6 → 1.13.4

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 (102) hide show
  1. package/README.md +15 -12
  2. package/dist/littlejs.d.ts +466 -236
  3. package/dist/littlejs.esm.js +6126 -5370
  4. package/dist/littlejs.esm.min.js +4 -1
  5. package/dist/littlejs.js +6093 -5359
  6. package/dist/littlejs.min.js +4 -1
  7. package/dist/littlejs.release.js +5614 -4907
  8. package/examples/box2d/game.js +2 -2
  9. package/examples/box2d/gameObjects.js +8 -7
  10. package/examples/box2d/index.html +1 -1
  11. package/examples/box2d/scenes.js +8 -8
  12. package/examples/breakout/game.js +1 -1
  13. package/examples/breakout/gameObjects.js +2 -2
  14. package/examples/breakout/index.html +1 -1
  15. package/examples/breakoutTutorial/README.md +46 -43
  16. package/examples/breakoutTutorial/game.js +3 -3
  17. package/examples/breakoutTutorial/index.html +1 -1
  18. package/examples/electron/build.bat +7 -0
  19. package/examples/electron/build.js +124 -0
  20. package/examples/electron/electron.js +35 -0
  21. package/examples/electron/game.js +140 -0
  22. package/examples/electron/index.html +13 -0
  23. package/examples/electron/package.json +12 -0
  24. package/examples/electron/tiles.png +0 -0
  25. package/examples/empty/game.js +1 -0
  26. package/examples/empty/index.html +1 -1
  27. package/examples/htmlMenu/game.js +1 -1
  28. package/examples/htmlMenu/index.html +1 -1
  29. package/examples/index.html +486 -182
  30. package/examples/module/game.js +6 -3
  31. package/examples/module/index.html +1 -1
  32. package/examples/particles/index.html +12 -3
  33. package/examples/platformer/game.js +4 -4
  34. package/examples/platformer/gameCharacter.js +6 -6
  35. package/examples/platformer/gameEffects.js +74 -57
  36. package/examples/platformer/gameLevel.js +61 -70
  37. package/examples/platformer/gameObjects.js +4 -4
  38. package/examples/platformer/index.html +1 -1
  39. package/examples/puzzle/index.html +1 -1
  40. package/examples/shorts/base.html +23 -3
  41. package/examples/shorts/blending.js +9 -5
  42. package/examples/shorts/box2d.js +1 -1
  43. package/examples/shorts/box2dCar.js +9 -13
  44. package/examples/shorts/clock.js +1 -1
  45. package/examples/shorts/colors.js +2 -2
  46. package/examples/shorts/flappyGame.js +52 -0
  47. package/examples/shorts/helloWorld.js +6 -3
  48. package/examples/shorts/hillGlideGame.js +56 -0
  49. package/examples/shorts/landerGame.js +32 -0
  50. package/examples/shorts/maze.js +47 -0
  51. package/examples/shorts/medals.js +42 -0
  52. package/examples/shorts/nineSlice.js +21 -0
  53. package/examples/shorts/piano.js +52 -0
  54. package/examples/shorts/platformer.js +24 -20
  55. package/examples/shorts/{pong.js → pongGame.js} +1 -1
  56. package/examples/shorts/raycasting.js +71 -0
  57. package/examples/shorts/shapes.js +9 -9
  58. package/examples/shorts/slidingPuzzle.js +42 -0
  59. package/examples/shorts/spaceGame.js +56 -0
  60. package/examples/shorts/starfield.js +15 -0
  61. package/examples/shorts/systemFont.js +1 -1
  62. package/examples/shorts/tileLayer.js +3 -5
  63. package/examples/shorts/tiles.png +0 -0
  64. package/examples/shorts/tiltedView.js +8 -7
  65. package/examples/shorts/topDown.js +18 -26
  66. package/examples/shorts/uiSystem.js +5 -7
  67. package/examples/starter/build.bat +6 -1
  68. package/examples/starter/build.js +9 -8
  69. package/examples/starter/game.js +7 -4
  70. package/examples/starter/index.html +23 -13
  71. package/examples/stress/index.html +7 -8
  72. package/examples/style.css +139 -0
  73. package/examples/typescript/build.js +2 -3
  74. package/examples/typescript/game.js +4 -4
  75. package/examples/typescript/game.ts +6 -3
  76. package/examples/typescript/index.html +1 -1
  77. package/examples/uiSystem/game.js +10 -25
  78. package/examples/uiSystem/index.html +1 -1
  79. package/package.json +2 -2
  80. package/plugins/box2d.js +22 -97
  81. package/plugins/drawUtilities.js +132 -0
  82. package/plugins/newgrounds.js +1 -1
  83. package/plugins/pluginExport.js +7 -1
  84. package/plugins/postProcess.js +9 -2
  85. package/plugins/uiSystem.js +155 -67
  86. package/plugins/zzfxm.js +1 -1
  87. package/reference.md +10 -10
  88. package/src/engine.js +56 -30
  89. package/src/engineAudio.js +15 -6
  90. package/src/engineBuild.bat +6 -1
  91. package/src/engineBuild.js +20 -5
  92. package/src/engineDebug.js +55 -28
  93. package/src/engineDraw.js +321 -177
  94. package/src/engineExport.js +27 -9
  95. package/src/engineInput.js +110 -38
  96. package/src/engineMedals.js +4 -4
  97. package/src/engineObject.js +71 -70
  98. package/src/engineParticles.js +33 -9
  99. package/src/engineSettings.js +69 -23
  100. package/src/engineTileLayer.js +312 -153
  101. package/src/engineUtilities.js +187 -135
  102. package/src/engineWebGL.js +70 -39
@@ -22,7 +22,7 @@ let tileCollisionLayers = [];
22
22
  * @param {Vector2} pos
23
23
  * @return {number}
24
24
  * @memberof TileCollision */
25
- function getTileCollisionData(pos)
25
+ function tileCollisionGetData(pos)
26
26
  {
27
27
  // check all tile collision layers
28
28
  for (const layer of tileCollisionLayers)
@@ -34,35 +34,103 @@ function getTileCollisionData(pos)
34
34
  /** Check if a tile layer collides with another object
35
35
  * @param {Vector2} pos
36
36
  * @param {Vector2} [size=(0,0)]
37
- * @param {EngineObject} [object]
37
+ * @param {EngineObject} [object] - An object or undefined for generic test
38
+ * @param {boolean} [solidOnly] - Only check solid layers if true
38
39
  * @return {TileCollisionLayer}
39
40
  * @memberof TileCollision */
40
- function tileCollisionTest(pos, size=vec2(), object)
41
+ function tileCollisionTest(pos, size=vec2(), object, solidOnly=true)
41
42
  {
42
- // check all tile collision layers
43
43
  for (const layer of tileCollisionLayers)
44
+ {
45
+ if (!solidOnly || layer.isSolid)
44
46
  if (layer.collisionTest(pos, size, object))
45
47
  return layer;
48
+ }
46
49
  }
47
50
 
48
51
  /** Return the center of first tile hit, undefined if nothing was hit.
49
52
  * This does not return the exact intersection, but the center of the tile hit.
50
53
  * @param {Vector2} posStart
51
54
  * @param {Vector2} posEnd
52
- * @param {EngineObject} [object]
55
+ * @param {EngineObject} [object] - An object or undefined for generic test
56
+ * @param {boolean} [solidOnly=true] - Only check solid layers if true
53
57
  * @return {Vector2}
54
58
  * @memberof TileCollision */
55
- function tileCollisionRaycast(posStart, posEnd, object)
59
+ function tileCollisionRaycast(posStart, posEnd, object, solidOnly=true)
56
60
  {
57
- // check all tile collision layers
58
61
  for (const layer of tileCollisionLayers)
59
62
  {
60
- const hitPos = layer.collisionRaycast(posStart, posEnd, object)
61
- if (hitPos)
62
- return hitPos;
63
+ if (!solidOnly || layer.isSolid)
64
+ {
65
+ const hitPos = layer.collisionRaycast(posStart, posEnd, object)
66
+ if (hitPos)
67
+ return hitPos;
68
+ }
69
+ }
70
+ }
71
+
72
+ ///////////////////////////////////////////////////////////////////////////////
73
+ /**
74
+ * Load tile layers from exported data
75
+ * @param {Object} tileMapData - Level data from exported data
76
+ * @param {TileInfo} [tileInfo] - Default tile info (used for size and texture)
77
+ * @param {number} [renderOrder] - Render order of the top layer
78
+ * @param {number} [collisionLayer] - Layer to use for collision if any
79
+ * @param {boolean} [draw] - Should the layer be drawn automatically
80
+ * @return {Array<TileCollisionLayer>}
81
+ * @memberof TileCollision */
82
+ function tileCollisionLoad(tileMapData, tileInfo=tile(), renderOrder=0, collisionLayer, draw=true)
83
+ {
84
+ if (!tileMapData)
85
+ {
86
+ // default level data if loading failed
87
+ const s = 50;
88
+ tileMapData = {};
89
+ tileMapData.height = tileMapData.width = s;
90
+ tileMapData.layers = [{}];
91
+ tileMapData.layers[0].data = new Array(s*s).fill(0);
92
+ }
93
+
94
+ // validate the tile map data
95
+ ASSERT(tileMapData.width && tileMapData.height);
96
+ ASSERT(tileMapData.layers && tileMapData.layers.length);
97
+
98
+ // create tile layers and fill with data
99
+ const tileLayers = [];
100
+ const levelSize = vec2(tileMapData.width, tileMapData.height);
101
+ const layerCount = tileMapData.layers.length;
102
+ for (let layerIndex=layerCount; layerIndex--;)
103
+ {
104
+ const dataLayer = tileMapData.layers[layerIndex];
105
+ ASSERT(dataLayer.data && dataLayer.data.length);
106
+ ASSERT(levelSize.area() == dataLayer.data.length);
107
+
108
+ const layerRenderOrder = renderOrder - (layerCount - 1 - layerIndex);
109
+ const tileLayer = new TileCollisionLayer(vec2(), levelSize, tileInfo, layerRenderOrder);
110
+ tileLayers[layerIndex] = tileLayer;
111
+
112
+ for (let x=levelSize.x; x--;)
113
+ for (let y=levelSize.y; y--;)
114
+ {
115
+ const pos = vec2(x, levelSize.y-1-y);
116
+ const data = dataLayer.data[x + y*levelSize.x];
117
+ if (data)
118
+ {
119
+ const layerData = new TileLayerData(data-1);
120
+ tileLayer.setData(pos, layerData);
121
+
122
+ // set collision for top layer
123
+ if (layerIndex === collisionLayer)
124
+ tileLayer.setCollisionData(pos, 1);
125
+ }
126
+ }
127
+ if (draw)
128
+ tileLayer.redraw();
63
129
  }
130
+ return tileLayers;
64
131
  }
65
132
 
133
+ ///////////////////////////////////////////////////////////////////////////////
66
134
  /**
67
135
  * Tile layer data object stores info about how to draw a tile
68
136
  * @example
@@ -96,6 +164,145 @@ class TileLayerData
96
164
  clear() { this.tile = this.direction = 0; this.mirror = false; this.color = new Color; }
97
165
  }
98
166
 
167
+ ///////////////////////////////////////////////////////////////////////////////
168
+ /**
169
+ * Canvas Layer - cached off screen rendering system
170
+ * - Contains an offscreen canvas that can be rendered to
171
+ * - Webgl rendering is optional, call useWebGL to enable
172
+ * @extends EngineObject
173
+ * @example
174
+ * const canvasLayer = new CanvasLayer(vec2(), vec2(200,100));
175
+ */
176
+ class CanvasLayer extends EngineObject
177
+ {
178
+ /** Create a canvas layer object
179
+ * @param {Vector2} [position] - World space position of the layer
180
+ * @param {Vector2} [size] - World space size of the layer
181
+ * @param {number} [angle] - Angle the layer is rotated by
182
+ * @param {number} [renderOrder] - Objects sorted by renderOrder
183
+ * @param {Vector2} [canvasSize] - Default size of canvas, can be changed later
184
+ */
185
+ constructor(position, size, angle=0, renderOrder=0, canvasSize=vec2(512))
186
+ {
187
+ super(position, size, undefined, angle, WHITE, renderOrder);
188
+
189
+ /** @property {HTMLCanvasElement} - The canvas used by this layer */
190
+ this.canvas = headlessMode ? undefined : new OffscreenCanvas(canvasSize.x, canvasSize.y);
191
+ /** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this layer */
192
+ this.context = headlessMode ? undefined : this.canvas.getContext('2d');
193
+ /** @property {WebGLTexture} - Texture if using webgl for this layer, call useWebGL to enable */
194
+ this.glTexture = undefined;
195
+ this.gravityScale = 0; // disable gravity by default for canvas layers
196
+ }
197
+
198
+ /** Destroy this canvas layer */
199
+ destroy()
200
+ {
201
+ if (this.destroyed)
202
+ return;
203
+
204
+ // free up the webgl texture
205
+ if (this.glTexture)
206
+ glDeleteTexture(this.glTexture);
207
+ super.destroy();
208
+ }
209
+
210
+ // Render the layer, called automatically by the engine
211
+ render()
212
+ {
213
+ this.draw(this.pos, this.size, this.angle, this.color, this.mirror, this.additiveColor);
214
+ }
215
+
216
+ /** Draw this canvas layer centered in world space, with color applied if using WebGL
217
+ * @param {Vector2} pos - Center in world space
218
+ * @param {Vector2} [size] - Size in world space
219
+ * @param {Color} [color] - Color to modulate with
220
+ * @param {number} [angle] - Angle to rotate by
221
+ * @param {boolean} [mirror] - If true image is flipped along the Y axis
222
+ * @param {Color} [additiveColor] - Additive color to be applied if any
223
+ * @param {boolean} [screenSpace] - If true the pos and size are in screen space
224
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
225
+ * @memberof Draw */
226
+ draw(pos, size, angle=0, color=WHITE, mirror=false, additiveColor, screenSpace=false, context)
227
+ {
228
+ // draw the canvas layer as a single tile that uses the whole texture
229
+ const useWebgl = glEnable && this.glTexture != undefined;
230
+ const tileInfo = new TileInfo().setFullImage(this.canvas, this.glTexture);
231
+ drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, useWebgl, screenSpace, context);
232
+ }
233
+
234
+ /** Draw onto the layer canvas in world space (bypass webgl)
235
+ * @param {Vector2} pos
236
+ * @param {Vector2} size
237
+ * @param {number} angle
238
+ * @param {boolean} mirror
239
+ * @param {Function} drawFunction */
240
+ drawCanvas2D(pos, size, angle, mirror, drawFunction)
241
+ {
242
+ const context = this.context;
243
+ context.save();
244
+ pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
245
+ size = size.multiply(this.tileInfo.size);
246
+ context.translate(pos.x, this.canvas.height - pos.y);
247
+ context.rotate(angle);
248
+ context.scale(mirror ? -size.x : size.x, size.y);
249
+ drawFunction(context);
250
+ context.restore();
251
+ }
252
+
253
+ /** Draw a tile onto the layer canvas in world space
254
+ * @param {Vector2} pos
255
+ * @param {Vector2} [size=(1,1)]
256
+ * @param {TileInfo} [tileInfo]
257
+ * @param {Color} [color=(1,1,1,1)]
258
+ * @param {number} [angle=0]
259
+ * @param {boolean} [mirror=0] */
260
+ drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
261
+ {
262
+ this.drawCanvas2D(pos, size, angle, mirror, (context)=>
263
+ {
264
+ const textureInfo = tileInfo && tileInfo.textureInfo;
265
+ if (textureInfo)
266
+ {
267
+ context.globalAlpha = color.a; // only alpha is supported
268
+ context.drawImage(textureInfo.image,
269
+ tileInfo.pos.x, tileInfo.pos.y,
270
+ tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
271
+ context.globalAlpha = 1;
272
+ }
273
+ else
274
+ {
275
+ // untextured
276
+ context.fillStyle = color;
277
+ context.fillRect(-.5, -.5, 1, 1);
278
+ }
279
+ });
280
+ }
281
+
282
+ /** Draw a rectangle onto the layer canvas in world space
283
+ * @param {Vector2} pos
284
+ * @param {Vector2} [size=(1,1)]
285
+ * @param {Color} [color=(1,1,1,1)]
286
+ * @param {number} [angle=0] */
287
+ drawRect(pos, size, color, angle)
288
+ { this.drawTile(pos, size, undefined, color, angle); }
289
+
290
+ /** Create or update the webgl texture for this layer
291
+ * @param {boolean} [enable] - enable webgl rendering and update the texture */
292
+ useWebGL(enable=true)
293
+ {
294
+ if (glEnable && enable)
295
+ {
296
+ if (this.glTexture)
297
+ glSetTextureData(this.glTexture, this.canvas);
298
+ else
299
+ this.glTexture = glCreateTexture(this.canvas);
300
+ }
301
+ else
302
+ this.glTexture = undefined;
303
+ }
304
+ }
305
+
99
306
  ///////////////////////////////////////////////////////////////////////////////
100
307
  /**
101
308
  * Tile Layer - cached rendering system for tile layers
@@ -103,35 +310,36 @@ class TileLayerData
103
310
  * - To allow dynamic modifications, layers are rendered using canvas 2d
104
311
  * - Some devices like mobile phones are limited to 4k texture resolution
105
312
  * - For with 16x16 tiles this limits layers to 256x256 on mobile devices
106
- * @extends EngineObject
313
+ * @extends CanvasLayer
107
314
  * @example
108
315
  * const tileLayer = new TileLayer(vec2(), vec2(200,100));
109
316
  */
110
- class TileLayer extends EngineObject
317
+ class TileLayer extends CanvasLayer
111
318
  {
112
319
  /** Create a tile layer object
113
- * @param {Vector2} [position=(0,0)] - World space position
114
- * @param {Vector2} [size=(1,1)] - World space size
115
- * @param {TileInfo} [tileInfo] - Tile info for layer
116
- * @param {Vector2} [scale=(1,1)] - How much to scale this layer when rendered
117
- * @param {number} [renderOrder] - Objects are sorted by renderOrder
320
+ * @param {Vector2} position - World space position
321
+ * @param {Vector2} size - World space size
322
+ * @param {TileInfo} [tileInfo] - Default tile info for layer (used for size and texture)
323
+ * @param {Vector2} [scale=(1,1)] - How much to scale this layer when rendered
324
+ * @param {number} [renderOrder] - Objects are sorted by renderOrder
325
+ * @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
118
326
  */
119
- constructor(position, size, tileInfo=tile(), scale=vec2(1), renderOrder=0)
327
+ constructor(position, size, tileInfo=tile(), scale=vec2(1), renderOrder=0, useWebGL=glEnable)
120
328
  {
121
- super(position, size, tileInfo, 0, undefined, renderOrder);
329
+ super(position, size, 0, renderOrder, size);
330
+ this.tileInfo = tileInfo;
122
331
 
332
+ const canvasSize = size.multiply(tileInfo.size);
123
333
  /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
124
- this.canvas = document.createElement('canvas');
125
- /** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
334
+ this.canvas = new OffscreenCanvas(canvasSize.x, canvasSize.y);
335
+ /** @property {OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
126
336
  this.context = this.canvas.getContext('2d');
127
- /** @property {Vector2} - How much to scale this layer when rendered */
128
- this.scale = scale;
129
- /** @property {boolean} - If true this layer will render to overlay canvas and appear above all objects */
130
- this.isOverlay = false;
337
+ /** @property {WebGLTexture} - Texture if using webgl for this layer */
338
+ this.glTexture = useWebGL ? glCreateTexture(this.canvas) : undefined;
131
339
  // set no friction by default, applied friction is max of both objects
132
340
  this.friction = 0;
133
- // set no elasticity by default, applied elasticity is max of both objects
134
- this.elasticity = 0;
341
+ // set no restitution by default, applied restitution is max of both objects
342
+ this.restitution = 0;
135
343
 
136
344
  // init tile data
137
345
  this.data = [];
@@ -147,9 +355,10 @@ class TileLayer extends EngineObject
147
355
  this.redrawEnd = () => {};
148
356
  this.drawTileData = () => {};
149
357
  this.drawCanvas2D = () => {};
358
+ this.useWebGL = () => {};
150
359
  }
151
360
  }
152
-
361
+
153
362
  /** Set data at a given position in the array
154
363
  * @param {Vector2} layerPos - Local position in array
155
364
  * @param {TileLayerData} data - Data to set
@@ -169,28 +378,16 @@ class TileLayer extends EngineObject
169
378
  getData(layerPos)
170
379
  { return layerPos.arrayCheck(this.size) && this.data[(layerPos.y|0)*this.size.x+layerPos.x|0]; }
171
380
 
172
- // Tile layers are not updated
173
- update() {}
174
-
175
381
  // Render the tile layer, called automatically by the engine
176
382
  render()
177
383
  {
178
- ASSERT(mainContext != this.context, 'must call redrawEnd() after drawing tiles');
179
-
180
- // flush and copy gl canvas because tile canvas does not use webgl
181
- !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
182
-
183
- // draw the entire cached level onto the canvas
184
- let pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
384
+ ASSERT(drawContext != this.context, 'must call redrawEnd() after drawing tiles!');
185
385
 
186
- // fix canvas jitter in some browsers if position is not an integer
187
- pos = pos.floor();
188
-
189
- (this.isOverlay ? overlayContext : mainContext).drawImage
190
- (
191
- this.canvas, pos.x, pos.y,
192
- cameraScale*this.size.x*this.scale.x, cameraScale*this.size.y*this.scale.y
193
- );
386
+ // draw the tile layer as a single tile
387
+ const tileInfo = new TileInfo().setFullImage(this.canvas, this.glTexture);
388
+ const pos = this.pos.add(this.size.scale(.5));
389
+ const useWebgl = glEnable && this.glTexture != undefined;
390
+ drawTile(pos, this.size, tileInfo, WHITE, 0, false, CLEAR_BLACK, useWebgl);
194
391
  }
195
392
 
196
393
  /** Draw all the tile data to an offscreen canvas
@@ -202,6 +399,8 @@ class TileLayer extends EngineObject
202
399
  for (let y = this.size.y; y--;)
203
400
  this.drawTileData(vec2(x,y), false);
204
401
  this.redrawEnd();
402
+ if (this.glTexture)
403
+ this.useWebGL(); // update webgl texture
205
404
  }
206
405
 
207
406
  /** Call to start the redraw process
@@ -210,22 +409,21 @@ class TileLayer extends EngineObject
210
409
  redrawStart(clear=false)
211
410
  {
212
411
  // save current render settings
213
- /** @type {[HTMLCanvasElement, CanvasRenderingContext2D, Vector2, Vector2, number]} */
214
- this.savedRenderSettings = [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale];
412
+ /** @type {[HTMLCanvasElement|OffscreenCanvas, CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number]} */
413
+ this.savedRenderSettings = [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale];
215
414
 
216
- // use webgl rendering system to render the tiles if enabled
217
- // this works by temporally taking control of the rendering system
218
- mainCanvas = this.canvas;
219
- mainContext = this.context;
220
- mainCanvasSize = this.size.multiply(this.tileInfo.size);
415
+ // set the draw canvas and context to this layer
416
+ // use camera settings to match this layer's canvas
417
+ drawCanvas = this.canvas;
418
+ drawContext = this.context;
221
419
  cameraPos = this.size.scale(.5);
222
420
  cameraScale = this.tileInfo.size.x;
223
-
421
+ mainCanvasSize = this.size.multiply(this.tileInfo.size);
224
422
  if (clear)
225
423
  {
226
424
  // clear and set size
227
- mainCanvas.width = mainCanvasSize.x;
228
- mainCanvas.height = mainCanvasSize.y;
425
+ drawCanvas.width = mainCanvasSize.x;
426
+ drawCanvas.height = mainCanvasSize.y;
229
427
  }
230
428
 
231
429
  // disable smoothing for pixel art
@@ -238,12 +436,12 @@ class TileLayer extends EngineObject
238
436
  /** Call to end the redraw process */
239
437
  redrawEnd()
240
438
  {
241
- ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
242
- glCopyToContext(mainContext, true);
439
+ ASSERT(drawContext == this.context, 'must call redrawStart() before drawing tiles');
440
+ glCopyToContext(drawContext);
243
441
  //debugSaveCanvas(this.canvas);
244
442
 
245
443
  // set stuff back to normal
246
- [mainCanvas, mainContext, mainCanvasSize, cameraPos, cameraScale] = this.savedRenderSettings;
444
+ [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale] = this.savedRenderSettings;
247
445
  }
248
446
 
249
447
  /** Draw the tile at a given position in the tile grid
@@ -262,72 +460,16 @@ class TileLayer extends EngineObject
262
460
  this.context.clearRect(pos.x, this.canvas.height-pos.y, s.x, -s.y);
263
461
  }
264
462
 
265
- // draw the tile if not undefined
463
+ // draw the tile if it has layer data
266
464
  const d = this.getData(layerPos);
267
465
  if (d.tile != undefined)
268
466
  {
269
- ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
467
+ ASSERT(drawContext == this.context, 'must call redrawStart() before drawing tiles');
270
468
  const pos = layerPos.add(vec2(.5));
271
469
  const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex, this.tileInfo.padding);
272
470
  drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
273
471
  }
274
472
  }
275
-
276
- /** Draw directly to the 2D canvas in world space (bypass webgl)
277
- * @param {Vector2} pos
278
- * @param {Vector2} size
279
- * @param {number} angle
280
- * @param {boolean} mirror
281
- * @param {Function} drawFunction */
282
- drawCanvas2D(pos, size, angle, mirror, drawFunction)
283
- {
284
- const context = this.context;
285
- context.save();
286
- pos = pos.subtract(this.pos).multiply(this.tileInfo.size);
287
- size = size.multiply(this.tileInfo.size);
288
- context.translate(pos.x, this.canvas.height - pos.y);
289
- context.rotate(angle);
290
- context.scale(mirror ? -size.x : size.x, size.y);
291
- drawFunction(context);
292
- context.restore();
293
- }
294
-
295
- /** Draw a tile directly onto the layer canvas in world space
296
- * @param {Vector2} pos
297
- * @param {Vector2} [size=(1,1)]
298
- * @param {TileInfo} [tileInfo]
299
- * @param {Color} [color=(1,1,1,1)]
300
- * @param {number} [angle=0]
301
- * @param {boolean} [mirror=0] */
302
- drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
303
- {
304
- this.drawCanvas2D(pos, size, angle, mirror, (context)=>
305
- {
306
- const textureInfo = tileInfo && tileInfo.getTextureInfo();
307
- if (textureInfo)
308
- {
309
- context.globalAlpha = color.a; // only alpha is supported
310
- context.drawImage(textureInfo.image,
311
- tileInfo.pos.x, tileInfo.pos.y,
312
- tileInfo.size.x, tileInfo.size.y, -.5, -.5, 1, 1);
313
- context.globalAlpha = 1;
314
- }
315
- else
316
- {
317
- // untextured
318
- context.fillStyle = color;
319
- context.fillRect(-.5, -.5, 1, 1);
320
- }
321
- });
322
- }
323
-
324
- /** Draw a rectangle directly onto the layer canvas in world space
325
- * @param {Vector2} pos
326
- * @param {Vector2} [size=(1,1)]
327
- * @param {Color} [color=(1,1,1,1)]
328
- * @param {number} [angle=0] */
329
- drawRect(pos, size, color, angle)
330
- { this.drawTile(pos, size, undefined, color, angle); }
331
473
  }
332
474
 
333
475
  ///////////////////////////////////////////////////////////////////////////////
@@ -341,15 +483,16 @@ class TileLayer extends EngineObject
341
483
  class TileCollisionLayer extends TileLayer
342
484
  {
343
485
  /** Create a tile layer object
344
- * @param {Vector2} [position=(0,0)] - World space position
345
- * @param {Vector2} [size=(0,0)] - World space size
346
- * @param {TileInfo} [tileInfo] - Tile info for layer
347
- * @param {number} [renderOrder] - Objects are sorted by renderOrder
486
+ * @param {Vector2} position - World space position
487
+ * @param {Vector2} size - World space size
488
+ * @param {TileInfo} [tileInfo] - Tile info for layer
489
+ * @param {number} [renderOrder] - Objects are sorted by renderOrder
490
+ * @param {boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
348
491
  */
349
- constructor(position, size, tileInfo=tile(), renderOrder=0)
492
+ constructor(position, size, tileInfo=tile(), renderOrder=0, useWebGL=glEnable)
350
493
  {
351
494
  const scale = vec2(1); // collision layers are not scaled
352
- super(position, size.floor(), tileInfo, scale, renderOrder);
495
+ super(position, size.floor(), tileInfo, scale, renderOrder, useWebGL);
353
496
 
354
497
  /** @property {Array<number>} - The tile collision grid */
355
498
  this.collisionData = [];
@@ -357,11 +500,14 @@ class TileCollisionLayer extends TileLayer
357
500
 
358
501
  // keep track of all collision layers
359
502
  tileCollisionLayers.push(this);
503
+
504
+ // tile collision layers are solid by default
505
+ this.isSolid = true;
360
506
  }
361
507
 
362
- /** Destroy this collision layer */
508
+ /** Destroy this tile layer */
363
509
  destroy()
364
- {
510
+ {
365
511
  if (this.destroyed)
366
512
  return;
367
513
 
@@ -383,21 +529,21 @@ class TileCollisionLayer extends TileLayer
383
529
  }
384
530
 
385
531
  /** Set tile collision data for a given cell in the grid
386
- * @param {Vector2} pos
532
+ * @param {Vector2} gridPos
387
533
  * @param {number} [data] */
388
- setCollisionData(pos, data=1)
534
+ setCollisionData(gridPos, data=1)
389
535
  {
390
- const i = (pos.y|0)*this.size.x + pos.x|0;
391
- pos.arrayCheck(this.size) && (this.collisionData[i] = data);
536
+ const i = (gridPos.y|0)*this.size.x + gridPos.x|0;
537
+ gridPos.arrayCheck(this.size) && (this.collisionData[i] = data);
392
538
  }
393
539
 
394
540
  /** Get tile collision data for a given cell in the grid
395
- * @param {Vector2} pos
541
+ * @param {Vector2} gridPos
396
542
  * @return {number} */
397
- getCollisionData(pos)
543
+ getCollisionData(gridPos)
398
544
  {
399
- const i = (pos.y|0)*this.size.x + pos.x|0;
400
- return pos.arrayCheck(this.size) ? this.collisionData[i] : 0;
545
+ const i = (gridPos.y|0)*this.size.x + gridPos.x|0;
546
+ return gridPos.arrayCheck(this.size) ? this.collisionData[i] : 0;
401
547
  }
402
548
 
403
549
  /** Check if collision with another object should occur
@@ -405,18 +551,25 @@ class TileCollisionLayer extends TileLayer
405
551
  * @param {Vector2} [size=(0,0)]
406
552
  * @param {EngineObject} [object]
407
553
  * @return {boolean} */
408
- collisionTest(pos, size=vec2(), object)
554
+ collisionTest(pos, size=new Vector2, object)
409
555
  {
410
- const minX = max(pos.x - size.x/2|0, 0);
411
- const minY = max(pos.y - size.y/2|0, 0);
412
- const maxX = min(pos.x + size.x/2, this.size.x);
413
- const maxY = min(pos.y + size.y/2, this.size.y);
556
+ // transform to local layer space
557
+ const posX = pos.x - this.pos.x;
558
+ const posY = pos.y - this.pos.y;
559
+
560
+ // check any tiles in the area for collision
561
+ const minX = max(posX - size.x/2|0, 0);
562
+ const minY = max(posY - size.y/2|0, 0);
563
+ const maxX = min(posX + size.x/2, this.size.x);
564
+ const maxY = min(posY + size.y/2, this.size.y);
565
+ const hitPos = new Vector2;
414
566
  for (let y = minY; y < maxY; ++y)
415
567
  for (let x = minX; x < maxX; ++x)
416
568
  {
417
569
  // check if the object should collide with this tile
418
570
  const tileData = this.collisionData[y*this.size.x+x];
419
- if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
571
+ if (tileData)
572
+ if (!object || object.collideWithTile(tileData, hitPos.set(x, y)))
420
573
  return true;
421
574
  }
422
575
  return false;
@@ -430,18 +583,23 @@ class TileCollisionLayer extends TileLayer
430
583
  * @return {Vector2} */
431
584
  collisionRaycast(posStart, posEnd, object)
432
585
  {
586
+ // transform to local layer space
587
+ const posStartX = posStart.x - this.pos.x;
588
+ const posStartY = posStart.y - this.pos.y;
589
+ const posEndX = posEnd.x - this.pos.x;
590
+ const posEndY = posEnd.y - this.pos.y;
591
+
433
592
  // test if a ray collides with tiles from start to end
434
- // todo: a way to get the exact hit point, it must still be inside the hit tile
435
- const delta = posEnd.subtract(posStart);
436
- const totalLength = delta.length();
437
- const normalizedDelta = delta.normalize();
438
- const unit = vec2(abs(1/normalizedDelta.x), abs(1/normalizedDelta.y));
439
- const flooredPosStart = posStart.floor();
593
+ const deltaX = posEndX - posStartX;
594
+ const deltaY = posEndY - posStartY;
595
+ const totalLength = (deltaX**2 + deltaY**2)**.5;
596
+ const unitX = abs(totalLength/deltaX);
597
+ const unitY = abs(totalLength/deltaY);
440
598
 
441
599
  // setup iteration variables
442
- let pos = flooredPosStart;
443
- let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
444
- let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
600
+ const pos = posStart.floor(), signDeltaX = sign(deltaX), signDeltaY = sign(deltaY);
601
+ let xi = unitX * (deltaX < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1) || 0;
602
+ let yi = unitY * (deltaY < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1) || 0;
445
603
 
446
604
  // use line drawing algorithm to test for collisions
447
605
  while (true)
@@ -450,20 +608,21 @@ class TileCollisionLayer extends TileLayer
450
608
  const tileData = this.getCollisionData(pos);
451
609
  if (tileData && (!object || object.collideWithTile(tileData, pos)))
452
610
  {
611
+ pos.x += .5; pos.y += .5;
453
612
  debugRaycast && debugLine(posStart, posEnd, '#f00', .02);
454
- debugRaycast && debugPoint(pos.add(vec2(.5)), '#ff0');
455
- return pos.add(vec2(.5));
613
+ debugRaycast && debugPoint(pos, '#ff0');
614
+ return pos;
456
615
  }
457
616
 
458
617
  // check if past the end
459
- if (xi > totalLength && yi > totalLength)
618
+ if (xi >= totalLength && yi >= totalLength)
460
619
  break;
461
620
 
462
621
  // get coordinates of next tile to check
463
622
  if (xi > yi)
464
- pos.y += sign(delta.y), yi += unit.y;
623
+ pos.y += signDeltaY, yi += unitY;
465
624
  else
466
- pos.x += sign(delta.x), xi += unit.x;
625
+ pos.x += signDeltaX, xi += unitX;
467
626
  }
468
627
 
469
628
  debugRaycast && debugLine(posStart, posEnd, '#00f', .02);