littlejsengine 1.1.17 → 1.2.0
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/README.md +13 -3
- package/build.bat +1 -0
- package/engine/engine.all.js +520 -522
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.release.js +444 -278
- package/engine/engine.js +68 -33
- package/engine/engineAudio.js +21 -7
- package/engine/engineBuild.bat +9 -1
- package/engine/engineBuildSetup.bat +2 -1
- package/engine/engineDebug.js +80 -246
- package/engine/engineDraw.js +106 -21
- package/engine/engineInput.js +89 -52
- package/engine/engineMedals.js +14 -17
- package/engine/engineObject.js +14 -23
- package/engine/engineParticles.js +3 -3
- package/engine/engineRelease.js +3 -1
- package/engine/engineSettings.js +39 -29
- package/engine/engineTileLayer.js +26 -24
- package/engine/engineUtilities.js +14 -12
- package/engine/engineWebGL.js +47 -56
- package/engine/index.d.ts +1612 -0
- package/examples/breakout/game.js +9 -7
- package/examples/breakout/gameObjects.js +1 -1
- package/examples/breakout/index.html +6 -4
- package/examples/platformer/game.js +2 -2
- package/examples/platformer/gameEffects.js +8 -11
- package/examples/platformer/gameLevel.js +14 -7
- package/examples/platformer/gameObjects.js +1 -1
- package/examples/platformer/gamePlayer.js +13 -8
- package/examples/platformer/index.html +9 -7
- package/examples/platformer/tiles.png +0 -0
- package/examples/puzzle/game.js +1 -1
- package/examples/puzzle/index.html +5 -3
- package/examples/stress/index.html +6 -4
- package/game.js +5 -4
- package/index.html +16 -14
- package/package.json +1 -1
package/engine/engine.all.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
LittleJS - Debug Build
|
|
3
|
+
MIT License - Copyright 2021 Frank Force
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* LittleJS Debug System
|
|
3
|
-
* <br> -
|
|
8
|
+
* <br> - Press ~ to show debug overlay with mouse pick
|
|
9
|
+
* <br> - Number keys toggle debug functions
|
|
10
|
+
* <br> - +/- apply time scale
|
|
4
11
|
* <br> - Debug primitive rendering
|
|
5
|
-
* <br> - Save
|
|
12
|
+
* <br> - Save a 2d canvas as an image
|
|
6
13
|
* @namespace Debug
|
|
7
14
|
*/
|
|
8
15
|
|
|
@@ -34,7 +41,7 @@ let showWatermark = 1;
|
|
|
34
41
|
let godMode = 0;
|
|
35
42
|
|
|
36
43
|
// Engine internal variables not exposed to documentation
|
|
37
|
-
let debugPrimitives = [], debugOverlay = 0, debugPhysics = 0, debugRaycast = 0,
|
|
44
|
+
let debugPrimitives = [], debugOverlay = 0, debugPhysics = 0, debugRaycast = 0,
|
|
38
45
|
debugParticles = 0, debugGamepads = 0, debugMedals = 0, debugTakeScreenshot, downloadLink;
|
|
39
46
|
|
|
40
47
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -156,22 +163,25 @@ const debugUpdate = ()=>
|
|
|
156
163
|
|
|
157
164
|
if (keyWasPressed(192)) // ~
|
|
158
165
|
debugOverlay = !debugOverlay;
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
166
|
+
if (debugOverlay)
|
|
167
|
+
{
|
|
168
|
+
if (keyWasPressed(48)) // 0
|
|
169
|
+
showWatermark = !showWatermark;
|
|
170
|
+
if (keyWasPressed(49)) // 1
|
|
171
|
+
debugPhysics = !debugPhysics, debugParticles = 0;
|
|
172
|
+
if (keyWasPressed(50)) // 2
|
|
173
|
+
debugParticles = !debugParticles, debugPhysics = 0;
|
|
174
|
+
if (keyWasPressed(51)) // 3
|
|
175
|
+
debugGamepads = !debugGamepads;
|
|
176
|
+
if (keyWasPressed(52)) // 4
|
|
177
|
+
godMode = !godMode;
|
|
178
|
+
if (keyWasPressed(53)) // 5
|
|
179
|
+
debugTakeScreenshot = 1;
|
|
180
|
+
//if (keyWasPressed(54)) // 6
|
|
181
|
+
//if (keyWasPressed(55)) // 7
|
|
182
|
+
//if (keyWasPressed(56)) // 8
|
|
183
|
+
//if (keyWasPressed(57)) // 9
|
|
184
|
+
}
|
|
175
185
|
}
|
|
176
186
|
|
|
177
187
|
const debugRender = ()=>
|
|
@@ -191,21 +201,20 @@ const debugRender = ()=>
|
|
|
191
201
|
|
|
192
202
|
if (debugGamepads && gamepadsEnable && navigator.getGamepads)
|
|
193
203
|
{
|
|
194
|
-
//
|
|
204
|
+
// gamepad debug display
|
|
195
205
|
const gamepads = navigator.getGamepads();
|
|
196
206
|
for (let i = gamepads.length; i--;)
|
|
197
207
|
{
|
|
198
208
|
const gamepad = gamepads[i];
|
|
199
209
|
if (gamepad)
|
|
200
210
|
{
|
|
201
|
-
// gamepad debug display
|
|
202
211
|
const stickScale = 1;
|
|
203
212
|
const buttonScale = .2;
|
|
204
213
|
const centerPos = cameraPos;
|
|
205
214
|
const sticks = stickData[i];
|
|
206
215
|
for (let j = sticks.length; j--;)
|
|
207
216
|
{
|
|
208
|
-
const drawPos = centerPos.add(vec2(j*stickScale*2,i*stickScale*3));
|
|
217
|
+
const drawPos = centerPos.add(vec2(j*stickScale*2, i*stickScale*3));
|
|
209
218
|
const stickPos = drawPos.add(sticks[j].scale(stickScale));
|
|
210
219
|
debugCircle(drawPos, stickScale, '#fff7',0,1);
|
|
211
220
|
debugLine(drawPos, stickPos, '#f00');
|
|
@@ -224,38 +233,36 @@ const debugRender = ()=>
|
|
|
224
233
|
|
|
225
234
|
if (debugOverlay)
|
|
226
235
|
{
|
|
236
|
+
// mouse pick
|
|
237
|
+
let bestDistance = Infinity, bestObject;
|
|
227
238
|
for (const o of engineObjects)
|
|
228
239
|
{
|
|
229
|
-
if (o.canvas)
|
|
240
|
+
if (o.canvas || o.destroyed)
|
|
230
241
|
continue; // skip tile layers
|
|
231
242
|
|
|
232
243
|
const size = o.size.copy();
|
|
233
|
-
size.x
|
|
234
|
-
|
|
244
|
+
if (!size.x || !size.y)
|
|
245
|
+
continue;
|
|
235
246
|
|
|
247
|
+
const distance = mousePos.distanceSquared(o.pos);
|
|
248
|
+
if (distance < bestDistance)
|
|
249
|
+
{
|
|
250
|
+
bestDistance = distance;
|
|
251
|
+
bestObject = o
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// show object info
|
|
236
255
|
const color = new Color(
|
|
237
256
|
o.collideTiles?1:0,
|
|
238
257
|
o.collideSolidObjects?1:0,
|
|
239
258
|
o.isSolid?1:0,
|
|
240
259
|
o.parent ? .2 : .5);
|
|
241
|
-
|
|
242
|
-
|
|
260
|
+
size.x = max(size.x, .2);
|
|
261
|
+
size.y = max(size.y, .2);
|
|
243
262
|
drawRect(o.pos, size, color);
|
|
244
263
|
drawRect(o.pos, size.scale(.8), o.parent ? new Color(1,1,1,.5) : new Color(0,0,0,.8));
|
|
245
264
|
o.parent && drawLine(o.pos, o.parent.pos, .1, new Color(0,0,1,.5));
|
|
246
265
|
}
|
|
247
|
-
|
|
248
|
-
// mouse pick
|
|
249
|
-
let bestDistance = Infinity, bestObject;
|
|
250
|
-
for (const o of engineObjects)
|
|
251
|
-
{
|
|
252
|
-
const distance = mousePos.distanceSquared(o.pos);
|
|
253
|
-
if (distance < bestDistance)
|
|
254
|
-
{
|
|
255
|
-
bestDistance = distance;
|
|
256
|
-
bestObject = o
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
266
|
|
|
260
267
|
if (bestObject)
|
|
261
268
|
{
|
|
@@ -275,7 +282,8 @@ const debugRender = ()=>
|
|
|
275
282
|
pos = pos.add(height), ...args);
|
|
276
283
|
drawText('vel = ' + printVec2(bestObject.velocity), pos = pos.add(height), ...args);
|
|
277
284
|
drawText('size = ' + printVec2(bestObject.size), pos = pos.add(height), ...args);
|
|
278
|
-
drawText('
|
|
285
|
+
drawText('type = ' + ( bestObject.constructor.name), pos = mousePos.subtract(height), ...args);
|
|
286
|
+
drawText('collision = ' + getTileCollisionData(mousePos), pos = mousePos.subtract(height.scale(2)), ...args);
|
|
279
287
|
mainContext = saveContext;
|
|
280
288
|
}
|
|
281
289
|
|
|
@@ -283,16 +291,15 @@ const debugRender = ()=>
|
|
|
283
291
|
}
|
|
284
292
|
|
|
285
293
|
{
|
|
286
|
-
//
|
|
287
|
-
overlayContext.
|
|
294
|
+
// draw debug primitives
|
|
295
|
+
overlayContext.save();
|
|
296
|
+
overlayContext.lineWidth = 2;
|
|
288
297
|
const pointSize = debugPointSize * cameraScale;
|
|
289
298
|
debugPrimitives.forEach(p=>
|
|
290
299
|
{
|
|
291
300
|
// create canvas transform from world space to screen space
|
|
292
301
|
const pos = worldToScreen(p.pos);
|
|
293
302
|
|
|
294
|
-
overlayContext.save();
|
|
295
|
-
overlayContext.lineWidth = 2;
|
|
296
303
|
overlayContext.translate(pos.x|0, pos.y|0);
|
|
297
304
|
overlayContext.rotate(p.angle);
|
|
298
305
|
overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
|
|
@@ -325,15 +332,17 @@ const debugRender = ()=>
|
|
|
325
332
|
p.fill && overlayContext.fill();
|
|
326
333
|
overlayContext.stroke();
|
|
327
334
|
}
|
|
328
|
-
|
|
329
|
-
overlayContext.restore();
|
|
330
335
|
});
|
|
331
336
|
|
|
332
|
-
|
|
337
|
+
// remove expired pritives
|
|
338
|
+
debugPrimitives = debugPrimitives.filter(r=>r.time.get()<0);
|
|
339
|
+
|
|
340
|
+
overlayContext.restore();
|
|
333
341
|
}
|
|
334
342
|
|
|
335
343
|
{
|
|
336
|
-
|
|
344
|
+
// draw debug overlay
|
|
345
|
+
overlayContext.save();
|
|
337
346
|
overlayContext.fillStyle = '#fff';
|
|
338
347
|
overlayContext.textAlign = 'left';
|
|
339
348
|
overlayContext.textBaseline = 'top';
|
|
@@ -341,6 +350,7 @@ const debugRender = ()=>
|
|
|
341
350
|
overlayContext.shadowColor = '#000';
|
|
342
351
|
overlayContext.shadowBlur = 9;
|
|
343
352
|
|
|
353
|
+
let x = 9, y = -20, h = 30;
|
|
344
354
|
if (debugOverlay)
|
|
345
355
|
{
|
|
346
356
|
overlayContext.fillText(engineName, x, y += h);
|
|
@@ -353,217 +363,41 @@ const debugRender = ()=>
|
|
|
353
363
|
overlayContext.fillText('1: Debug Physics', x, y += h);
|
|
354
364
|
overlayContext.fillStyle = debugParticles ? '#f00' : '#fff';
|
|
355
365
|
overlayContext.fillText('2: Debug Particles', x, y += h);
|
|
366
|
+
overlayContext.fillStyle = debugGamepads ? '#f00' : '#fff';
|
|
367
|
+
overlayContext.fillText('3: Debug Gamepads', x, y += h);
|
|
356
368
|
overlayContext.fillStyle = godMode ? '#f00' : '#fff';
|
|
357
|
-
overlayContext.fillText('
|
|
369
|
+
overlayContext.fillText('4: God Mode', x, y += h);
|
|
358
370
|
overlayContext.fillStyle = '#fff';
|
|
359
371
|
overlayContext.fillText('5: Save Screenshot', x, y += h);
|
|
360
|
-
//overlayContext.fillStyle = debugParticleEditor ? '#f00' : '#fff';
|
|
361
|
-
//overlayContext.fillText('6: Particle Editor', x, y += h);
|
|
362
|
-
overlayContext.fillStyle = debugGamepads ? '#f00' : '#fff';
|
|
363
|
-
overlayContext.fillText('7: Debug Gamepads', x, y += h);
|
|
364
|
-
}
|
|
365
|
-
else
|
|
366
|
-
{
|
|
367
|
-
overlayContext.fillText(debugPhysics ? 'Debug Physics' : '', x, y += h);
|
|
368
|
-
overlayContext.fillText(debugParticles ? 'Debug Particles' : '', x, y += h);
|
|
369
|
-
overlayContext.fillText(godMode ? 'God Mode' : '', x, y += h);
|
|
370
|
-
overlayContext.fillText(debugGamepads ? 'Debug Gamepads' : '', x, y += h);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
overlayContext.shadowBlur = 0;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
debugPrimitives = debugPrimitives.filter(r=>r.time.get()<0);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
380
|
-
// particle system editor (work in progress)
|
|
381
|
-
let debugParticleEditor = 0, debugParticleSystem, debugParticleSystemDiv, particleSystemCode;
|
|
382
|
-
|
|
383
|
-
const debugToggleParticleEditor = ()=>
|
|
384
|
-
{
|
|
385
|
-
debugParticleEditor = !debugParticleEditor;
|
|
386
|
-
|
|
387
|
-
if (debugParticleEditor)
|
|
388
|
-
{
|
|
389
|
-
if (!debugParticleSystem || debugParticleSystem.destroyed)
|
|
390
|
-
debugParticleSystem = new ParticleEmitter(cameraPos);
|
|
391
|
-
}
|
|
392
|
-
else if (debugParticleSystem && !debugParticleSystem.destroyed)
|
|
393
|
-
debugParticleSystem.destroy();
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const colorToHex = (color)=>
|
|
397
|
-
{
|
|
398
|
-
const componentToHex = (c)=>
|
|
399
|
-
{
|
|
400
|
-
const hex = (c*255|0).toString(16);
|
|
401
|
-
return hex.length == 1 ? '0' + hex : hex;
|
|
402
|
-
}
|
|
403
372
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const hexToColor = (hex)=>
|
|
407
|
-
{
|
|
408
|
-
return new Color(
|
|
409
|
-
parseInt(hex.substr(1,2), 16)/255,
|
|
410
|
-
parseInt(hex.substr(3,2), 16)/255,
|
|
411
|
-
parseInt(hex.substr(5,2), 16)/255)
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (!debugParticleSystemDiv)
|
|
415
|
-
{
|
|
416
|
-
const div = debugParticleSystemDiv = document.createElement('div');
|
|
417
|
-
div.innerHTML = '<big><b>Particle Editor';
|
|
418
|
-
div.style = 'position:absolute;top:10;left:10;color:#fff';
|
|
419
|
-
document.body.appendChild(div);
|
|
420
|
-
|
|
421
|
-
for ( const setting of debugParticleSettings)
|
|
422
|
-
{
|
|
423
|
-
const input = setting[2] = document.createElement('input');
|
|
424
|
-
const name = setting[0];
|
|
425
|
-
const type = setting[1];
|
|
426
|
-
if (type)
|
|
373
|
+
let keysPressed = '';
|
|
374
|
+
for(const i in inputData[0])
|
|
427
375
|
{
|
|
428
|
-
if (
|
|
429
|
-
|
|
430
|
-
input.type = type;
|
|
431
|
-
const color = debugParticleSystem[name];
|
|
432
|
-
input.value = colorToHex(color);
|
|
433
|
-
}
|
|
434
|
-
else if (type == 'alpha' && name == 'colorStartAlpha')
|
|
435
|
-
input.value = debugParticleSystem.colorStartA.a;
|
|
436
|
-
else if (type == 'alpha' && name == 'colorEndAlpha')
|
|
437
|
-
input.value = debugParticleSystem.colorEndA.a;
|
|
438
|
-
else if (name == 'tileSizeX')
|
|
439
|
-
input.value = debugParticleSystem.tileSize.x;
|
|
440
|
-
else if (name == 'tileSizeY')
|
|
441
|
-
input.value = debugParticleSystem.tileSize.y;
|
|
376
|
+
if (i && keyIsDown(i, 0))
|
|
377
|
+
keysPressed += i + ' ' ;
|
|
442
378
|
}
|
|
443
|
-
|
|
444
|
-
input.value = debugParticleSystem[name] || '0';
|
|
379
|
+
keysPressed && overlayContext.fillText('Keys Down: ' + keysPressed, x, y += h);
|
|
445
380
|
|
|
446
|
-
|
|
381
|
+
let buttonsPressed = '';
|
|
382
|
+
if (inputData[1])
|
|
383
|
+
for(const i in inputData[1])
|
|
447
384
|
{
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
{
|
|
451
|
-
if (type == 'color')
|
|
452
|
-
{
|
|
453
|
-
const color = hexToColor(input.value);
|
|
454
|
-
debugParticleSystem[name].r = color.r;
|
|
455
|
-
debugParticleSystem[name].g = color.g;
|
|
456
|
-
debugParticleSystem[name].b = color.b;
|
|
457
|
-
}
|
|
458
|
-
else if (type == 'alpha' && name == 'colorStartAlpha')
|
|
459
|
-
{
|
|
460
|
-
debugParticleSystem.colorStartA.a = clamp(inputFloat);
|
|
461
|
-
debugParticleSystem.colorStartB.a = clamp(inputFloat);
|
|
462
|
-
}
|
|
463
|
-
else if (type == 'alpha' && name == 'colorEndAlpha')
|
|
464
|
-
{
|
|
465
|
-
debugParticleSystem.colorEndA.a = clamp(inputFloat);
|
|
466
|
-
debugParticleSystem.colorEndB.a = clamp(inputFloat);
|
|
467
|
-
}
|
|
468
|
-
else if (name == 'tileSizeX')
|
|
469
|
-
{
|
|
470
|
-
debugParticleSystem.tileSize = vec2(parseInt(input.value), debugParticleSystem.tileSize.y);
|
|
471
|
-
}
|
|
472
|
-
else if (name == 'tileSizeY')
|
|
473
|
-
{
|
|
474
|
-
debugParticleSystem.tileSize.y = vec2(debugParticleSystem.tileSize.x, parseInt(input.value));
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
else
|
|
478
|
-
debugParticleSystem[name] = inputFloat;
|
|
479
|
-
|
|
480
|
-
updateCode();
|
|
385
|
+
if (i && keyIsDown(i, 1))
|
|
386
|
+
buttonsPressed += i + ' ' ;
|
|
481
387
|
}
|
|
482
|
-
|
|
483
|
-
div.appendChild(input);
|
|
484
|
-
div.appendChild(document.createTextNode(' ' + name));
|
|
388
|
+
buttonsPressed && overlayContext.fillText('Gamepad: ' + buttonsPressed, x, y += h);
|
|
485
389
|
}
|
|
486
|
-
|
|
487
|
-
div.appendChild(document.createElement('br'));
|
|
488
|
-
div.appendChild(document.createElement('br'));
|
|
489
|
-
div.appendChild(particleSystemCode = document.createElement('input'));
|
|
490
|
-
particleSystemCode.disabled = true;
|
|
491
|
-
div.appendChild(document.createTextNode(' code'));
|
|
492
|
-
|
|
493
|
-
div.appendChild(document.createElement('br'));
|
|
494
|
-
const button = document.createElement('button')
|
|
495
|
-
div.appendChild(button);
|
|
496
|
-
button.innerHTML = 'Copy To Clipboard';
|
|
497
|
-
|
|
498
|
-
button.onclick = (e)=> navigator.clipboard.writeText(particleSystemCode.value);
|
|
499
|
-
|
|
500
|
-
const updateCode = ()=>
|
|
390
|
+
else
|
|
501
391
|
{
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const name = setting[0];
|
|
507
|
-
const type = setting[1];
|
|
508
|
-
let value;
|
|
509
|
-
if (name == 'tileSizeX' || type == 'alpha')
|
|
510
|
-
continue;
|
|
511
|
-
|
|
512
|
-
if (count++)
|
|
513
|
-
code += ', ';
|
|
514
|
-
|
|
515
|
-
if (name == 'tileSizeY')
|
|
516
|
-
{
|
|
517
|
-
value = `vec2(${debugParticleSystem.tileSize.x},${debugParticleSystem.tileSize.y})`;
|
|
518
|
-
}
|
|
519
|
-
else if (type == 'color')
|
|
520
|
-
{
|
|
521
|
-
const c = debugParticleSystem[name];
|
|
522
|
-
value = `new Color(${c.r},${c.g},${c.b},${c.a})`;
|
|
523
|
-
}
|
|
524
|
-
else
|
|
525
|
-
value = debugParticleSystem[name];
|
|
526
|
-
code += value;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
particleSystemCode.value = '...[' + code + ']';
|
|
392
|
+
overlayContext.fillText(debugPhysics ? 'Debug Physics' : '', x, y += h);
|
|
393
|
+
overlayContext.fillText(debugParticles ? 'Debug Particles' : '', x, y += h);
|
|
394
|
+
overlayContext.fillText(godMode ? 'God Mode' : '', x, y += h);
|
|
395
|
+
overlayContext.fillText(debugGamepads ? 'Debug Gamepads' : '', x, y += h);
|
|
530
396
|
}
|
|
531
|
-
|
|
397
|
+
|
|
398
|
+
overlayContext.restore();
|
|
532
399
|
}
|
|
533
|
-
debugParticleSystemDiv.style.display = debugParticleEditor ? '' : 'none'
|
|
534
400
|
}
|
|
535
|
-
|
|
536
|
-
const debugParticleSettings =
|
|
537
|
-
[
|
|
538
|
-
['emitSize'],
|
|
539
|
-
['emitTime'],
|
|
540
|
-
['emitRate'],
|
|
541
|
-
['emitConeAngle'],
|
|
542
|
-
['tileIndex'],
|
|
543
|
-
['tileSizeX', 'tileSize'],
|
|
544
|
-
['tileSizeY', 'tileSize'],
|
|
545
|
-
['colorStartA', 'color'],
|
|
546
|
-
['colorStartB', 'color'],
|
|
547
|
-
['colorStartAlpha', 'alpha'],
|
|
548
|
-
['colorEndA', 'color'],
|
|
549
|
-
['colorEndB', 'color'],
|
|
550
|
-
['colorEndAlpha', 'alpha'],
|
|
551
|
-
['particleTime'],
|
|
552
|
-
['sizeStart'],
|
|
553
|
-
['sizeEnd'],
|
|
554
|
-
['speed'],
|
|
555
|
-
['angleSpeed'],
|
|
556
|
-
['damping'],
|
|
557
|
-
['angleDamping'],
|
|
558
|
-
['gravityScale'],
|
|
559
|
-
['particleConeAngle'],
|
|
560
|
-
['fadeRate'],
|
|
561
|
-
['randomness'],
|
|
562
|
-
['collideTiles'],
|
|
563
|
-
['additive'],
|
|
564
|
-
['randomColorComponents'],
|
|
565
|
-
['renderOrder'],
|
|
566
|
-
];
|
|
567
401
|
/**
|
|
568
402
|
* LittleJS Utility Classes and Functions
|
|
569
403
|
* <br> - General purpose math library
|
|
@@ -615,27 +449,27 @@ const mod = (a, b=1)=> ((a % b) + b) % b;
|
|
|
615
449
|
|
|
616
450
|
/** Clamps the value beween max and min
|
|
617
451
|
* @param {Number} value
|
|
618
|
-
* @param {Number} [max=1]
|
|
619
452
|
* @param {Number} [min=0]
|
|
453
|
+
* @param {Number} [max=1]
|
|
620
454
|
* @return {Number}
|
|
621
455
|
* @memberof Utilities */
|
|
622
|
-
const clamp = (v,
|
|
456
|
+
const clamp = (v, min=0, max=1)=> v < min ? min : v > max ? max : v;
|
|
623
457
|
|
|
624
458
|
/** Returns what percentage the value is between max and min
|
|
625
459
|
* @param {Number} value
|
|
626
|
-
* @param {Number} [max=1]
|
|
627
460
|
* @param {Number} [min=0]
|
|
461
|
+
* @param {Number} [max=1]
|
|
628
462
|
* @return {Number}
|
|
629
463
|
* @memberof Utilities */
|
|
630
|
-
const percent = (v,
|
|
464
|
+
const percent = (v, min=0, max=1)=> max-min ? clamp((v-min) / (max-min)) : 0;
|
|
631
465
|
|
|
632
466
|
/** Linearly interpolates the percent value between max and min
|
|
633
467
|
* @param {Number} percent
|
|
634
|
-
* @param {Number} [max=1]
|
|
635
468
|
* @param {Number} [min=0]
|
|
469
|
+
* @param {Number} [max=1]
|
|
636
470
|
* @return {Number}
|
|
637
471
|
* @memberof Utilities */
|
|
638
|
-
const lerp = (p,
|
|
472
|
+
const lerp = (p, min=0, max=1)=> min + clamp(p) * (max-min);
|
|
639
473
|
|
|
640
474
|
/** Applies smoothstep function to the percentage value
|
|
641
475
|
* @param {Number} value
|
|
@@ -650,11 +484,11 @@ const smoothStep = (p)=> p * p * (3 - 2 * p);
|
|
|
650
484
|
const nearestPowerOfTwo = (v)=> 2**Math.ceil(Math.log2(v));
|
|
651
485
|
|
|
652
486
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
653
|
-
* @param {Vector2} pointA
|
|
654
|
-
* @param {Vector2} sizeA
|
|
655
|
-
* @param {Vector2} pointB
|
|
656
|
-
* @param {Vector2} sizeB
|
|
657
|
-
* @return {Boolean}
|
|
487
|
+
* @param {Vector2} pointA - Center of box A
|
|
488
|
+
* @param {Vector2} sizeA - Size of box A
|
|
489
|
+
* @param {Vector2} pointB - Center of box B
|
|
490
|
+
* @param {Vector2} [sizeB] - Size of box B
|
|
491
|
+
* @return {Boolean} - True if overlapping
|
|
658
492
|
* @memberof Utilities */
|
|
659
493
|
const isOverlapping = (pA, sA, pB, sB)=> abs(pA.x - pB.x)*2 < sA.x + sB.x & abs(pA.y - pB.y)*2 < sA.y + sB.y;
|
|
660
494
|
|
|
@@ -751,10 +585,12 @@ const vec2 = (x=0, y)=> x.x == undefined ? new Vector2(x, y == undefined? x : y)
|
|
|
751
585
|
|
|
752
586
|
/**
|
|
753
587
|
* 2D Vector object with vector math library
|
|
588
|
+
* <br> - Functions do not change this so they can be chained together
|
|
754
589
|
* @example
|
|
755
590
|
* let a = new Vector2(2, 3); // vector with coordinates (2, 3)
|
|
756
591
|
* let b = new Vector2; // vector with coordinates (0, 0)
|
|
757
592
|
* let c = vec2(4, 2); // use the vec2 function to make a Vector2
|
|
593
|
+
* let d = a.add(b).scale(5); // operators can be chained
|
|
758
594
|
*/
|
|
759
595
|
class Vector2
|
|
760
596
|
{
|
|
@@ -1041,7 +877,7 @@ class Timer
|
|
|
1041
877
|
|
|
1042
878
|
/** Get percentage elapsed based on time it was set to, returns 0 if not set
|
|
1043
879
|
* @return {Number} */
|
|
1044
|
-
getPercent() { return this.isSet()? percent(this.time - time,
|
|
880
|
+
getPercent() { return this.isSet()? percent(this.time - time, this.setTime, 0) : 0; }
|
|
1045
881
|
}
|
|
1046
882
|
/**
|
|
1047
883
|
* LittleJS Engine Settings
|
|
@@ -1057,7 +893,7 @@ class Timer
|
|
|
1057
893
|
* @type {Vector2}
|
|
1058
894
|
* @default
|
|
1059
895
|
* @memberof Settings */
|
|
1060
|
-
|
|
896
|
+
let canvasMaxSize = vec2(1920, 1200);
|
|
1061
897
|
|
|
1062
898
|
/** Fixed size of the canvas, if enabled cavnvas size never changes
|
|
1063
899
|
* @type {Vector2}
|
|
@@ -1082,12 +918,12 @@ let fontDefault = 'arial';
|
|
|
1082
918
|
* @type {Vector2}
|
|
1083
919
|
* @default
|
|
1084
920
|
* @memberof Settings */
|
|
1085
|
-
|
|
921
|
+
let tileSizeDefault = vec2(16);
|
|
1086
922
|
|
|
1087
923
|
/** Prevent tile bleeding from neighbors in pixels
|
|
1088
924
|
* @default
|
|
1089
925
|
* @memberof Settings */
|
|
1090
|
-
|
|
926
|
+
let tileFixBleedScale = .3;
|
|
1091
927
|
|
|
1092
928
|
///////////////////////////////////////////////////////////////////////////////
|
|
1093
929
|
// Object settings
|
|
@@ -1096,43 +932,48 @@ const tileBleedShrinkFix = .3;
|
|
|
1096
932
|
* @type {Vector2}
|
|
1097
933
|
* @default
|
|
1098
934
|
* @memberof Settings */
|
|
1099
|
-
|
|
935
|
+
let objectDefaultSize = vec2(1);
|
|
1100
936
|
|
|
1101
937
|
/** Default object mass for collison calcuations (how heavy objects are)
|
|
1102
938
|
* @default
|
|
1103
939
|
* @memberof Settings */
|
|
1104
|
-
|
|
940
|
+
let objectDefaultMass = 1;
|
|
1105
941
|
|
|
1106
942
|
/** How much to slow velocity by each frame (0-1)
|
|
1107
943
|
* @default
|
|
1108
944
|
* @memberof Settings */
|
|
1109
|
-
|
|
945
|
+
let objectDefaultDamping = .99;
|
|
1110
946
|
|
|
1111
947
|
/** How much to slow angular velocity each frame (0-1)
|
|
1112
948
|
* @default
|
|
1113
949
|
* @memberof Settings */
|
|
1114
|
-
|
|
950
|
+
let objectDefaultAngleDamping = .99;
|
|
1115
951
|
|
|
1116
952
|
/** How much to bounce when a collision occurs (0-1)
|
|
1117
953
|
* @default
|
|
1118
954
|
* @memberof Settings */
|
|
1119
|
-
|
|
955
|
+
let objectDefaultElasticity = 0;
|
|
1120
956
|
|
|
1121
957
|
/** How much to slow when touching (0-1)
|
|
1122
958
|
* @default
|
|
1123
959
|
* @memberof Settings */
|
|
1124
|
-
|
|
960
|
+
let objectDefaultFriction = .8;
|
|
1125
961
|
|
|
1126
962
|
/** Clamp max speed to avoid fast objects missing collisions
|
|
1127
963
|
* @default
|
|
1128
964
|
* @memberof Settings */
|
|
1129
|
-
|
|
965
|
+
let objectMaxSpeed = 1;
|
|
1130
966
|
|
|
1131
967
|
/** How much gravity to apply to objects along the Y axis, negative is down
|
|
1132
968
|
* @default
|
|
1133
969
|
* @memberof Settings */
|
|
1134
970
|
let gravity = 0;
|
|
1135
971
|
|
|
972
|
+
/** Scales emit rate of particles, useful for low graphics mode (0 disables particle emitters)
|
|
973
|
+
* @default
|
|
974
|
+
* @memberof Settings */
|
|
975
|
+
let particleEmitRateScale = 1;
|
|
976
|
+
|
|
1136
977
|
///////////////////////////////////////////////////////////////////////////////
|
|
1137
978
|
// Camera settings
|
|
1138
979
|
|
|
@@ -1153,7 +994,7 @@ let cameraScale = max(tileSizeDefault.x, tileSizeDefault.y);
|
|
|
1153
994
|
/** Enable webgl rendering, webgl can be disabled and removed from build (with some features disabled)
|
|
1154
995
|
* @default
|
|
1155
996
|
* @memberof Settings */
|
|
1156
|
-
|
|
997
|
+
let glEnable = 1;
|
|
1157
998
|
|
|
1158
999
|
/** Fixes slow rendering in some browsers by not compositing the WebGL canvas
|
|
1159
1000
|
* @default
|
|
@@ -1166,34 +1007,39 @@ let glOverlay = 1;
|
|
|
1166
1007
|
/** Should gamepads be allowed
|
|
1167
1008
|
* @default
|
|
1168
1009
|
* @memberof Settings */
|
|
1169
|
-
|
|
1010
|
+
let gamepadsEnable = 1;
|
|
1170
1011
|
|
|
1171
1012
|
/** If true, the dpad input is also routed to the left analog stick (for better accessability)
|
|
1172
1013
|
* @default
|
|
1173
1014
|
* @memberof Settings */
|
|
1174
|
-
|
|
1015
|
+
let gamepadDirectionEmulateStick = 1;
|
|
1175
1016
|
|
|
1176
1017
|
/** If true the WASD keys are also routed to the direction keys (for better accessability)
|
|
1177
1018
|
* @default
|
|
1178
1019
|
* @memberof Settings */
|
|
1179
|
-
|
|
1020
|
+
let inputWASDEmulateDirection = 1;
|
|
1180
1021
|
|
|
1181
|
-
/**
|
|
1022
|
+
/** True if touch gamepad should appear on mobile devices
|
|
1023
|
+
* <br> - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
1024
|
+
* <br> - Must be set by end of gameInit to be activated
|
|
1182
1025
|
* @default
|
|
1183
1026
|
* @memberof Settings */
|
|
1184
|
-
|
|
1027
|
+
let touchGamepadEnable = 0;
|
|
1185
1028
|
|
|
1186
|
-
/** Size of virutal gamepad for touch devices in pixels
|
|
1187
|
-
* <br> - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
1188
|
-
* <br> - Must be set by end of gameInit to be activated
|
|
1029
|
+
/** Size of virutal gamepad for touch devices in pixels
|
|
1189
1030
|
* @default
|
|
1190
1031
|
* @memberof Settings */
|
|
1191
|
-
let touchGamepadSize =
|
|
1032
|
+
let touchGamepadSize = 80;
|
|
1192
1033
|
|
|
1193
1034
|
/** Transparency of touch gamepad overlay
|
|
1194
1035
|
* @default
|
|
1195
1036
|
* @memberof Settings */
|
|
1196
|
-
let touchGamepadAlpha = .
|
|
1037
|
+
let touchGamepadAlpha = .3;
|
|
1038
|
+
|
|
1039
|
+
/** Allow vibration hardware if it exists
|
|
1040
|
+
* @default
|
|
1041
|
+
* @memberof Settings */
|
|
1042
|
+
let vibrateEnable = 1;
|
|
1197
1043
|
|
|
1198
1044
|
///////////////////////////////////////////////////////////////////////////////
|
|
1199
1045
|
// Audio settings
|
|
@@ -1206,17 +1052,17 @@ let soundVolume = .5;
|
|
|
1206
1052
|
/** All audio code can be disabled and removed from build
|
|
1207
1053
|
* @default
|
|
1208
1054
|
* @memberof Settings */
|
|
1209
|
-
|
|
1055
|
+
let soundEnable = 1;
|
|
1210
1056
|
|
|
1211
1057
|
/** Default range where sound no longer plays
|
|
1212
1058
|
* @default
|
|
1213
1059
|
* @memberof Settings */
|
|
1214
|
-
|
|
1060
|
+
let soundDefaultRange = 30;
|
|
1215
1061
|
|
|
1216
1062
|
/** Default range percent to start tapering off sound (0-1)
|
|
1217
1063
|
* @default
|
|
1218
1064
|
* @memberof Settings */
|
|
1219
|
-
|
|
1065
|
+
let soundDefaultTaper = .7;
|
|
1220
1066
|
|
|
1221
1067
|
///////////////////////////////////////////////////////////////////////////////
|
|
1222
1068
|
// Medals settings
|
|
@@ -1224,27 +1070,27 @@ const soundDefaultTaper = .7;
|
|
|
1224
1070
|
/** How long to show medals for in seconds
|
|
1225
1071
|
* @default
|
|
1226
1072
|
* @memberof Settings */
|
|
1227
|
-
|
|
1073
|
+
let medalDisplayTime = 5;
|
|
1228
1074
|
|
|
1229
1075
|
/** How quickly to slide on/off medals in seconds
|
|
1230
1076
|
* @default
|
|
1231
1077
|
* @memberof Settings */
|
|
1232
|
-
|
|
1078
|
+
let medalDisplaySlideTime = .5;
|
|
1233
1079
|
|
|
1234
1080
|
/** Width of medal display
|
|
1235
1081
|
* @default
|
|
1236
1082
|
* @memberof Settings */
|
|
1237
|
-
|
|
1083
|
+
let medalDisplayWidth = 640;
|
|
1238
1084
|
|
|
1239
1085
|
/** Height of medal display
|
|
1240
1086
|
* @default
|
|
1241
1087
|
* @memberof Settings */
|
|
1242
|
-
|
|
1088
|
+
let medalDisplayHeight = 80;
|
|
1243
1089
|
|
|
1244
1090
|
/** Size of icon in medal display
|
|
1245
1091
|
* @default
|
|
1246
1092
|
* @memberof Settings */
|
|
1247
|
-
|
|
1093
|
+
let medalDisplayIconSize = 50;
|
|
1248
1094
|
/*
|
|
1249
1095
|
LittleJS Object System
|
|
1250
1096
|
*/
|
|
@@ -1279,7 +1125,7 @@ const medalDisplayIconSize = 80;
|
|
|
1279
1125
|
class EngineObject
|
|
1280
1126
|
{
|
|
1281
1127
|
/** Create an engine object and adds it to the list of objects
|
|
1282
|
-
* @param {Vector2} [position=new Vector2(
|
|
1128
|
+
* @param {Vector2} [position=new Vector2()] - World space position of the object
|
|
1283
1129
|
* @param {Vector2} [size=objectDefaultSize] - World space size of the object
|
|
1284
1130
|
* @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
|
|
1285
1131
|
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
@@ -1324,12 +1170,15 @@ class EngineObject
|
|
|
1324
1170
|
this.gravityScale = 1;
|
|
1325
1171
|
/** @property {Number} [renderOrder=0] - Objects are sorted by render order */
|
|
1326
1172
|
this.renderOrder = renderOrder;
|
|
1173
|
+
/** @property {Vector2} [velocity=new Vector2()] - Velocity of the object */
|
|
1174
|
+
this.velocity = new Vector2();
|
|
1175
|
+
/** @property {Number} [angleVelocity=0] - Angular velocity of the object */
|
|
1176
|
+
this.angleVelocity = 0;
|
|
1327
1177
|
|
|
1328
1178
|
// init other internal object stuff
|
|
1329
1179
|
this.spawnTime = time;
|
|
1330
|
-
this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
|
|
1331
|
-
this.collideTiles = 1;
|
|
1332
1180
|
this.children = [];
|
|
1181
|
+
this.collideTiles = 1;
|
|
1333
1182
|
|
|
1334
1183
|
// add to list of objects
|
|
1335
1184
|
engineObjects.push(this);
|
|
@@ -1348,8 +1197,8 @@ class EngineObject
|
|
|
1348
1197
|
}
|
|
1349
1198
|
|
|
1350
1199
|
// limit max speed to prevent missing collisions
|
|
1351
|
-
this.velocity.x = clamp(this.velocity.x, objectMaxSpeed,
|
|
1352
|
-
this.velocity.y = clamp(this.velocity.y, objectMaxSpeed,
|
|
1200
|
+
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
1201
|
+
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
1353
1202
|
|
|
1354
1203
|
// apply physics
|
|
1355
1204
|
const oldPos = this.pos.copy();
|
|
@@ -1397,7 +1246,7 @@ class EngineObject
|
|
|
1397
1246
|
// if already was touching, try to push away
|
|
1398
1247
|
const deltaPos = oldPos.subtract(o.pos);
|
|
1399
1248
|
const length = deltaPos.length();
|
|
1400
|
-
const pushAwayAccel = .001; // push away if
|
|
1249
|
+
const pushAwayAccel = .001; // push away if already overlapping
|
|
1401
1250
|
const velocity = length < .01 ? randVector(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
|
|
1402
1251
|
this.velocity = this.velocity.add(velocity);
|
|
1403
1252
|
if (o.mass) // push away if not fixed
|
|
@@ -1439,8 +1288,8 @@ class EngineObject
|
|
|
1439
1288
|
|
|
1440
1289
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1441
1290
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1442
|
-
this.velocity.y = lerp(elasticity,
|
|
1443
|
-
o.velocity.y = lerp(elasticity,
|
|
1291
|
+
this.velocity.y = lerp(elasticity, inelastic, elastic0);
|
|
1292
|
+
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
1444
1293
|
}
|
|
1445
1294
|
}
|
|
1446
1295
|
if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
|
|
@@ -1460,8 +1309,8 @@ class EngineObject
|
|
|
1460
1309
|
|
|
1461
1310
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1462
1311
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1463
|
-
this.velocity.x = lerp(elasticity,
|
|
1464
|
-
o.velocity.x = lerp(elasticity,
|
|
1312
|
+
this.velocity.x = lerp(elasticity, inelastic, elastic0);
|
|
1313
|
+
o.velocity.x = lerp(elasticity, inelastic, elastic1);
|
|
1465
1314
|
}
|
|
1466
1315
|
else // bounce if other object is fixed
|
|
1467
1316
|
this.velocity.x *= -this.elasticity;
|
|
@@ -1491,7 +1340,7 @@ class EngineObject
|
|
|
1491
1340
|
|
|
1492
1341
|
// adjust next velocity to settle on ground
|
|
1493
1342
|
const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
|
|
1494
|
-
if (o < 0 && o >
|
|
1343
|
+
if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
|
|
1495
1344
|
this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
|
|
1496
1345
|
|
|
1497
1346
|
// move to previous position
|
|
@@ -1592,18 +1441,6 @@ class EngineObject
|
|
|
1592
1441
|
{
|
|
1593
1442
|
ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
|
|
1594
1443
|
|
|
1595
|
-
// track collidable objects in separate list
|
|
1596
|
-
if (collideSolidObjects && !this.collideSolidObjects)
|
|
1597
|
-
{
|
|
1598
|
-
ASSERT(!engineObjectsCollide.includes(this));
|
|
1599
|
-
engineObjectsCollide.push(this);
|
|
1600
|
-
}
|
|
1601
|
-
else if (!collideSolidObjects && this.collideSolidObjects)
|
|
1602
|
-
{
|
|
1603
|
-
ASSERT(engineObjectsCollide.includes(this))
|
|
1604
|
-
engineObjectsCollide.splice(engineObjectsCollide.indexOf(this), 1);
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
1444
|
this.collideSolidObjects = collideSolidObjects;
|
|
1608
1445
|
this.isSolid = isSolid;
|
|
1609
1446
|
this.collideTiles = collideTiles;
|
|
@@ -1703,17 +1540,13 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
|
|
|
1703
1540
|
{
|
|
1704
1541
|
// calculate uvs and render
|
|
1705
1542
|
const cols = tileImageSize.x / tileSize.x |0;
|
|
1706
|
-
const uvSizeX = tileSize.x
|
|
1707
|
-
const uvSizeY = tileSize.y
|
|
1543
|
+
const uvSizeX = tileSize.x / tileImageSize.x;
|
|
1544
|
+
const uvSizeY = tileSize.y / tileImageSize.y;
|
|
1708
1545
|
const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
|
|
1709
|
-
|
|
1710
|
-
// shrink uvs to prevent bleeding
|
|
1711
|
-
const shrinkTilesX = tileBleedShrinkFix * tileImageSizeInverse.x;
|
|
1712
|
-
const shrinkTilesY = tileBleedShrinkFix * tileImageSizeInverse.y;
|
|
1713
1546
|
|
|
1714
1547
|
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
1715
|
-
uvX +
|
|
1716
|
-
uvX -
|
|
1548
|
+
uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
|
|
1549
|
+
uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
|
|
1717
1550
|
color.rgbaInt(), additiveColor.rgbaInt());
|
|
1718
1551
|
}
|
|
1719
1552
|
}
|
|
@@ -1732,10 +1565,10 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
|
|
|
1732
1565
|
{
|
|
1733
1566
|
// calculate uvs and render
|
|
1734
1567
|
const cols = tileImageSize.x / tileSize.x |0;
|
|
1735
|
-
const sX = (tileIndex%cols)*tileSize.x +
|
|
1736
|
-
const sY = (tileIndex/cols|0)*tileSize.y +
|
|
1737
|
-
const sWidth = tileSize.x - 2*
|
|
1738
|
-
const sHeight = tileSize.y - 2*
|
|
1568
|
+
const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
|
|
1569
|
+
const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
|
|
1570
|
+
const sWidth = tileSize.x - 2*tileFixBleedScale;
|
|
1571
|
+
const sHeight = tileSize.y - 2*tileFixBleedScale;
|
|
1739
1572
|
context.globalAlpha = color.a; // only alpha is supported
|
|
1740
1573
|
context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
|
|
1741
1574
|
}
|
|
@@ -1818,6 +1651,18 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
|
|
|
1818
1651
|
context.restore();
|
|
1819
1652
|
}
|
|
1820
1653
|
|
|
1654
|
+
/** Enable normal or additive blend mode
|
|
1655
|
+
* @param {Boolean} [additive=0]
|
|
1656
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1657
|
+
* @memberof Draw */
|
|
1658
|
+
function setBlendMode(additive, useWebGL=glEnable)
|
|
1659
|
+
{
|
|
1660
|
+
if (glEnable && useWebGL)
|
|
1661
|
+
glSetBlendMode(additive);
|
|
1662
|
+
else
|
|
1663
|
+
mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1821
1666
|
/** Draw text on overlay canvas in world space
|
|
1822
1667
|
* @param {String} text
|
|
1823
1668
|
* @param {Vector2} pos
|
|
@@ -1843,16 +1688,93 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
|
|
|
1843
1688
|
overlayContext.fillText(text, pos.x, pos.y);
|
|
1844
1689
|
}
|
|
1845
1690
|
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
*
|
|
1850
|
-
|
|
1691
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1692
|
+
|
|
1693
|
+
/**
|
|
1694
|
+
* Font Image Object - Draw text on a 2D canvas by using characters in an image
|
|
1695
|
+
* <br> - 96 characters (from space to tilde) are stored in an image
|
|
1696
|
+
* <br> - Uses a default 8x8 font if none is supplied
|
|
1697
|
+
* <br> - You can also use fonts from the main tile sheet
|
|
1698
|
+
* @example
|
|
1699
|
+
* // use built in font
|
|
1700
|
+
* const font = new ImageFont;
|
|
1701
|
+
*
|
|
1702
|
+
* // draw text
|
|
1703
|
+
* font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
|
|
1704
|
+
*/
|
|
1705
|
+
class FontImage
|
|
1851
1706
|
{
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1707
|
+
/** Create an image font
|
|
1708
|
+
* @param {Image} [image] - The image the font is stored in, if undefined the default font is used
|
|
1709
|
+
* @param {Vector2} [tileSize=vec2(8)] - The size of the font source tiles
|
|
1710
|
+
* @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
|
|
1711
|
+
* @param {Number} [startTileIndex=0] - Tile index in image where font starts
|
|
1712
|
+
* @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
1713
|
+
*/
|
|
1714
|
+
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
|
|
1715
|
+
{
|
|
1716
|
+
if (!image)
|
|
1717
|
+
{
|
|
1718
|
+
// use default font image
|
|
1719
|
+
image = new Image();
|
|
1720
|
+
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6MCIBdFY+Q0PMNgf0yCgsSAaZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWw1hPWNRcEyWh2npdpBmSUBrRaN1WSlQAWXaWLF0kkwR7C/N7XUvIaQr4xpeqeQigjLwBRZ/6KkufAACcv5z7GYhvgWHII8xN/4C2hvLCkmZSVgIJMHNTxpYOsJOiitEjUQnIQAFFP+opoQfCbjWmfzWiNzCjEvxAPFARmw/Y5Te0sl2zEp4M+m7cToJjdJS7y1xcFlFT7EtWBZ8nl7uLSff7UgYewtlyY6yHM4RrjNrnsfn/W0DBK9xNZctZ7gsTeXTe5eqLqXFlKnAn+vkNXzH6TqPW10ksKoSlauKk1TcncZLSTxNnQ8Av/8RGPXOFIuMCZ/fpwIZx5vU4QIRQ44HwUyHx/QrZhm5VtXFex7ja2se0KBpnPT0DSwS4xd86uAy+SqnLBgkPY5JolhJ33ENyK6n0VyRzCnzebIkFdY/NUtM66lTdhix1KUss6q5snH5ftVJXG0QITb1gUDvzD3z/BbyOLMirwCtPzAAAAAElFTkSuQmCC';
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
this.image = image;
|
|
1724
|
+
this.tileSize = tileSize;
|
|
1725
|
+
this.paddingSize = paddingSize;
|
|
1726
|
+
this.startTileIndex = startTileIndex;
|
|
1727
|
+
this.context = context;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
/** Draw text in screen space using the image font
|
|
1731
|
+
* @param {String} text
|
|
1732
|
+
* @param {Vector2} pos
|
|
1733
|
+
* @param {Number} [scale=4]
|
|
1734
|
+
* @param {Boolean} [center]
|
|
1735
|
+
*/
|
|
1736
|
+
drawTextScreen(text, pos, scale=4, center)
|
|
1737
|
+
{
|
|
1738
|
+
const context = this.context;
|
|
1739
|
+
context.save();
|
|
1740
|
+
context.imageSmoothingEnabled = !cavasPixelated;
|
|
1741
|
+
|
|
1742
|
+
const size = this.tileSize;
|
|
1743
|
+
const drawSize = size.add(this.paddingSize).scale(scale);
|
|
1744
|
+
const cols = this.image.width / this.tileSize.x |0;
|
|
1745
|
+
text.split('\n').forEach((line, i)=>
|
|
1746
|
+
{
|
|
1747
|
+
const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
|
|
1748
|
+
for(let j=line.length; j--;)
|
|
1749
|
+
{
|
|
1750
|
+
// draw each character
|
|
1751
|
+
let charCode = line[j].charCodeAt();
|
|
1752
|
+
if (charCode < 32 || charCode > 127)
|
|
1753
|
+
charCode = 127; // unknown character
|
|
1754
|
+
|
|
1755
|
+
// get the character source location and draw it
|
|
1756
|
+
const tile = this.startTileIndex + charCode - 32;
|
|
1757
|
+
const x = tile % cols;
|
|
1758
|
+
const y = tile / cols |0;
|
|
1759
|
+
const drawPos = pos.add(vec2(j,i).multiply(drawSize));
|
|
1760
|
+
context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
|
|
1761
|
+
drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
|
|
1762
|
+
}
|
|
1763
|
+
});
|
|
1764
|
+
|
|
1765
|
+
context.restore();
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
/** Draw text in world space using the image font
|
|
1769
|
+
* @param {String} text
|
|
1770
|
+
* @param {Vector2} pos
|
|
1771
|
+
* @param {Number} [scale=.25]
|
|
1772
|
+
* @param {Boolean} [center]
|
|
1773
|
+
*/
|
|
1774
|
+
drawText(text, pos, scale=1, center)
|
|
1775
|
+
{
|
|
1776
|
+
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
|
|
1777
|
+
}
|
|
1856
1778
|
}
|
|
1857
1779
|
|
|
1858
1780
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1916,7 +1838,7 @@ const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][
|
|
|
1916
1838
|
|
|
1917
1839
|
/** Clears all input
|
|
1918
1840
|
* @memberof Input */
|
|
1919
|
-
const clearInput = ()=> inputData
|
|
1841
|
+
const clearInput = ()=> inputData = [[]];
|
|
1920
1842
|
|
|
1921
1843
|
/** Returns true if mouse button is down
|
|
1922
1844
|
* @param {Number} button
|
|
@@ -1985,7 +1907,9 @@ const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepa
|
|
|
1985
1907
|
///////////////////////////////////////////////////////////////////////////////
|
|
1986
1908
|
// Input update called by engine
|
|
1987
1909
|
|
|
1988
|
-
|
|
1910
|
+
// store input as a bit field for each key: 1 = isDown, 2 = wasPressed, 4 = wasReleased
|
|
1911
|
+
// mouse and keyboard are stored together in device 0, gamepads are in devices > 0
|
|
1912
|
+
let inputData = [[]];
|
|
1989
1913
|
|
|
1990
1914
|
function inputUpdate()
|
|
1991
1915
|
{
|
|
@@ -2011,36 +1935,38 @@ function inputUpdatePost()
|
|
|
2011
1935
|
///////////////////////////////////////////////////////////////////////////////
|
|
2012
1936
|
// Keyboard event handlers
|
|
2013
1937
|
|
|
2014
|
-
onkeydown = e=>
|
|
1938
|
+
onkeydown = (e)=>
|
|
2015
1939
|
{
|
|
2016
1940
|
if (debug && e.target != document.body) return;
|
|
2017
1941
|
e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
|
|
2018
1942
|
debug || e.preventDefault();
|
|
2019
1943
|
}
|
|
2020
|
-
onkeyup = e=>
|
|
1944
|
+
onkeyup = (e)=>
|
|
2021
1945
|
{
|
|
2022
1946
|
if (debug && e.target != document.body) return;
|
|
2023
1947
|
inputData[0][remapKeyCode(e.keyCode)] = 4;
|
|
2024
1948
|
}
|
|
2025
|
-
const remapKeyCode = c=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
1949
|
+
const remapKeyCode = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
2026
1950
|
|
|
2027
1951
|
///////////////////////////////////////////////////////////////////////////////
|
|
2028
1952
|
// Mouse event handlers
|
|
2029
1953
|
|
|
2030
|
-
onmousedown = e=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
2031
|
-
onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2032
|
-
onmousemove = e=>
|
|
1954
|
+
onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
1955
|
+
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
1956
|
+
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
1957
|
+
onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
1958
|
+
oncontextmenu = (e)=> !1; // prevent right click menu
|
|
1959
|
+
|
|
1960
|
+
// convert a mouse or touch event position to screen space
|
|
1961
|
+
const mouseToScreen = (mousePos)=>
|
|
2033
1962
|
{
|
|
2034
1963
|
if (!mainCanvas)
|
|
2035
|
-
return; // fix bug that can occur if user clicks before page loads
|
|
1964
|
+
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
2036
1965
|
|
|
2037
|
-
// convert mouse pos to canvas space
|
|
2038
1966
|
const rect = mainCanvas.getBoundingClientRect();
|
|
2039
|
-
|
|
2040
|
-
vec2(percent(
|
|
1967
|
+
return mainCanvasSize.multiply(
|
|
1968
|
+
vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
|
|
2041
1969
|
}
|
|
2042
|
-
onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
2043
|
-
oncontextmenu = e=> !1; // prevent right click menu
|
|
2044
1970
|
|
|
2045
1971
|
///////////////////////////////////////////////////////////////////////////////
|
|
2046
1972
|
// Gamepad input
|
|
@@ -2048,6 +1974,21 @@ oncontextmenu = e=> !1; // prevent right click menu
|
|
|
2048
1974
|
const stickData = [];
|
|
2049
1975
|
function gamepadsUpdate()
|
|
2050
1976
|
{
|
|
1977
|
+
if (touchGamepadEnable && touchGamepadTimer.isSet())
|
|
1978
|
+
{
|
|
1979
|
+
// read virtual analog stick
|
|
1980
|
+
const sticks = stickData[0] || (stickData[0] = []);
|
|
1981
|
+
sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
|
|
1982
|
+
|
|
1983
|
+
// read virtual gamepad buttons
|
|
1984
|
+
const data = inputData[1] || (inputData[1] = []);
|
|
1985
|
+
for (let i=10; i--;)
|
|
1986
|
+
{
|
|
1987
|
+
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
1988
|
+
data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
|
|
2051
1992
|
if (!gamepadsEnable || !navigator.getGamepads || !document.hasFocus() && !debug)
|
|
2052
1993
|
return;
|
|
2053
1994
|
|
|
@@ -2065,8 +2006,8 @@ function gamepadsUpdate()
|
|
|
2065
2006
|
// read clamp dead zone of analog sticks
|
|
2066
2007
|
const deadZone = .3, deadZoneMax = .8;
|
|
2067
2008
|
const applyDeadZone = (v)=>
|
|
2068
|
-
v > deadZone ? percent( v,
|
|
2069
|
-
v < -deadZone ? -percent(-v,
|
|
2009
|
+
v > deadZone ? percent( v, deadZone, deadZoneMax) :
|
|
2010
|
+
v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
|
|
2070
2011
|
|
|
2071
2012
|
// read analog sticks
|
|
2072
2013
|
for (let j = 0; j < gamepad.axes.length-1; j+=2)
|
|
@@ -2078,6 +2019,7 @@ function gamepadsUpdate()
|
|
|
2078
2019
|
const button = gamepad.buttons[j];
|
|
2079
2020
|
data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
|
|
2080
2021
|
isUsingGamepad |= !i && button.pressed;
|
|
2022
|
+
touchGamepadEnable && touchGamepadTimer.unset(); // disable touch gamepad if using real gamepad
|
|
2081
2023
|
}
|
|
2082
2024
|
|
|
2083
2025
|
if (gamepadDirectionEmulateStick)
|
|
@@ -2091,6 +2033,17 @@ function gamepadsUpdate()
|
|
|
2091
2033
|
}
|
|
2092
2034
|
}
|
|
2093
2035
|
|
|
2036
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2037
|
+
|
|
2038
|
+
/** Pulse the vibration hardware if it exists
|
|
2039
|
+
* @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
|
|
2040
|
+
* @memberof Input */
|
|
2041
|
+
const vibrate = (pattern)=> vibrateEnable && Navigator.vibrate && Navigator.vibrate(pattern);
|
|
2042
|
+
|
|
2043
|
+
/** Cancel any ongoing vibration
|
|
2044
|
+
* @memberof Input */
|
|
2045
|
+
const vibrateStop = ()=> vibrate(0);
|
|
2046
|
+
|
|
2094
2047
|
///////////////////////////////////////////////////////////////////////////////
|
|
2095
2048
|
// Touch input
|
|
2096
2049
|
|
|
@@ -2098,7 +2051,9 @@ function gamepadsUpdate()
|
|
|
2098
2051
|
* @const {boolean}
|
|
2099
2052
|
* @memberof Input */
|
|
2100
2053
|
const isTouchDevice = window.ontouchstart !== undefined;
|
|
2101
|
-
|
|
2054
|
+
|
|
2055
|
+
// try to enable touch mouse
|
|
2056
|
+
if (isTouchDevice)
|
|
2102
2057
|
{
|
|
2103
2058
|
// handle all touch events the same way
|
|
2104
2059
|
let wasTouching, hadTouchInput;
|
|
@@ -2129,19 +2084,25 @@ if (touchMouseEnable && isTouchDevice)
|
|
|
2129
2084
|
}
|
|
2130
2085
|
|
|
2131
2086
|
///////////////////////////////////////////////////////////////////////////////
|
|
2132
|
-
//
|
|
2087
|
+
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
2133
2088
|
|
|
2134
|
-
//
|
|
2135
|
-
|
|
2089
|
+
// touch input internal variables
|
|
2090
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2(), touchGamepadAnalog = 1;
|
|
2091
|
+
|
|
2092
|
+
// create the touch gamepad, called automatically by the engine
|
|
2136
2093
|
function touchGamepadCreate()
|
|
2137
2094
|
{
|
|
2138
|
-
if (!
|
|
2095
|
+
if (!touchGamepadEnable || !isTouchDevice)
|
|
2139
2096
|
return;
|
|
2140
2097
|
|
|
2141
2098
|
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
2142
2099
|
{
|
|
2143
|
-
if (!
|
|
2100
|
+
if (!touchGamepadEnable)
|
|
2144
2101
|
return;
|
|
2102
|
+
|
|
2103
|
+
// clear touch gamepad input
|
|
2104
|
+
touchGamepadStick = vec2();
|
|
2105
|
+
touchGamepadButtons = [];
|
|
2145
2106
|
|
|
2146
2107
|
const touching = e.touches.length;
|
|
2147
2108
|
if (touching)
|
|
@@ -2151,6 +2112,13 @@ function touchGamepadCreate()
|
|
|
2151
2112
|
// set that gamepad is active
|
|
2152
2113
|
isUsingGamepad = 1;
|
|
2153
2114
|
touchGamepadTimer.set();
|
|
2115
|
+
|
|
2116
|
+
if (paused)
|
|
2117
|
+
{
|
|
2118
|
+
// touch anywhere to press start when paused
|
|
2119
|
+
touchGamepadButtons[9] = 1;
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2154
2122
|
}
|
|
2155
2123
|
|
|
2156
2124
|
// get center of left and right sides
|
|
@@ -2158,68 +2126,58 @@ function touchGamepadCreate()
|
|
|
2158
2126
|
const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
|
|
2159
2127
|
const startCenter = mainCanvasSize.scale(.5);
|
|
2160
2128
|
|
|
2161
|
-
// update virtual gamepad
|
|
2162
|
-
const data = inputData[1] || (inputData[1] = []);
|
|
2163
|
-
const sticks = stickData[0] || (stickData[0] = []);
|
|
2164
|
-
sticks[0] = vec2();
|
|
2165
|
-
|
|
2166
2129
|
// check each touch point
|
|
2167
|
-
const buttons = [];
|
|
2168
2130
|
for (const touch of e.touches)
|
|
2169
2131
|
{
|
|
2170
|
-
const touchPos = vec2(touch.clientX, touch.clientY);
|
|
2132
|
+
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
2171
2133
|
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
2172
2134
|
{
|
|
2173
2135
|
// virtual analog stick
|
|
2174
|
-
|
|
2175
|
-
|
|
2136
|
+
if (touchGamepadAnalog)
|
|
2137
|
+
touchGamepadStick = touchPos.subtract(stickCenter).clampLength(touchGamepadSize/2).scale(2/touchGamepadSize);
|
|
2138
|
+
else
|
|
2139
|
+
{
|
|
2140
|
+
// 8 way dpad
|
|
2141
|
+
const angle = touchPos.subtract(stickCenter).angle();
|
|
2142
|
+
touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
|
|
2143
|
+
}
|
|
2176
2144
|
}
|
|
2177
2145
|
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
2178
2146
|
{
|
|
2179
2147
|
// virtual face buttons
|
|
2180
2148
|
const button = touchPos.subtract(buttonCenter).direction();
|
|
2181
|
-
|
|
2149
|
+
touchGamepadButtons[button] = 1;
|
|
2182
2150
|
}
|
|
2183
2151
|
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
2184
2152
|
{
|
|
2185
|
-
// virtual start button
|
|
2186
|
-
|
|
2153
|
+
// virtual start button in center
|
|
2154
|
+
touchGamepadButtons[9] = 1;
|
|
2187
2155
|
}
|
|
2188
2156
|
}
|
|
2189
|
-
|
|
2190
|
-
// read virtual buttons
|
|
2191
|
-
for (let j=10; j--;)
|
|
2192
|
-
data[j] = buttons[j] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
|
|
2193
2157
|
}
|
|
2194
2158
|
}
|
|
2195
2159
|
|
|
2196
2160
|
// render the touch gamepad, called automatically by the engine
|
|
2197
2161
|
function touchGamepadRender()
|
|
2198
2162
|
{
|
|
2199
|
-
if (!
|
|
2163
|
+
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
2200
2164
|
return;
|
|
2201
2165
|
|
|
2202
|
-
// fade off when not touching
|
|
2203
|
-
const alpha = percent(touchGamepadTimer.get(), 4,
|
|
2204
|
-
if (!alpha)
|
|
2166
|
+
// fade off when not touching or paused
|
|
2167
|
+
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
2168
|
+
if (!alpha || paused)
|
|
2205
2169
|
return;
|
|
2206
2170
|
|
|
2207
2171
|
// setup the canvas
|
|
2208
2172
|
overlayContext.save();
|
|
2209
2173
|
overlayContext.globalAlpha = alpha*touchGamepadAlpha;
|
|
2210
|
-
overlayContext.fillStyle = '#fff6';
|
|
2211
2174
|
overlayContext.strokeStyle = '#fff';
|
|
2212
2175
|
overlayContext.lineWidth = 3;
|
|
2213
2176
|
|
|
2214
|
-
// draw start button
|
|
2215
|
-
//overlayContext.beginPath();
|
|
2216
|
-
//overlayContext.arc(mainCanvasSize.x/2, mainCanvasSize.y/2, touchGamepadSize*.9, 0,9);
|
|
2217
|
-
//overlayContext.fill();
|
|
2218
|
-
//overlayContext.stroke();
|
|
2219
|
-
|
|
2220
2177
|
// draw left analog stick
|
|
2178
|
+
overlayContext.fillStyle = gamepadStick(0).length() > 0 ? '#fff' : '#000';
|
|
2221
2179
|
overlayContext.beginPath();
|
|
2222
|
-
overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize
|
|
2180
|
+
overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize/2, 0,9);
|
|
2223
2181
|
overlayContext.fill();
|
|
2224
2182
|
overlayContext.stroke();
|
|
2225
2183
|
|
|
@@ -2227,9 +2185,10 @@ function touchGamepadRender()
|
|
|
2227
2185
|
const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2228
2186
|
for (let i=4; i--;)
|
|
2229
2187
|
{
|
|
2230
|
-
const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize
|
|
2188
|
+
const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize/2));
|
|
2189
|
+
overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
2231
2190
|
overlayContext.beginPath();
|
|
2232
|
-
overlayContext.arc(pos.x, pos.y, touchGamepadSize
|
|
2191
|
+
overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
2233
2192
|
overlayContext.fill();
|
|
2234
2193
|
overlayContext.stroke();
|
|
2235
2194
|
}
|
|
@@ -2308,7 +2267,7 @@ class Sound
|
|
|
2308
2267
|
return; // out of range
|
|
2309
2268
|
|
|
2310
2269
|
// attenuate volume by distance
|
|
2311
|
-
volume *= percent(lengthSquared**.5, range*this.taper
|
|
2270
|
+
volume *= percent(lengthSquared**.5, range, range*this.taper);
|
|
2312
2271
|
}
|
|
2313
2272
|
|
|
2314
2273
|
// get pan from screen space coords
|
|
@@ -2434,7 +2393,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
2434
2393
|
|
|
2435
2394
|
/** Stop all queued speech
|
|
2436
2395
|
* @memberof Audio */
|
|
2437
|
-
const
|
|
2396
|
+
const speakStop = ()=> speechSynthesis && speechSynthesis.cancel();
|
|
2438
2397
|
|
|
2439
2398
|
/** Get frequency of a note on a musical scale
|
|
2440
2399
|
* @param {Number} semitoneOffset - How many semitones away from the root note
|
|
@@ -2465,6 +2424,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2465
2424
|
if (!audioContext)
|
|
2466
2425
|
audioContext = new (window.AudioContext||webkitAudioContext);
|
|
2467
2426
|
|
|
2427
|
+
// fix stalled audio
|
|
2428
|
+
audioContext.resume();
|
|
2429
|
+
|
|
2430
|
+
// prevent sounds from building up if they can't be played
|
|
2431
|
+
if (audioContext.state != 'running')
|
|
2432
|
+
return;
|
|
2433
|
+
|
|
2468
2434
|
// create buffer and source
|
|
2469
2435
|
const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, zzfxR),
|
|
2470
2436
|
source = audioContext.createBufferSource();
|
|
@@ -2475,11 +2441,18 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2475
2441
|
source.playbackRate.value = rate;
|
|
2476
2442
|
source.loop = loop;
|
|
2477
2443
|
|
|
2478
|
-
// create
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2444
|
+
// create and connect gain node (createGain is more widley spported then GainNode construtor)
|
|
2445
|
+
const gainNode = audioContext.createGain();
|
|
2446
|
+
gainNode.gain.value = soundVolume*volume;
|
|
2447
|
+
gainNode.connect(audioContext.destination);
|
|
2448
|
+
|
|
2449
|
+
// connect source to gain
|
|
2450
|
+
(
|
|
2451
|
+
window.StereoPannerNode ? // create pan node if possible
|
|
2452
|
+
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
|
|
2453
|
+
: source
|
|
2454
|
+
)
|
|
2455
|
+
.connect(gainNode);
|
|
2483
2456
|
|
|
2484
2457
|
// play and return sound
|
|
2485
2458
|
source.start();
|
|
@@ -2837,8 +2810,9 @@ class TileLayerData
|
|
|
2837
2810
|
/**
|
|
2838
2811
|
* Tile layer object - cached rendering system for tile layers
|
|
2839
2812
|
* <br> - Each Tile layer is rendered to an off screen canvas
|
|
2840
|
-
* <br> -
|
|
2841
|
-
* <br> -
|
|
2813
|
+
* <br> - To allow dynamic modifications, layers are rendered using canvas 2d
|
|
2814
|
+
* <br> - Some devices like mobile phones are limited to 4k texture resolution
|
|
2815
|
+
* <br> - So with 16x16 tiles this limits layers to 256x256 on mobile devices
|
|
2842
2816
|
* @extends EngineObject
|
|
2843
2817
|
* @example
|
|
2844
2818
|
* // create tile collision and visible tile layer
|
|
@@ -2848,11 +2822,11 @@ class TileLayerData
|
|
|
2848
2822
|
class TileLayer extends EngineObject
|
|
2849
2823
|
{
|
|
2850
2824
|
/** Create a tile layer object
|
|
2851
|
-
* @param {Vector2} [position=new Vector2(
|
|
2852
|
-
* @param {Vector2} [size=objectDefaultSize]
|
|
2853
|
-
* @param {Vector2} [tileSize=tileSizeDefault]
|
|
2854
|
-
* @param {Vector2} [scale=new Vector2(1,1)]
|
|
2855
|
-
* @param {Number} [renderOrder=0]
|
|
2825
|
+
* @param {Vector2} [position=new Vector2()] - World space position
|
|
2826
|
+
* @param {Vector2} [size=objectDefaultSize] - World space size
|
|
2827
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
|
|
2828
|
+
* @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
|
|
2829
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
2856
2830
|
*/
|
|
2857
2831
|
constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
|
|
2858
2832
|
{
|
|
@@ -2903,7 +2877,7 @@ class TileLayer extends EngineObject
|
|
|
2903
2877
|
// flush and copy gl canvas because tile canvas does not use webgl
|
|
2904
2878
|
glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
2905
2879
|
|
|
2906
|
-
// draw the entire cached level onto the
|
|
2880
|
+
// draw the entire cached level onto the canvas
|
|
2907
2881
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
2908
2882
|
(this.isOverlay ? overlayContext : mainContext).drawImage
|
|
2909
2883
|
(
|
|
@@ -2912,35 +2886,36 @@ class TileLayer extends EngineObject
|
|
|
2912
2886
|
);
|
|
2913
2887
|
}
|
|
2914
2888
|
|
|
2915
|
-
/** Draw all the tile data to an offscreen canvas
|
|
2889
|
+
/** Draw all the tile data to an offscreen canvas
|
|
2890
|
+
* - This may be slow in some browsers
|
|
2891
|
+
*/
|
|
2916
2892
|
redraw()
|
|
2917
2893
|
{
|
|
2918
|
-
this.redrawStart();
|
|
2894
|
+
this.redrawStart(1);
|
|
2919
2895
|
this.drawAllTileData();
|
|
2920
2896
|
this.redrawEnd();
|
|
2921
2897
|
}
|
|
2922
2898
|
|
|
2923
2899
|
/** Call to start the redraw process
|
|
2924
|
-
* @param {Boolean} [clear=
|
|
2925
|
-
redrawStart(clear =
|
|
2900
|
+
* @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
|
|
2901
|
+
redrawStart(clear = 0)
|
|
2926
2902
|
{
|
|
2927
|
-
// clear and set size
|
|
2928
|
-
const width = this.size.x * this.tileSize.x;
|
|
2929
|
-
const height = this.size.y * this.tileSize.y;
|
|
2930
2903
|
if (clear)
|
|
2931
2904
|
{
|
|
2932
|
-
|
|
2933
|
-
this.canvas.
|
|
2905
|
+
// clear and set size
|
|
2906
|
+
this.canvas.width = this.size.x * this.tileSize.x;
|
|
2907
|
+
this.canvas.height = this.size.y * this.tileSize.y;
|
|
2934
2908
|
}
|
|
2935
2909
|
|
|
2936
2910
|
// save current render settings
|
|
2937
|
-
this.savedRenderSettings = [mainCanvas, mainContext];
|
|
2911
|
+
this.savedRenderSettings = [mainCanvas, mainContext, cameraPos, cameraScale];
|
|
2938
2912
|
|
|
2939
|
-
//
|
|
2913
|
+
// use normal rendering system to render the tiles
|
|
2940
2914
|
mainCanvas = this.canvas;
|
|
2941
2915
|
mainContext = this.context;
|
|
2942
|
-
|
|
2943
|
-
|
|
2916
|
+
cameraPos = this.size.scale(.5);
|
|
2917
|
+
cameraScale = this.tileSize.x;
|
|
2918
|
+
enginePreRender();
|
|
2944
2919
|
}
|
|
2945
2920
|
|
|
2946
2921
|
/** Call to end the redraw process */
|
|
@@ -2951,7 +2926,7 @@ class TileLayer extends EngineObject
|
|
|
2951
2926
|
//debugSaveCanvas(this.canvas);
|
|
2952
2927
|
|
|
2953
2928
|
// set stuff back to normal
|
|
2954
|
-
[mainCanvas, mainContext] = this.savedRenderSettings;
|
|
2929
|
+
[mainCanvas, mainContext, cameraPos, cameraScale] = this.savedRenderSettings;
|
|
2955
2930
|
}
|
|
2956
2931
|
|
|
2957
2932
|
/** Draw the tile at a given position
|
|
@@ -2979,7 +2954,7 @@ class TileLayer extends EngineObject
|
|
|
2979
2954
|
this.drawTileData(vec2(x,y));
|
|
2980
2955
|
}
|
|
2981
2956
|
|
|
2982
|
-
/** Draw directly to the
|
|
2957
|
+
/** Draw directly to the 2D canvas in world space (bipass webgl)
|
|
2983
2958
|
* @param {Vector2} pos
|
|
2984
2959
|
* @param {Vector2} size
|
|
2985
2960
|
* @param {Number} [angle=0]
|
|
@@ -3019,7 +2994,7 @@ class TileLayer extends EngineObject
|
|
|
3019
2994
|
else
|
|
3020
2995
|
{
|
|
3021
2996
|
const cols = tileImage.width/tileSize.x;
|
|
3022
|
-
context.globalAlpha = color.a; //
|
|
2997
|
+
context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
|
|
3023
2998
|
context.drawImage(tileImage,
|
|
3024
2999
|
(tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.x,
|
|
3025
3000
|
tileSize.x, tileSize.y, -.5, -.5, 1, 1);
|
|
@@ -3087,7 +3062,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3087
3062
|
* @param {Number} [randomness=.2] - Apply extra randomness percent
|
|
3088
3063
|
* @param {Boolean} [collideTiles=0] - Do particles collide against tiles
|
|
3089
3064
|
* @param {Boolean} [additive=0] - Should particles use addtive blend
|
|
3090
|
-
* @param {Boolean} [randomColorLinear=
|
|
3065
|
+
* @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
|
|
3091
3066
|
* @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
|
|
3092
3067
|
*/
|
|
3093
3068
|
constructor
|
|
@@ -3188,9 +3163,9 @@ class ParticleEmitter extends EngineObject
|
|
|
3188
3163
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
3189
3164
|
{
|
|
3190
3165
|
// emit particles
|
|
3191
|
-
if (this.emitRate)
|
|
3166
|
+
if (this.emitRate * particleEmitRateScale)
|
|
3192
3167
|
{
|
|
3193
|
-
const rate = 1/this.emitRate;
|
|
3168
|
+
const rate = 1/this.emitRate/particleEmitRateScale;
|
|
3194
3169
|
for (this.emitTimeBuffer += timeDelta; this.emitTimeBuffer > 0; this.emitTimeBuffer -= rate)
|
|
3195
3170
|
this.emitParticle();
|
|
3196
3171
|
}
|
|
@@ -3353,7 +3328,7 @@ function medalsInit(saveName)
|
|
|
3353
3328
|
{
|
|
3354
3329
|
// check if medals are unlocked
|
|
3355
3330
|
medalsSaveName = saveName;
|
|
3356
|
-
debugMedals || medals.forEach(medal=> medal.unlocked = localStorage[medal.storageKey()]);
|
|
3331
|
+
debugMedals || medals.forEach(medal=> medal.unlocked = 1 || localStorage[medal.storageKey()]);
|
|
3357
3332
|
}
|
|
3358
3333
|
|
|
3359
3334
|
/**
|
|
@@ -3386,13 +3361,9 @@ class Medal
|
|
|
3386
3361
|
this.name = name;
|
|
3387
3362
|
this.description = description;
|
|
3388
3363
|
this.icon = icon;
|
|
3389
|
-
|
|
3364
|
+
this.image = new Image();
|
|
3390
3365
|
if (src)
|
|
3391
|
-
{
|
|
3392
|
-
// load image
|
|
3393
|
-
this.image = new Image();
|
|
3394
3366
|
this.image.src = src;
|
|
3395
|
-
}
|
|
3396
3367
|
}
|
|
3397
3368
|
|
|
3398
3369
|
/** Unlocks a medal if not already unlocked */
|
|
@@ -3422,21 +3393,23 @@ class Medal
|
|
|
3422
3393
|
const y = -medalDisplayHeight*hidePercent;
|
|
3423
3394
|
|
|
3424
3395
|
// draw containing rect and clip to that region
|
|
3425
|
-
context.
|
|
3396
|
+
context.save();
|
|
3397
|
+
context.beginPath();
|
|
3426
3398
|
context.fillStyle = '#ddd'
|
|
3427
3399
|
context.fill(context.rect(x, y, width, medalDisplayHeight));
|
|
3428
|
-
context.strokeStyle =
|
|
3429
|
-
context.lineWidth =
|
|
3400
|
+
context.strokeStyle = '#000';
|
|
3401
|
+
context.lineWidth = 3;
|
|
3430
3402
|
context.stroke();
|
|
3431
3403
|
context.clip();
|
|
3432
3404
|
|
|
3433
3405
|
// draw the icon and text
|
|
3434
3406
|
this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
|
|
3435
3407
|
context.textAlign = 'left';
|
|
3436
|
-
context.font = '
|
|
3437
|
-
context.fillText(this.name, x+medalDisplayIconSize+
|
|
3438
|
-
context.font = '
|
|
3439
|
-
context.
|
|
3408
|
+
context.font = '38px '+ fontDefault;
|
|
3409
|
+
context.fillText(this.name, x+medalDisplayIconSize+30, y+28);
|
|
3410
|
+
context.font = '24px '+ fontDefault;
|
|
3411
|
+
context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
|
|
3412
|
+
context.restore();
|
|
3440
3413
|
}
|
|
3441
3414
|
|
|
3442
3415
|
/** Render the icon for a medal
|
|
@@ -3448,11 +3421,11 @@ class Medal
|
|
|
3448
3421
|
{
|
|
3449
3422
|
// draw the image or icon
|
|
3450
3423
|
const context = overlayContext;
|
|
3424
|
+
context.fillStyle = '#000';
|
|
3451
3425
|
context.textAlign = 'center';
|
|
3452
3426
|
context.textBaseline = 'middle';
|
|
3453
|
-
context.font = size*.
|
|
3454
|
-
|
|
3455
|
-
if (this.image)
|
|
3427
|
+
context.font = size*.7 + 'px '+ fontDefault;
|
|
3428
|
+
if (this.image.src)
|
|
3456
3429
|
context.drawImage(this.image, x-size/2, y-size/2, size, size);
|
|
3457
3430
|
else
|
|
3458
3431
|
context.fillText(this.icon, x, y); // show icon if there is no image
|
|
@@ -3528,7 +3501,6 @@ class Newgrounds
|
|
|
3528
3501
|
if (medal)
|
|
3529
3502
|
{
|
|
3530
3503
|
// copy newgrounds medal data
|
|
3531
|
-
medal.image = new Image();
|
|
3532
3504
|
medal.image.src = newgroundsMedal['icon'];
|
|
3533
3505
|
medal.name = newgroundsMedal['name'];
|
|
3534
3506
|
medal.description = newgroundsMedal['description'];
|
|
@@ -3657,32 +3629,27 @@ function glInit()
|
|
|
3657
3629
|
|
|
3658
3630
|
// create the canvas and tile texture
|
|
3659
3631
|
glCanvas = document.createElement('canvas');
|
|
3660
|
-
glContext = glCanvas.getContext('webgl');
|
|
3661
|
-
|
|
3632
|
+
glContext = glCanvas.getContext('webgl', {antialias: false});
|
|
3633
|
+
glCanvas.style = styleCanvas;
|
|
3662
3634
|
glTileTexture = glCreateTexture(tileImage);
|
|
3663
3635
|
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
3667
|
-
document.body.appendChild(glCanvas);
|
|
3668
|
-
glCanvas.style = mainCanvas.style.cssText;
|
|
3669
|
-
}
|
|
3636
|
+
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
3637
|
+
glOverlay && document.body.appendChild(glCanvas);
|
|
3670
3638
|
|
|
3671
3639
|
// setup vertex and fragment shaders
|
|
3672
3640
|
glShader = glCreateProgram(
|
|
3673
3641
|
'precision highp float;'+ // use highp for better accuracy, lowp for better perf
|
|
3674
3642
|
'uniform mat4 m;'+ // transform matrix
|
|
3675
|
-
'attribute
|
|
3676
|
-
'attribute
|
|
3677
|
-
'attribute vec4 c,b;'+ // color, additiveColor
|
|
3643
|
+
'attribute vec2 p,t;'+ // position, uv
|
|
3644
|
+
'attribute vec4 c,a;'+ // color, additiveColor
|
|
3678
3645
|
'varying vec2 v;'+ // return uv
|
|
3679
3646
|
'varying vec4 d,e;'+ // return color, additiveColor
|
|
3680
3647
|
'void main(){'+ // shader entry point
|
|
3681
|
-
'gl_Position=m*vec4(
|
|
3682
|
-
'v=t;d=c;e=
|
|
3648
|
+
'gl_Position=m*vec4(p,1,1);'+// transform position
|
|
3649
|
+
'v=t;d=c;e=a;'+ // pass stuff to fragment shader
|
|
3683
3650
|
'}' // end of shader
|
|
3684
3651
|
,
|
|
3685
|
-
'precision
|
|
3652
|
+
'precision highp float;'+ // use highp for better accuracy, lowp for better perf
|
|
3686
3653
|
'varying vec2 v;'+ // uv
|
|
3687
3654
|
'varying vec4 d,e;'+ // color, additiveColor
|
|
3688
3655
|
'uniform sampler2D j;'+ // texture
|
|
@@ -3698,6 +3665,7 @@ function glInit()
|
|
|
3698
3665
|
glColorData = new Uint32Array(glVertexData);
|
|
3699
3666
|
|
|
3700
3667
|
// setup the vertex data array
|
|
3668
|
+
let offset = glBatchCount = 0;
|
|
3701
3669
|
const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
|
|
3702
3670
|
{
|
|
3703
3671
|
const location = glContext.getAttribLocation(glShader, name);
|
|
@@ -3705,13 +3673,10 @@ function glInit()
|
|
|
3705
3673
|
glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
|
|
3706
3674
|
offset += size*typeSize;
|
|
3707
3675
|
}
|
|
3708
|
-
let offset = glBatchCount = 0;
|
|
3709
|
-
initVertexAttribArray('a', gl_FLOAT, 4, 1); // angle
|
|
3710
3676
|
initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
|
|
3711
|
-
initVertexAttribArray('s', gl_FLOAT, 4, 2); // size
|
|
3712
3677
|
initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
|
|
3713
3678
|
initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
|
|
3714
|
-
initVertexAttribArray('
|
|
3679
|
+
initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
3715
3680
|
}
|
|
3716
3681
|
|
|
3717
3682
|
/** Set the WebGl blend mode, normally you should call setBlendMode instead
|
|
@@ -3732,7 +3697,8 @@ function glSetBlendMode(additive)
|
|
|
3732
3697
|
function glSetTexture(texture=glTileTexture)
|
|
3733
3698
|
{
|
|
3734
3699
|
if (!glEnable) return;
|
|
3735
|
-
|
|
3700
|
+
|
|
3701
|
+
// must flush cache with the old texture to set a new one
|
|
3736
3702
|
if (texture != glActiveTexture)
|
|
3737
3703
|
glFlush();
|
|
3738
3704
|
|
|
@@ -3825,6 +3791,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
|
|
|
3825
3791
|
|
|
3826
3792
|
// clear and set to same size as main canvas
|
|
3827
3793
|
glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
|
|
3794
|
+
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3828
3795
|
|
|
3829
3796
|
// set up the shader
|
|
3830
3797
|
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
|
|
@@ -3868,15 +3835,13 @@ function glFlush()
|
|
|
3868
3835
|
* @memberof WebGL */
|
|
3869
3836
|
function glCopyToContext(context, forceDraw)
|
|
3870
3837
|
{
|
|
3871
|
-
if (!glEnable || !glBatchCount)
|
|
3838
|
+
if (!glEnable || !glBatchCount) return;
|
|
3872
3839
|
|
|
3873
3840
|
glFlush();
|
|
3841
|
+
|
|
3842
|
+
// do not draw in overlay mode because the canvas is visible
|
|
3874
3843
|
if (!glOverlay || forceDraw)
|
|
3875
|
-
{
|
|
3876
|
-
// do not draw/clear in overlay mode because the canvas is visible
|
|
3877
3844
|
context.drawImage(glCanvas, 0, 0);
|
|
3878
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3879
|
-
}
|
|
3880
3845
|
}
|
|
3881
3846
|
|
|
3882
3847
|
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
@@ -3899,51 +3864,49 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
|
|
|
3899
3864
|
// flush if there is no room for more verts or if different blend mode
|
|
3900
3865
|
if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
|
|
3901
3866
|
glFlush();
|
|
3867
|
+
|
|
3868
|
+
// prepare to create the verts from size and angle
|
|
3869
|
+
const c = Math.cos(angle)/2, s = Math.sin(angle)/2;
|
|
3870
|
+
const cX = c*sizeX, cY = c*sizeY, sX = s*sizeX, sY = s*sizeY;
|
|
3902
3871
|
|
|
3903
3872
|
// setup 2 triangles to form a quad
|
|
3904
3873
|
let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
|
|
3905
3874
|
|
|
3906
3875
|
// vertex 0
|
|
3907
|
-
glPositionData[offset++] =
|
|
3908
|
-
glPositionData[offset++] =
|
|
3909
|
-
glPositionData[offset++] =
|
|
3910
|
-
|
|
3911
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3876
|
+
glPositionData[offset++] = x - cX - sY;
|
|
3877
|
+
glPositionData[offset++] = y - cY + sX;
|
|
3878
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3879
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3912
3880
|
|
|
3913
3881
|
// vertex 1
|
|
3914
|
-
glPositionData[offset++] =
|
|
3915
|
-
glPositionData[offset++] =
|
|
3916
|
-
glPositionData[offset++] =
|
|
3917
|
-
|
|
3918
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3882
|
+
glPositionData[offset++] = x + cX + sY;
|
|
3883
|
+
glPositionData[offset++] = y + cY - sX;
|
|
3884
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3885
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3919
3886
|
|
|
3920
3887
|
// vertex 2
|
|
3921
|
-
glPositionData[offset++] =
|
|
3922
|
-
glPositionData[offset++] =
|
|
3923
|
-
glPositionData[offset++] =
|
|
3924
|
-
|
|
3925
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3888
|
+
glPositionData[offset++] = x - cX + sY;
|
|
3889
|
+
glPositionData[offset++] = y + cY + sX;
|
|
3890
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
|
|
3891
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3926
3892
|
|
|
3927
3893
|
// vertex 0
|
|
3928
|
-
glPositionData[offset++] =
|
|
3929
|
-
glPositionData[offset++] =
|
|
3930
|
-
glPositionData[offset++] =
|
|
3931
|
-
|
|
3932
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3894
|
+
glPositionData[offset++] = x - cX - sY;
|
|
3895
|
+
glPositionData[offset++] = y - cY + sX;
|
|
3896
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3897
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3933
3898
|
|
|
3934
3899
|
// vertex 3
|
|
3935
|
-
glPositionData[offset++] =
|
|
3936
|
-
glPositionData[offset++] =
|
|
3937
|
-
glPositionData[offset++] =
|
|
3938
|
-
|
|
3939
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3900
|
+
glPositionData[offset++] = x + cX - sY;
|
|
3901
|
+
glPositionData[offset++] = y - cY - sX;
|
|
3902
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
|
|
3903
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3940
3904
|
|
|
3941
3905
|
// vertex 1
|
|
3942
|
-
glPositionData[offset++] =
|
|
3943
|
-
glPositionData[offset++] =
|
|
3944
|
-
glPositionData[offset++] =
|
|
3945
|
-
|
|
3946
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3906
|
+
glPositionData[offset++] = x + cX + sY;
|
|
3907
|
+
glPositionData[offset++] = y + cY - sX;
|
|
3908
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3909
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3947
3910
|
}
|
|
3948
3911
|
|
|
3949
3912
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3975,9 +3938,9 @@ gl_LINK_STATUS = 35714,
|
|
|
3975
3938
|
|
|
3976
3939
|
// constants for batch rendering
|
|
3977
3940
|
gl_VERTICES_PER_QUAD = 6,
|
|
3978
|
-
gl_INDICIES_PER_VERT =
|
|
3941
|
+
gl_INDICIES_PER_VERT = 6,
|
|
3979
3942
|
gl_MAX_BATCH = 1<<16,
|
|
3980
|
-
gl_VERTEX_BYTE_STRIDE =
|
|
3943
|
+
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
|
|
3981
3944
|
/*
|
|
3982
3945
|
LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
3983
3946
|
MIT License - Copyright 2021 Frank Force
|
|
@@ -4002,7 +3965,7 @@ gl_VERTEX_BYTE_STRIDE = 4 + (4 * 2) * 3 + (4) * 2; // float + vec2 * 3 + (char *
|
|
|
4002
3965
|
const engineName = 'LittleJS';
|
|
4003
3966
|
|
|
4004
3967
|
/** Version of engine */
|
|
4005
|
-
const engineVersion = '1.
|
|
3968
|
+
const engineVersion = '1.2.0';
|
|
4006
3969
|
|
|
4007
3970
|
/** Frames per second to update objects
|
|
4008
3971
|
* @default */
|
|
@@ -4015,7 +3978,7 @@ const timeDelta = 1/frameRate;
|
|
|
4015
3978
|
/** Array containing all engine objects */
|
|
4016
3979
|
let engineObjects = [];
|
|
4017
3980
|
|
|
4018
|
-
/** Array containing only objects that are set to collide with other objects (for optimization) */
|
|
3981
|
+
/** Array containing only objects that are set to collide with other objects this frame (for optimization) */
|
|
4019
3982
|
let engineObjectsCollide = [];
|
|
4020
3983
|
|
|
4021
3984
|
/** Current update frame, used to calculate time */
|
|
@@ -4031,8 +3994,15 @@ let timeReal = 0;
|
|
|
4031
3994
|
let paused = 0;
|
|
4032
3995
|
|
|
4033
3996
|
// Engine internal variables not exposed to documentation
|
|
4034
|
-
let frameTimeLastMS = 0, frameTimeBufferMS = 0,
|
|
4035
|
-
|
|
3997
|
+
let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
|
|
3998
|
+
|
|
3999
|
+
// Engine stat tracking, if showWatermark is true
|
|
4000
|
+
let averageFPS, drawCount;
|
|
4001
|
+
|
|
4002
|
+
// css text used for elements created by engine
|
|
4003
|
+
const styleBody = 'margin:0;overflow:hidden;background:#000' +
|
|
4004
|
+
';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none';
|
|
4005
|
+
const styleCanvas = 'position:absolute';
|
|
4036
4006
|
|
|
4037
4007
|
///////////////////////////////////////////////////////////////////////////////
|
|
4038
4008
|
|
|
@@ -4050,17 +4020,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4050
4020
|
tileImage.onerror = tileImage.onload = ()=>
|
|
4051
4021
|
{
|
|
4052
4022
|
// save tile image info
|
|
4053
|
-
|
|
4023
|
+
tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
4054
4024
|
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
4055
|
-
shrinkTilesX = tileBleedShrinkFix/tileImageSize.x;
|
|
4056
|
-
shrinkTilesY = tileBleedShrinkFix/tileImageSize.y;
|
|
4057
4025
|
|
|
4058
4026
|
// setup html
|
|
4027
|
+
document.body.style = styleBody;
|
|
4059
4028
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
4060
|
-
document.body.style = 'margin:0;overflow:hidden;background:#000' +
|
|
4061
|
-
(touchMouseEnable?';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none':'');
|
|
4062
|
-
mainCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
4063
4029
|
mainContext = mainCanvas.getContext('2d');
|
|
4030
|
+
mainCanvas.style = styleCanvas;
|
|
4064
4031
|
|
|
4065
4032
|
// init stuff and start engine
|
|
4066
4033
|
debugInit();
|
|
@@ -4068,8 +4035,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4068
4035
|
|
|
4069
4036
|
// create overlay canvas for hud to appear above gl canvas
|
|
4070
4037
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
4071
|
-
overlayCanvas.style = mainCanvas.style.cssText;
|
|
4072
4038
|
overlayContext = overlayCanvas.getContext('2d');
|
|
4039
|
+
overlayCanvas.style = styleCanvas;
|
|
4073
4040
|
|
|
4074
4041
|
gameInit();
|
|
4075
4042
|
touchGamepadCreate();
|
|
@@ -4085,7 +4052,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4085
4052
|
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
4086
4053
|
frameTimeLastMS = frameTimeMS;
|
|
4087
4054
|
if (debug || showWatermark)
|
|
4088
|
-
|
|
4055
|
+
averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
|
|
4089
4056
|
if (debug)
|
|
4090
4057
|
frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
|
|
4091
4058
|
timeReal += frameTimeDeltaMS / 1e3;
|
|
@@ -4136,13 +4103,24 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4136
4103
|
|
|
4137
4104
|
// fit to window by adding space on top or bottom if necessary
|
|
4138
4105
|
const aspect = innerWidth / innerHeight;
|
|
4139
|
-
const fixedAspect =
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4106
|
+
const fixedAspect = canvasFixedSize.x / canvasFixedSize.y;
|
|
4107
|
+
|
|
4108
|
+
// clear css
|
|
4109
|
+
mainCanvas.style.width = overlayCanvas.style.width =
|
|
4110
|
+
mainCanvas.style.height = overlayCanvas.style.height =
|
|
4111
|
+
mainCanvas.style.top = overlayCanvas.style.top =
|
|
4112
|
+
mainCanvas.style.left = overlayCanvas.style.left = '';
|
|
4113
|
+
|
|
4114
|
+
// letterbox center canvas
|
|
4115
|
+
if (aspect < fixedAspect)
|
|
4116
|
+
{
|
|
4117
|
+
mainCanvas.style.width = overlayCanvas.style.width = innerWidth;
|
|
4118
|
+
mainCanvas.style.top = overlayCanvas.style.top = (innerHeight - innerWidth/fixedAspect)/2;
|
|
4119
|
+
}
|
|
4120
|
+
else
|
|
4143
4121
|
{
|
|
4144
|
-
|
|
4145
|
-
|
|
4122
|
+
mainCanvas.style.height = overlayCanvas.style.height = innerHeight;
|
|
4123
|
+
mainCanvas.style.left = overlayCanvas.style.left = (innerWidth - innerHeight*fixedAspect)/2;
|
|
4146
4124
|
}
|
|
4147
4125
|
}
|
|
4148
4126
|
else
|
|
@@ -4150,14 +4128,22 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4150
4128
|
// clear and set size to same as window
|
|
4151
4129
|
mainCanvas.width = min(innerWidth, canvasMaxSize.x);
|
|
4152
4130
|
mainCanvas.height = min(innerHeight, canvasMaxSize.y);
|
|
4131
|
+
|
|
4132
|
+
// center canvas
|
|
4133
|
+
overlayCanvas.style.top = mainCanvas.style.top = (innerHeight - mainCanvas.height)/2;
|
|
4134
|
+
overlayCanvas.style.left = mainCanvas.style.left = (innerWidth - mainCanvas.width)/2;
|
|
4135
|
+
}
|
|
4136
|
+
|
|
4137
|
+
if (glEnable)
|
|
4138
|
+
{
|
|
4139
|
+
glCanvas.style.width = mainCanvas.style.width;
|
|
4140
|
+
glCanvas.style.height = mainCanvas.style.height;
|
|
4141
|
+
glCanvas.style.top = mainCanvas.style.top;
|
|
4142
|
+
glCanvas.style.left = mainCanvas.style.left;
|
|
4153
4143
|
}
|
|
4154
4144
|
|
|
4155
|
-
// save canvas size and clear overlay canvas
|
|
4156
|
-
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
4157
|
-
mainContext.imageSmoothingEnabled = !cavasPixelated; // disable smoothing for pixel art
|
|
4158
|
-
|
|
4159
4145
|
// render sort then render while removing destroyed objects
|
|
4160
|
-
|
|
4146
|
+
enginePreRender();
|
|
4161
4147
|
gameRender();
|
|
4162
4148
|
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
4163
4149
|
for (const o of engineObjects)
|
|
@@ -4176,7 +4162,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4176
4162
|
overlayContext.font = '1em monospace';
|
|
4177
4163
|
overlayContext.fillStyle = '#000';
|
|
4178
4164
|
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
4179
|
-
+ drawCount + ' / ' + engineObjects.length + ' / ' +
|
|
4165
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
4166
|
+
+ ' ' + (glEnable ? 'GL' : '2D') ;
|
|
4180
4167
|
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
4181
4168
|
overlayContext.fillStyle = '#fff';
|
|
4182
4169
|
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
@@ -4188,12 +4175,27 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4188
4175
|
tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
|
|
4189
4176
|
}
|
|
4190
4177
|
|
|
4178
|
+
// called by engine to setup render system
|
|
4179
|
+
function enginePreRender()
|
|
4180
|
+
{
|
|
4181
|
+
// save canvas size and clear canvases
|
|
4182
|
+
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
4183
|
+
|
|
4184
|
+
// disable smoothing for pixel art
|
|
4185
|
+
mainContext.imageSmoothingEnabled = !cavasPixelated;
|
|
4186
|
+
|
|
4187
|
+
// setup gl rendering if enabled
|
|
4188
|
+
glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
|
|
4189
|
+
}
|
|
4191
4190
|
|
|
4192
4191
|
///////////////////////////////////////////////////////////////////////////////
|
|
4193
4192
|
|
|
4194
4193
|
/** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
|
|
4195
4194
|
function engineObjectsUpdate()
|
|
4196
4195
|
{
|
|
4196
|
+
// get list of solid objects for physics optimzation
|
|
4197
|
+
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
4198
|
+
|
|
4197
4199
|
// recursive object update
|
|
4198
4200
|
const updateObject = (o)=>
|
|
4199
4201
|
{
|
|
@@ -4209,17 +4211,16 @@ function engineObjectsUpdate()
|
|
|
4209
4211
|
|
|
4210
4212
|
// remove destroyed objects
|
|
4211
4213
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
4212
|
-
engineObjectsCollide = engineObjectsCollide.filter(o=>!o.destroyed);
|
|
4213
4214
|
|
|
4214
4215
|
// increment frame and update time
|
|
4215
4216
|
time = ++frame / frameRate;
|
|
4216
4217
|
}
|
|
4217
4218
|
|
|
4218
|
-
/**
|
|
4219
|
+
/** Destroy and remove all objects */
|
|
4219
4220
|
function engineObjectsDestroy()
|
|
4220
4221
|
{
|
|
4221
4222
|
for (const o of engineObjects)
|
|
4222
|
-
o.
|
|
4223
|
+
o.parent || o.destroy();
|
|
4223
4224
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
4224
4225
|
}
|
|
4225
4226
|
|
|
@@ -4230,21 +4231,18 @@ function engineObjectsDestroy()
|
|
|
4230
4231
|
* @param {Array} [objects=engineObjects] - List of objects to check */
|
|
4231
4232
|
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
4232
4233
|
{
|
|
4233
|
-
if (!pos)
|
|
4234
|
+
if (!pos) // all objects
|
|
4234
4235
|
{
|
|
4235
|
-
// all objects
|
|
4236
4236
|
for (const o of objects)
|
|
4237
4237
|
callbackFunction(o);
|
|
4238
4238
|
}
|
|
4239
|
-
else if (size.x != undefined)
|
|
4239
|
+
else if (size.x != undefined) // bounding box test
|
|
4240
4240
|
{
|
|
4241
|
-
// aabb test
|
|
4242
4241
|
for (const o of objects)
|
|
4243
4242
|
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
4244
4243
|
}
|
|
4245
|
-
else
|
|
4244
|
+
else // circle test
|
|
4246
4245
|
{
|
|
4247
|
-
// circle test
|
|
4248
4246
|
const sizeSquared = size*size;
|
|
4249
4247
|
for (const o of objects)
|
|
4250
4248
|
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|