littlejsengine 1.12.4 → 1.13.1
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.
- package/dist/box2d.wasm.js +630 -0
- package/dist/box2d.wasm.wasm +0 -0
- package/dist/littlejs.d.ts +437 -219
- package/dist/littlejs.esm.js +5950 -5307
- package/dist/littlejs.esm.min.js +4 -1
- package/dist/littlejs.js +5921 -5295
- package/dist/littlejs.min.js +4 -1
- package/dist/littlejs.release.js +5485 -4886
- package/examples/box2d/game.js +37 -42
- package/examples/box2d/gameObjects.js +41 -37
- package/examples/box2d/index.html +2 -2
- package/examples/box2d/scenes.js +24 -20
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +1 -1
- package/examples/breakout/gameObjects.js +2 -2
- package/examples/breakout/index.html +1 -1
- package/examples/breakoutTutorial/README.md +2 -2
- package/examples/breakoutTutorial/game.js +1 -1
- package/examples/breakoutTutorial/index.html +1 -1
- package/examples/empty/index.html +1 -1
- package/examples/htmlMenu/index.html +1 -1
- package/examples/index.html +410 -159
- package/examples/module/game.js +1 -1
- package/examples/module/index.html +1 -1
- package/examples/platformer/game.js +6 -15
- package/examples/platformer/gameCharacter.js +6 -6
- package/examples/platformer/gameEffects.js +74 -57
- package/examples/platformer/gameLevel.js +61 -70
- package/examples/platformer/gameObjects.js +4 -4
- package/examples/platformer/index.html +1 -1
- package/examples/puzzle/index.html +1 -1
- package/examples/shorts/base.html +24 -3
- package/examples/shorts/box2d.js +46 -0
- package/examples/shorts/box2dCar.js +45 -0
- package/examples/shorts/flappyGame.js +52 -0
- package/examples/shorts/helloWorld.js +6 -3
- package/examples/shorts/hillGlideGame.js +56 -0
- package/examples/shorts/landerGame.js +32 -0
- package/examples/shorts/maze.js +47 -0
- package/examples/shorts/medals.js +42 -0
- package/examples/shorts/nineSlice.js +21 -0
- package/examples/shorts/piano.js +52 -0
- package/examples/shorts/platformer.js +24 -20
- package/examples/shorts/{pong.js → pongGame.js} +1 -1
- package/examples/shorts/postProcess.js +45 -0
- package/examples/shorts/raycasting.js +71 -0
- package/examples/shorts/slidingPuzzle.js +42 -0
- package/examples/shorts/spaceGame.js +56 -0
- package/examples/shorts/starfield.js +17 -0
- package/examples/shorts/tileLayer.js +3 -5
- package/examples/shorts/tiles.png +0 -0
- package/examples/shorts/tiltedView.js +8 -7
- package/examples/shorts/topDown.js +18 -26
- package/examples/shorts/uiSystem.js +36 -0
- package/examples/starter/build.bat +6 -1
- package/examples/starter/game.js +4 -2
- package/examples/starter/index.html +23 -13
- package/examples/stress/index.html +2 -3
- package/examples/style.css +136 -0
- package/examples/typescript/build.js +1 -2
- package/examples/typescript/game.js +2 -2
- package/examples/typescript/game.ts +1 -1
- package/examples/typescript/index.html +1 -1
- package/examples/uiSystem/game.js +9 -25
- package/examples/uiSystem/index.html +1 -1
- package/package.json +1 -1
- package/plugins/box2d.js +29 -35
- package/plugins/drawUtilities.js +126 -0
- package/plugins/newgrounds.js +1 -1
- package/plugins/pluginExport.js +8 -2
- package/plugins/postProcess.js +2 -2
- package/plugins/uiSystem.js +162 -68
- package/plugins/zzfxm.js +1 -1
- package/reference.md +10 -10
- package/src/engine.js +59 -39
- package/src/engineAudio.js +38 -20
- package/src/engineBuild.bat +6 -1
- package/src/engineBuild.js +32 -7
- package/src/engineDebug.js +53 -26
- package/src/engineDraw.js +108 -57
- package/src/engineExport.js +22 -9
- package/src/engineInput.js +112 -40
- package/src/engineObject.js +68 -67
- package/src/engineParticles.js +26 -9
- package/src/engineSettings.js +15 -16
- package/src/engineTileLayer.js +312 -153
- package/src/engineUtilities.js +198 -130
- package/src/engineWebGL.js +58 -35
package/src/engineTileLayer.js
CHANGED
|
@@ -22,7 +22,7 @@ let tileCollisionLayers = [];
|
|
|
22
22
|
* @param {Vector2} pos
|
|
23
23
|
* @return {number}
|
|
24
24
|
* @memberof TileCollision */
|
|
25
|
-
function
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
313
|
+
* @extends CanvasLayer
|
|
107
314
|
* @example
|
|
108
315
|
* const tileLayer = new TileLayer(vec2(), vec2(200,100));
|
|
109
316
|
*/
|
|
110
|
-
class TileLayer extends
|
|
317
|
+
class TileLayer extends CanvasLayer
|
|
111
318
|
{
|
|
112
319
|
/** Create a tile layer object
|
|
113
|
-
* @param {Vector2}
|
|
114
|
-
* @param {Vector2}
|
|
115
|
-
* @param {TileInfo} [tileInfo]
|
|
116
|
-
* @param {Vector2} [scale=(1,1)]
|
|
117
|
-
* @param {number} [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,
|
|
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 =
|
|
125
|
-
/** @property {
|
|
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 {
|
|
128
|
-
this.
|
|
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
|
|
134
|
-
this.
|
|
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(
|
|
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
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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 = [
|
|
412
|
+
/** @type {[HTMLCanvasElement|OffscreenCanvas, CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D, Vector2, Vector2, number]} */
|
|
413
|
+
this.savedRenderSettings = [drawCanvas, drawContext, mainCanvasSize, cameraPos, cameraScale];
|
|
215
414
|
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
228
|
-
|
|
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(
|
|
242
|
-
glCopyToContext(
|
|
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
|
-
[
|
|
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
|
|
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(
|
|
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}
|
|
345
|
-
* @param {Vector2}
|
|
346
|
-
* @param {TileInfo} [tileInfo]
|
|
347
|
-
* @param {number} [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
|
|
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}
|
|
532
|
+
* @param {Vector2} gridPos
|
|
387
533
|
* @param {number} [data] */
|
|
388
|
-
setCollisionData(
|
|
534
|
+
setCollisionData(gridPos, data=1)
|
|
389
535
|
{
|
|
390
|
-
const i = (
|
|
391
|
-
|
|
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}
|
|
541
|
+
* @param {Vector2} gridPos
|
|
396
542
|
* @return {number} */
|
|
397
|
-
getCollisionData(
|
|
543
|
+
getCollisionData(gridPos)
|
|
398
544
|
{
|
|
399
|
-
const i = (
|
|
400
|
-
return
|
|
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=
|
|
554
|
+
collisionTest(pos, size=new Vector2, object)
|
|
409
555
|
{
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
const
|
|
413
|
-
|
|
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
|
|
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
|
-
|
|
435
|
-
const
|
|
436
|
-
const totalLength =
|
|
437
|
-
const
|
|
438
|
-
const
|
|
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
|
-
|
|
443
|
-
let xi =
|
|
444
|
-
let yi =
|
|
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
|
|
455
|
-
return pos
|
|
613
|
+
debugRaycast && debugPoint(pos, '#ff0');
|
|
614
|
+
return pos;
|
|
456
615
|
}
|
|
457
616
|
|
|
458
617
|
// check if past the end
|
|
459
|
-
if (xi
|
|
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 +=
|
|
623
|
+
pos.y += signDeltaY, yi += unitY;
|
|
465
624
|
else
|
|
466
|
-
pos.x +=
|
|
625
|
+
pos.x += signDeltaX, xi += unitX;
|
|
467
626
|
}
|
|
468
627
|
|
|
469
628
|
debugRaycast && debugLine(posStart, posEnd, '#00f', .02);
|