littlejsengine 1.1.18 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -11
- package/build.bat +1 -0
- package/engine/engine.all.js +495 -504
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.release.js +418 -259
- package/engine/engine.js +31 -23
- package/engine/engineAudio.js +21 -7
- package/engine/engineBuild.bat +9 -1
- package/engine/engineBuildSetup.bat +2 -1
- package/engine/engineDebug.js +81 -247
- package/engine/engineDraw.js +109 -21
- package/engine/engineFont.png +0 -0
- package/engine/engineInput.js +103 -50
- package/engine/engineMedals.js +17 -20
- package/engine/engineObject.js +14 -23
- package/engine/engineParticles.js +8 -6
- package/engine/engineRelease.js +3 -1
- package/engine/engineSettings.js +20 -10
- package/engine/engineTileLayer.js +19 -18
- package/engine/engineUtilities.js +14 -12
- package/engine/engineWebGL.js +59 -68
- package/engine/index.d.ts +1617 -0
- package/examples/breakout/game.js +9 -7
- package/examples/breakout/gameObjects.js +3 -3
- package/examples/breakout/index.html +6 -4
- package/examples/platformer/game.js +5 -5
- package/examples/platformer/gameEffects.js +11 -14
- package/examples/platformer/gameLevel.js +14 -7
- package/examples/platformer/gameObjects.js +3 -3
- 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 +2 -2
- package/examples/puzzle/index.html +5 -3
- package/examples/screenshot.jpg +0 -0
- package/examples/stress/index.html +7 -5
- package/game.js +9 -8
- 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
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -153,25 +160,28 @@ const debugUpdate = ()=>
|
|
|
153
160
|
{
|
|
154
161
|
if (!debug)
|
|
155
162
|
return;
|
|
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;
|
|
246
|
+
|
|
247
|
+
const distance = mousePos.distanceSquared(o.pos);
|
|
248
|
+
if (distance < bestDistance)
|
|
249
|
+
{
|
|
250
|
+
bestDistance = distance;
|
|
251
|
+
bestObject = o
|
|
252
|
+
}
|
|
235
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
372
|
|
|
376
|
-
|
|
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
|
-
|
|
404
|
-
return '#' + componentToHex(color.r) + componentToHex(color.g) + componentToHex(color.b);
|
|
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
|
|
@@ -1087,7 +923,7 @@ let tileSizeDefault = vec2(16);
|
|
|
1087
923
|
/** Prevent tile bleeding from neighbors in pixels
|
|
1088
924
|
* @default
|
|
1089
925
|
* @memberof Settings */
|
|
1090
|
-
let
|
|
926
|
+
let tileFixBleedScale = .3;
|
|
1091
927
|
|
|
1092
928
|
///////////////////////////////////////////////////////////////////////////////
|
|
1093
929
|
// Object settings
|
|
@@ -1133,6 +969,11 @@ let objectMaxSpeed = 1;
|
|
|
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
|
|
|
@@ -1178,11 +1019,6 @@ let gamepadDirectionEmulateStick = 1;
|
|
|
1178
1019
|
* @memberof Settings */
|
|
1179
1020
|
let inputWASDEmulateDirection = 1;
|
|
1180
1021
|
|
|
1181
|
-
/** If true touch input is routed to mouse functions
|
|
1182
|
-
* @default
|
|
1183
|
-
* @memberof Settings */
|
|
1184
|
-
let touchMouseEnable = 1;
|
|
1185
|
-
|
|
1186
1022
|
/** True if touch gamepad should appear on mobile devices
|
|
1187
1023
|
* <br> - Supports left analog stick, 4 face buttons and start button (button 9)
|
|
1188
1024
|
* <br> - Must be set by end of gameInit to be activated
|
|
@@ -1190,6 +1026,11 @@ let touchMouseEnable = 1;
|
|
|
1190
1026
|
* @memberof Settings */
|
|
1191
1027
|
let touchGamepadEnable = 0;
|
|
1192
1028
|
|
|
1029
|
+
/** True if touch gamepad should be analog stick or false to use if 8 way dpad
|
|
1030
|
+
* @default
|
|
1031
|
+
* @memberof Settings */
|
|
1032
|
+
let touchGamepadAnalog = 1;
|
|
1033
|
+
|
|
1193
1034
|
/** Size of virutal gamepad for touch devices in pixels
|
|
1194
1035
|
* @default
|
|
1195
1036
|
* @memberof Settings */
|
|
@@ -1198,7 +1039,12 @@ let touchGamepadSize = 80;
|
|
|
1198
1039
|
/** Transparency of touch gamepad overlay
|
|
1199
1040
|
* @default
|
|
1200
1041
|
* @memberof Settings */
|
|
1201
|
-
let touchGamepadAlpha = .
|
|
1042
|
+
let touchGamepadAlpha = .3;
|
|
1043
|
+
|
|
1044
|
+
/** Allow vibration hardware if it exists
|
|
1045
|
+
* @default
|
|
1046
|
+
* @memberof Settings */
|
|
1047
|
+
let vibrateEnable = 1;
|
|
1202
1048
|
|
|
1203
1049
|
///////////////////////////////////////////////////////////////////////////////
|
|
1204
1050
|
// Audio settings
|
|
@@ -1211,7 +1057,7 @@ let soundVolume = .5;
|
|
|
1211
1057
|
/** All audio code can be disabled and removed from build
|
|
1212
1058
|
* @default
|
|
1213
1059
|
* @memberof Settings */
|
|
1214
|
-
let soundEnable =
|
|
1060
|
+
let soundEnable = 1;
|
|
1215
1061
|
|
|
1216
1062
|
/** Default range where sound no longer plays
|
|
1217
1063
|
* @default
|
|
@@ -1244,12 +1090,12 @@ let medalDisplayWidth = 640;
|
|
|
1244
1090
|
/** Height of medal display
|
|
1245
1091
|
* @default
|
|
1246
1092
|
* @memberof Settings */
|
|
1247
|
-
let medalDisplayHeight =
|
|
1093
|
+
let medalDisplayHeight = 80;
|
|
1248
1094
|
|
|
1249
1095
|
/** Size of icon in medal display
|
|
1250
1096
|
* @default
|
|
1251
1097
|
* @memberof Settings */
|
|
1252
|
-
let medalDisplayIconSize =
|
|
1098
|
+
let medalDisplayIconSize = 50;
|
|
1253
1099
|
/*
|
|
1254
1100
|
LittleJS Object System
|
|
1255
1101
|
*/
|
|
@@ -1284,7 +1130,7 @@ let medalDisplayIconSize = 80;
|
|
|
1284
1130
|
class EngineObject
|
|
1285
1131
|
{
|
|
1286
1132
|
/** Create an engine object and adds it to the list of objects
|
|
1287
|
-
* @param {Vector2} [position=new Vector2(
|
|
1133
|
+
* @param {Vector2} [position=new Vector2()] - World space position of the object
|
|
1288
1134
|
* @param {Vector2} [size=objectDefaultSize] - World space size of the object
|
|
1289
1135
|
* @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
|
|
1290
1136
|
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
|
|
@@ -1329,12 +1175,15 @@ class EngineObject
|
|
|
1329
1175
|
this.gravityScale = 1;
|
|
1330
1176
|
/** @property {Number} [renderOrder=0] - Objects are sorted by render order */
|
|
1331
1177
|
this.renderOrder = renderOrder;
|
|
1178
|
+
/** @property {Vector2} [velocity=new Vector2()] - Velocity of the object */
|
|
1179
|
+
this.velocity = new Vector2();
|
|
1180
|
+
/** @property {Number} [angleVelocity=0] - Angular velocity of the object */
|
|
1181
|
+
this.angleVelocity = 0;
|
|
1332
1182
|
|
|
1333
1183
|
// init other internal object stuff
|
|
1334
1184
|
this.spawnTime = time;
|
|
1335
|
-
this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
|
|
1336
|
-
this.collideTiles = 1;
|
|
1337
1185
|
this.children = [];
|
|
1186
|
+
this.collideTiles = 1;
|
|
1338
1187
|
|
|
1339
1188
|
// add to list of objects
|
|
1340
1189
|
engineObjects.push(this);
|
|
@@ -1353,8 +1202,8 @@ class EngineObject
|
|
|
1353
1202
|
}
|
|
1354
1203
|
|
|
1355
1204
|
// limit max speed to prevent missing collisions
|
|
1356
|
-
this.velocity.x = clamp(this.velocity.x, objectMaxSpeed,
|
|
1357
|
-
this.velocity.y = clamp(this.velocity.y, objectMaxSpeed,
|
|
1205
|
+
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
1206
|
+
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
1358
1207
|
|
|
1359
1208
|
// apply physics
|
|
1360
1209
|
const oldPos = this.pos.copy();
|
|
@@ -1402,7 +1251,7 @@ class EngineObject
|
|
|
1402
1251
|
// if already was touching, try to push away
|
|
1403
1252
|
const deltaPos = oldPos.subtract(o.pos);
|
|
1404
1253
|
const length = deltaPos.length();
|
|
1405
|
-
const pushAwayAccel = .001; // push away if
|
|
1254
|
+
const pushAwayAccel = .001; // push away if already overlapping
|
|
1406
1255
|
const velocity = length < .01 ? randVector(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
|
|
1407
1256
|
this.velocity = this.velocity.add(velocity);
|
|
1408
1257
|
if (o.mass) // push away if not fixed
|
|
@@ -1444,8 +1293,8 @@ class EngineObject
|
|
|
1444
1293
|
|
|
1445
1294
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1446
1295
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1447
|
-
this.velocity.y = lerp(elasticity,
|
|
1448
|
-
o.velocity.y = lerp(elasticity,
|
|
1296
|
+
this.velocity.y = lerp(elasticity, inelastic, elastic0);
|
|
1297
|
+
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
1449
1298
|
}
|
|
1450
1299
|
}
|
|
1451
1300
|
if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
|
|
@@ -1465,8 +1314,8 @@ class EngineObject
|
|
|
1465
1314
|
|
|
1466
1315
|
// lerp betwen elastic or inelastic based on elasticity
|
|
1467
1316
|
const elasticity = max(this.elasticity, o.elasticity);
|
|
1468
|
-
this.velocity.x = lerp(elasticity,
|
|
1469
|
-
o.velocity.x = lerp(elasticity,
|
|
1317
|
+
this.velocity.x = lerp(elasticity, inelastic, elastic0);
|
|
1318
|
+
o.velocity.x = lerp(elasticity, inelastic, elastic1);
|
|
1470
1319
|
}
|
|
1471
1320
|
else // bounce if other object is fixed
|
|
1472
1321
|
this.velocity.x *= -this.elasticity;
|
|
@@ -1496,7 +1345,7 @@ class EngineObject
|
|
|
1496
1345
|
|
|
1497
1346
|
// adjust next velocity to settle on ground
|
|
1498
1347
|
const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
|
|
1499
|
-
if (o < 0 && o >
|
|
1348
|
+
if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
|
|
1500
1349
|
this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
|
|
1501
1350
|
|
|
1502
1351
|
// move to previous position
|
|
@@ -1597,18 +1446,6 @@ class EngineObject
|
|
|
1597
1446
|
{
|
|
1598
1447
|
ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
|
|
1599
1448
|
|
|
1600
|
-
// track collidable objects in separate list
|
|
1601
|
-
if (collideSolidObjects && !this.collideSolidObjects)
|
|
1602
|
-
{
|
|
1603
|
-
ASSERT(!engineObjectsCollide.includes(this));
|
|
1604
|
-
engineObjectsCollide.push(this);
|
|
1605
|
-
}
|
|
1606
|
-
else if (!collideSolidObjects && this.collideSolidObjects)
|
|
1607
|
-
{
|
|
1608
|
-
ASSERT(engineObjectsCollide.includes(this))
|
|
1609
|
-
engineObjectsCollide.splice(engineObjectsCollide.indexOf(this), 1);
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
1449
|
this.collideSolidObjects = collideSolidObjects;
|
|
1613
1450
|
this.isSolid = isSolid;
|
|
1614
1451
|
this.collideTiles = collideTiles;
|
|
@@ -1708,17 +1545,13 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
|
|
|
1708
1545
|
{
|
|
1709
1546
|
// calculate uvs and render
|
|
1710
1547
|
const cols = tileImageSize.x / tileSize.x |0;
|
|
1711
|
-
const uvSizeX = tileSize.x
|
|
1712
|
-
const uvSizeY = tileSize.y
|
|
1548
|
+
const uvSizeX = tileSize.x / tileImageSize.x;
|
|
1549
|
+
const uvSizeY = tileSize.y / tileImageSize.y;
|
|
1713
1550
|
const uvX = (tileIndex%cols)*uvSizeX, uvY = (tileIndex/cols|0)*uvSizeY;
|
|
1714
|
-
|
|
1715
|
-
// shrink uvs to prevent bleeding
|
|
1716
|
-
const shrinkTilesX = tileBleedShrinkFix * tileImageSizeInverse.x;
|
|
1717
|
-
const shrinkTilesY = tileBleedShrinkFix * tileImageSizeInverse.y;
|
|
1718
1551
|
|
|
1719
1552
|
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
1720
|
-
uvX +
|
|
1721
|
-
uvX -
|
|
1553
|
+
uvX + tileImageFixBleed.x, uvY + tileImageFixBleed.y,
|
|
1554
|
+
uvX - tileImageFixBleed.x + uvSizeX, uvY - tileImageFixBleed.y + uvSizeY,
|
|
1722
1555
|
color.rgbaInt(), additiveColor.rgbaInt());
|
|
1723
1556
|
}
|
|
1724
1557
|
}
|
|
@@ -1737,10 +1570,10 @@ function drawTile(pos, size=vec2(1), tileIndex=-1, tileSize=tileSizeDefault, col
|
|
|
1737
1570
|
{
|
|
1738
1571
|
// calculate uvs and render
|
|
1739
1572
|
const cols = tileImageSize.x / tileSize.x |0;
|
|
1740
|
-
const sX = (tileIndex%cols)*tileSize.x +
|
|
1741
|
-
const sY = (tileIndex/cols|0)*tileSize.y +
|
|
1742
|
-
const sWidth = tileSize.x - 2*
|
|
1743
|
-
const sHeight = tileSize.y - 2*
|
|
1573
|
+
const sX = (tileIndex%cols)*tileSize.x + tileFixBleedScale;
|
|
1574
|
+
const sY = (tileIndex/cols|0)*tileSize.y + tileFixBleedScale;
|
|
1575
|
+
const sWidth = tileSize.x - 2*tileFixBleedScale;
|
|
1576
|
+
const sHeight = tileSize.y - 2*tileFixBleedScale;
|
|
1744
1577
|
context.globalAlpha = color.a; // only alpha is supported
|
|
1745
1578
|
context.drawImage(tileImage, sX, sY, sWidth, sHeight, -.5, -.5, 1, 1);
|
|
1746
1579
|
}
|
|
@@ -1823,6 +1656,18 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
|
|
|
1823
1656
|
context.restore();
|
|
1824
1657
|
}
|
|
1825
1658
|
|
|
1659
|
+
/** Enable normal or additive blend mode
|
|
1660
|
+
* @param {Boolean} [additive=0]
|
|
1661
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
1662
|
+
* @memberof Draw */
|
|
1663
|
+
function setBlendMode(additive, useWebGL=glEnable)
|
|
1664
|
+
{
|
|
1665
|
+
if (glEnable && useWebGL)
|
|
1666
|
+
glSetBlendMode(additive);
|
|
1667
|
+
else
|
|
1668
|
+
mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1826
1671
|
/** Draw text on overlay canvas in world space
|
|
1827
1672
|
* @param {String} text
|
|
1828
1673
|
* @param {Vector2} pos
|
|
@@ -1848,16 +1693,96 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
|
|
|
1848
1693
|
overlayContext.fillText(text, pos.x, pos.y);
|
|
1849
1694
|
}
|
|
1850
1695
|
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
*
|
|
1855
|
-
|
|
1696
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1697
|
+
|
|
1698
|
+
/**
|
|
1699
|
+
* Font Image Object - Draw text on a 2D canvas by using characters in an image
|
|
1700
|
+
* <br> - 96 characters (from space to tilde) are stored in an image
|
|
1701
|
+
* <br> - Uses a default 8x8 font if none is supplied
|
|
1702
|
+
* <br> - You can also use fonts from the main tile sheet
|
|
1703
|
+
* @example
|
|
1704
|
+
* // use built in font
|
|
1705
|
+
* const font = new ImageFont;
|
|
1706
|
+
*
|
|
1707
|
+
* // draw text
|
|
1708
|
+
* font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
|
|
1709
|
+
*/
|
|
1710
|
+
|
|
1711
|
+
let engineFontImage;
|
|
1712
|
+
|
|
1713
|
+
class FontImage
|
|
1856
1714
|
{
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1715
|
+
/** Create an image font
|
|
1716
|
+
* @param {Image} [image] - The image the font is stored in, if undefined the default font is used
|
|
1717
|
+
* @param {Vector2} [tileSize=vec2(8)] - The size of the font source tiles
|
|
1718
|
+
* @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
|
|
1719
|
+
* @param {Number} [startTileIndex=0] - Tile index in image where font starts
|
|
1720
|
+
* @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
1721
|
+
*/
|
|
1722
|
+
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
|
|
1723
|
+
{
|
|
1724
|
+
if (!image && !engineFontImage)
|
|
1725
|
+
{
|
|
1726
|
+
// load default font image
|
|
1727
|
+
engineFontImage = new Image();
|
|
1728
|
+
engineFontImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAYAQAAAAA9+x6JAAAAAnRSTlMAAHaTzTgAAAGiSURBVHjaZZABhxxBEIUf6ECLBdFY+Q0PMNgf0yCgsSAGZcT9sgIPtBWwIA5wgAPEoHUyJeeSlW+gjK+fegWwtROWpVQEyWh2npdpBmTUFVhb29RINgLIukoXr5LIAvYQ5ve+1FqWEMqNKTX3FAJHyQDRZvmKWubAACcv5z5Gtg2oyCWE+Yk/8JZQX1jTTCpKAFGIgza+dJCNBF2UskRlsgwitHbSV0QLgt9sTPtsRlvJjEr8C/FARWA2bJ/TtJ7lko34dNDn6usJUMzuErP89UUBJbWeozrwLLncXczd508deAjLWipLO4Q5XGPcJvPu92cNDaN0P5G1FL0nSOzddZOrJ6rNhbXGmeDvO3TF7DeJWl4bvaYQTNHCTeuqKZmbjHaSOFes+IX/+IhHrnAkXOAsfn24EM68XieIECoccD4KZLk/odiwzeo2rovYdhvb2HYFgyznJyDpYJdYOmfXgVdJTaUi4xA2uWYNYec9BLeqdl9EsoTw582mSFDX2DxVLbNt9U3YYoeatBad1c2Tj8t2akrjaIGJNywKB/7h75/gN3vCMSaadIUTAAAAAElFTkSuQmCC';
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
this.image = image || engineFontImage;
|
|
1732
|
+
this.tileSize = tileSize;
|
|
1733
|
+
this.paddingSize = paddingSize;
|
|
1734
|
+
this.startTileIndex = startTileIndex;
|
|
1735
|
+
this.context = context;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
/** Draw text in screen space using the image font
|
|
1739
|
+
* @param {String} text
|
|
1740
|
+
* @param {Vector2} pos
|
|
1741
|
+
* @param {Number} [scale=4]
|
|
1742
|
+
* @param {Boolean} [center]
|
|
1743
|
+
*/
|
|
1744
|
+
drawTextScreen(text, pos, scale=4, center)
|
|
1745
|
+
{
|
|
1746
|
+
const context = this.context;
|
|
1747
|
+
context.save();
|
|
1748
|
+
context.imageSmoothingEnabled = !cavasPixelated;
|
|
1749
|
+
|
|
1750
|
+
const size = this.tileSize;
|
|
1751
|
+
const drawSize = size.add(this.paddingSize).scale(scale);
|
|
1752
|
+
const cols = this.image.width / this.tileSize.x |0;
|
|
1753
|
+
text.split('\n').forEach((line, i)=>
|
|
1754
|
+
{
|
|
1755
|
+
const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
|
|
1756
|
+
for(let j=line.length; j--;)
|
|
1757
|
+
{
|
|
1758
|
+
// draw each character
|
|
1759
|
+
let charCode = line[j].charCodeAt();
|
|
1760
|
+
if (charCode < 32 || charCode > 127)
|
|
1761
|
+
charCode = 127; // unknown character
|
|
1762
|
+
|
|
1763
|
+
// get the character source location and draw it
|
|
1764
|
+
const tile = this.startTileIndex + charCode - 32;
|
|
1765
|
+
const x = tile % cols;
|
|
1766
|
+
const y = tile / cols |0;
|
|
1767
|
+
const drawPos = pos.add(vec2(j,i).multiply(drawSize));
|
|
1768
|
+
context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
|
|
1769
|
+
drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
|
|
1770
|
+
}
|
|
1771
|
+
});
|
|
1772
|
+
|
|
1773
|
+
context.restore();
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
/** Draw text in world space using the image font
|
|
1777
|
+
* @param {String} text
|
|
1778
|
+
* @param {Vector2} pos
|
|
1779
|
+
* @param {Number} [scale=.25]
|
|
1780
|
+
* @param {Boolean} [center]
|
|
1781
|
+
*/
|
|
1782
|
+
drawText(text, pos, scale=1, center)
|
|
1783
|
+
{
|
|
1784
|
+
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
|
|
1785
|
+
}
|
|
1861
1786
|
}
|
|
1862
1787
|
|
|
1863
1788
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -1921,7 +1846,7 @@ const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][
|
|
|
1921
1846
|
|
|
1922
1847
|
/** Clears all input
|
|
1923
1848
|
* @memberof Input */
|
|
1924
|
-
const clearInput = ()=> inputData
|
|
1849
|
+
const clearInput = ()=> inputData = [[]];
|
|
1925
1850
|
|
|
1926
1851
|
/** Returns true if mouse button is down
|
|
1927
1852
|
* @param {Number} button
|
|
@@ -1990,7 +1915,9 @@ const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepa
|
|
|
1990
1915
|
///////////////////////////////////////////////////////////////////////////////
|
|
1991
1916
|
// Input update called by engine
|
|
1992
1917
|
|
|
1993
|
-
|
|
1918
|
+
// store input as a bit field for each key: 1 = isDown, 2 = wasPressed, 4 = wasReleased
|
|
1919
|
+
// mouse and keyboard are stored together in device 0, gamepads are in devices > 0
|
|
1920
|
+
let inputData = [[]];
|
|
1994
1921
|
|
|
1995
1922
|
function inputUpdate()
|
|
1996
1923
|
{
|
|
@@ -2016,36 +1943,38 @@ function inputUpdatePost()
|
|
|
2016
1943
|
///////////////////////////////////////////////////////////////////////////////
|
|
2017
1944
|
// Keyboard event handlers
|
|
2018
1945
|
|
|
2019
|
-
onkeydown = e=>
|
|
1946
|
+
onkeydown = (e)=>
|
|
2020
1947
|
{
|
|
2021
1948
|
if (debug && e.target != document.body) return;
|
|
2022
1949
|
e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
|
|
2023
1950
|
debug || e.preventDefault();
|
|
2024
1951
|
}
|
|
2025
|
-
onkeyup = e=>
|
|
1952
|
+
onkeyup = (e)=>
|
|
2026
1953
|
{
|
|
2027
1954
|
if (debug && e.target != document.body) return;
|
|
2028
1955
|
inputData[0][remapKeyCode(e.keyCode)] = 4;
|
|
2029
1956
|
}
|
|
2030
|
-
const remapKeyCode = c=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
1957
|
+
const remapKeyCode = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
2031
1958
|
|
|
2032
1959
|
///////////////////////////////////////////////////////////////////////////////
|
|
2033
1960
|
// Mouse event handlers
|
|
2034
1961
|
|
|
2035
|
-
onmousedown = e=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
2036
|
-
onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
2037
|
-
onmousemove = e=>
|
|
1962
|
+
onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
|
|
1963
|
+
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
1964
|
+
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
1965
|
+
onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
1966
|
+
oncontextmenu = (e)=> !1; // prevent right click menu
|
|
1967
|
+
|
|
1968
|
+
// convert a mouse or touch event position to screen space
|
|
1969
|
+
const mouseToScreen = (mousePos)=>
|
|
2038
1970
|
{
|
|
2039
1971
|
if (!mainCanvas)
|
|
2040
|
-
return; // fix bug that can occur if user clicks before page loads
|
|
1972
|
+
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
2041
1973
|
|
|
2042
|
-
// convert mouse pos to canvas space
|
|
2043
1974
|
const rect = mainCanvas.getBoundingClientRect();
|
|
2044
|
-
|
|
2045
|
-
vec2(percent(
|
|
1975
|
+
return mainCanvasSize.multiply(
|
|
1976
|
+
vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
|
|
2046
1977
|
}
|
|
2047
|
-
onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
|
|
2048
|
-
oncontextmenu = e=> !1; // prevent right click menu
|
|
2049
1978
|
|
|
2050
1979
|
///////////////////////////////////////////////////////////////////////////////
|
|
2051
1980
|
// Gamepad input
|
|
@@ -2053,6 +1982,21 @@ oncontextmenu = e=> !1; // prevent right click menu
|
|
|
2053
1982
|
const stickData = [];
|
|
2054
1983
|
function gamepadsUpdate()
|
|
2055
1984
|
{
|
|
1985
|
+
if (touchGamepadEnable && touchGamepadTimer.isSet())
|
|
1986
|
+
{
|
|
1987
|
+
// read virtual analog stick
|
|
1988
|
+
const sticks = stickData[0] || (stickData[0] = []);
|
|
1989
|
+
sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
|
|
1990
|
+
|
|
1991
|
+
// read virtual gamepad buttons
|
|
1992
|
+
const data = inputData[1] || (inputData[1] = []);
|
|
1993
|
+
for (let i=10; i--;)
|
|
1994
|
+
{
|
|
1995
|
+
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
1996
|
+
data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2056
2000
|
if (!gamepadsEnable || !navigator.getGamepads || !document.hasFocus() && !debug)
|
|
2057
2001
|
return;
|
|
2058
2002
|
|
|
@@ -2070,8 +2014,8 @@ function gamepadsUpdate()
|
|
|
2070
2014
|
// read clamp dead zone of analog sticks
|
|
2071
2015
|
const deadZone = .3, deadZoneMax = .8;
|
|
2072
2016
|
const applyDeadZone = (v)=>
|
|
2073
|
-
v > deadZone ? percent( v,
|
|
2074
|
-
v < -deadZone ? -percent(-v,
|
|
2017
|
+
v > deadZone ? percent( v, deadZone, deadZoneMax) :
|
|
2018
|
+
v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
|
|
2075
2019
|
|
|
2076
2020
|
// read analog sticks
|
|
2077
2021
|
for (let j = 0; j < gamepad.axes.length-1; j+=2)
|
|
@@ -2083,6 +2027,7 @@ function gamepadsUpdate()
|
|
|
2083
2027
|
const button = gamepad.buttons[j];
|
|
2084
2028
|
data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
|
|
2085
2029
|
isUsingGamepad |= !i && button.pressed;
|
|
2030
|
+
touchGamepadEnable && touchGamepadTimer.unset(); // disable touch gamepad if using real gamepad
|
|
2086
2031
|
}
|
|
2087
2032
|
|
|
2088
2033
|
if (gamepadDirectionEmulateStick)
|
|
@@ -2096,6 +2041,17 @@ function gamepadsUpdate()
|
|
|
2096
2041
|
}
|
|
2097
2042
|
}
|
|
2098
2043
|
|
|
2044
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2045
|
+
|
|
2046
|
+
/** Pulse the vibration hardware if it exists
|
|
2047
|
+
* @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
|
|
2048
|
+
* @memberof Input */
|
|
2049
|
+
const vibrate = (pattern)=> vibrateEnable && Navigator.vibrate && Navigator.vibrate(pattern);
|
|
2050
|
+
|
|
2051
|
+
/** Cancel any ongoing vibration
|
|
2052
|
+
* @memberof Input */
|
|
2053
|
+
const vibrateStop = ()=> vibrate(0);
|
|
2054
|
+
|
|
2099
2055
|
///////////////////////////////////////////////////////////////////////////////
|
|
2100
2056
|
// Touch input
|
|
2101
2057
|
|
|
@@ -2103,7 +2059,9 @@ function gamepadsUpdate()
|
|
|
2103
2059
|
* @const {boolean}
|
|
2104
2060
|
* @memberof Input */
|
|
2105
2061
|
const isTouchDevice = window.ontouchstart !== undefined;
|
|
2106
|
-
|
|
2062
|
+
|
|
2063
|
+
// try to enable touch mouse
|
|
2064
|
+
if (isTouchDevice)
|
|
2107
2065
|
{
|
|
2108
2066
|
// handle all touch events the same way
|
|
2109
2067
|
let wasTouching, hadTouchInput;
|
|
@@ -2134,10 +2092,12 @@ if (touchMouseEnable && isTouchDevice)
|
|
|
2134
2092
|
}
|
|
2135
2093
|
|
|
2136
2094
|
///////////////////////////////////////////////////////////////////////////////
|
|
2137
|
-
//
|
|
2095
|
+
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
2096
|
+
|
|
2097
|
+
// touch input internal variables
|
|
2098
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
|
|
2138
2099
|
|
|
2139
2100
|
// create the touch gamepad, called automatically by the engine
|
|
2140
|
-
const touchGamepadTimer = new Timer;
|
|
2141
2101
|
function touchGamepadCreate()
|
|
2142
2102
|
{
|
|
2143
2103
|
if (!touchGamepadEnable || !isTouchDevice)
|
|
@@ -2147,6 +2107,10 @@ function touchGamepadCreate()
|
|
|
2147
2107
|
{
|
|
2148
2108
|
if (!touchGamepadEnable)
|
|
2149
2109
|
return;
|
|
2110
|
+
|
|
2111
|
+
// clear touch gamepad input
|
|
2112
|
+
touchGamepadStick = vec2();
|
|
2113
|
+
touchGamepadButtons = [];
|
|
2150
2114
|
|
|
2151
2115
|
const touching = e.touches.length;
|
|
2152
2116
|
if (touching)
|
|
@@ -2156,6 +2120,13 @@ function touchGamepadCreate()
|
|
|
2156
2120
|
// set that gamepad is active
|
|
2157
2121
|
isUsingGamepad = 1;
|
|
2158
2122
|
touchGamepadTimer.set();
|
|
2123
|
+
|
|
2124
|
+
if (paused)
|
|
2125
|
+
{
|
|
2126
|
+
// touch anywhere to press start when paused
|
|
2127
|
+
touchGamepadButtons[9] = 1;
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2159
2130
|
}
|
|
2160
2131
|
|
|
2161
2132
|
// get center of left and right sides
|
|
@@ -2163,38 +2134,34 @@ function touchGamepadCreate()
|
|
|
2163
2134
|
const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
|
|
2164
2135
|
const startCenter = mainCanvasSize.scale(.5);
|
|
2165
2136
|
|
|
2166
|
-
// update virtual gamepad
|
|
2167
|
-
const data = inputData[1] || (inputData[1] = []);
|
|
2168
|
-
const sticks = stickData[0] || (stickData[0] = []);
|
|
2169
|
-
sticks[0] = vec2();
|
|
2170
|
-
|
|
2171
2137
|
// check each touch point
|
|
2172
|
-
const buttons = [];
|
|
2173
2138
|
for (const touch of e.touches)
|
|
2174
2139
|
{
|
|
2175
|
-
const touchPos = vec2(touch.clientX, touch.clientY);
|
|
2140
|
+
const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
|
|
2176
2141
|
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
2177
2142
|
{
|
|
2178
2143
|
// virtual analog stick
|
|
2179
|
-
|
|
2180
|
-
|
|
2144
|
+
if (touchGamepadAnalog)
|
|
2145
|
+
touchGamepadStick = touchPos.subtract(stickCenter).scale(2/touchGamepadSize).clampLength();
|
|
2146
|
+
else
|
|
2147
|
+
{
|
|
2148
|
+
// 8 way dpad
|
|
2149
|
+
const angle = touchPos.subtract(stickCenter).angle();
|
|
2150
|
+
touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
|
|
2151
|
+
}
|
|
2181
2152
|
}
|
|
2182
2153
|
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
2183
2154
|
{
|
|
2184
2155
|
// virtual face buttons
|
|
2185
2156
|
const button = touchPos.subtract(buttonCenter).direction();
|
|
2186
|
-
|
|
2157
|
+
touchGamepadButtons[button] = 1;
|
|
2187
2158
|
}
|
|
2188
2159
|
else if (touchPos.distance(startCenter) < touchGamepadSize)
|
|
2189
2160
|
{
|
|
2190
|
-
// virtual start button
|
|
2191
|
-
|
|
2161
|
+
// virtual start button in center
|
|
2162
|
+
touchGamepadButtons[9] = 1;
|
|
2192
2163
|
}
|
|
2193
2164
|
}
|
|
2194
|
-
|
|
2195
|
-
// read virtual buttons
|
|
2196
|
-
for (let j=10; j--;)
|
|
2197
|
-
data[j] = buttons[j] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
|
|
2198
2165
|
}
|
|
2199
2166
|
}
|
|
2200
2167
|
|
|
@@ -2204,37 +2171,48 @@ function touchGamepadRender()
|
|
|
2204
2171
|
if (!touchGamepadEnable || !touchGamepadTimer.isSet())
|
|
2205
2172
|
return;
|
|
2206
2173
|
|
|
2207
|
-
// fade off when not touching
|
|
2208
|
-
const alpha = percent(touchGamepadTimer.get(), 4,
|
|
2209
|
-
if (!alpha)
|
|
2174
|
+
// fade off when not touching or paused
|
|
2175
|
+
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
2176
|
+
if (!alpha || paused)
|
|
2210
2177
|
return;
|
|
2211
2178
|
|
|
2212
2179
|
// setup the canvas
|
|
2213
2180
|
overlayContext.save();
|
|
2214
2181
|
overlayContext.globalAlpha = alpha*touchGamepadAlpha;
|
|
2215
|
-
overlayContext.fillStyle = '#fff6';
|
|
2216
2182
|
overlayContext.strokeStyle = '#fff';
|
|
2217
2183
|
overlayContext.lineWidth = 3;
|
|
2218
2184
|
|
|
2219
|
-
// draw start button
|
|
2220
|
-
//overlayContext.beginPath();
|
|
2221
|
-
//overlayContext.arc(mainCanvasSize.x/2, mainCanvasSize.y/2, touchGamepadSize*.9, 0,9);
|
|
2222
|
-
//overlayContext.fill();
|
|
2223
|
-
//overlayContext.stroke();
|
|
2224
|
-
|
|
2225
2185
|
// draw left analog stick
|
|
2186
|
+
overlayContext.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
|
|
2226
2187
|
overlayContext.beginPath();
|
|
2227
|
-
overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize*.9, 0,9);
|
|
2228
|
-
overlayContext.fill();
|
|
2229
|
-
overlayContext.stroke();
|
|
2230
2188
|
|
|
2189
|
+
const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2190
|
+
if (touchGamepadAnalog)
|
|
2191
|
+
{
|
|
2192
|
+
overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
|
|
2193
|
+
overlayContext.fill();
|
|
2194
|
+
overlayContext.stroke();
|
|
2195
|
+
}
|
|
2196
|
+
else // draw cross shaped gamepad
|
|
2197
|
+
{
|
|
2198
|
+
for(let i=10; i--;)
|
|
2199
|
+
{
|
|
2200
|
+
const angle = i*PI/4;
|
|
2201
|
+
overlayContext.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
2202
|
+
i%2 && overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
|
|
2203
|
+
i==1 && overlayContext.fill();
|
|
2204
|
+
}
|
|
2205
|
+
overlayContext.stroke();
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2231
2208
|
// draw right face buttons
|
|
2232
2209
|
const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
2233
2210
|
for (let i=4; i--;)
|
|
2234
2211
|
{
|
|
2235
|
-
const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize
|
|
2212
|
+
const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize/2));
|
|
2213
|
+
overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
2236
2214
|
overlayContext.beginPath();
|
|
2237
|
-
overlayContext.arc(pos.x, pos.y, touchGamepadSize
|
|
2215
|
+
overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
2238
2216
|
overlayContext.fill();
|
|
2239
2217
|
overlayContext.stroke();
|
|
2240
2218
|
}
|
|
@@ -2313,7 +2291,7 @@ class Sound
|
|
|
2313
2291
|
return; // out of range
|
|
2314
2292
|
|
|
2315
2293
|
// attenuate volume by distance
|
|
2316
|
-
volume *= percent(lengthSquared**.5, range*this.taper
|
|
2294
|
+
volume *= percent(lengthSquared**.5, range, range*this.taper);
|
|
2317
2295
|
}
|
|
2318
2296
|
|
|
2319
2297
|
// get pan from screen space coords
|
|
@@ -2439,7 +2417,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
2439
2417
|
|
|
2440
2418
|
/** Stop all queued speech
|
|
2441
2419
|
* @memberof Audio */
|
|
2442
|
-
const
|
|
2420
|
+
const speakStop = ()=> speechSynthesis && speechSynthesis.cancel();
|
|
2443
2421
|
|
|
2444
2422
|
/** Get frequency of a note on a musical scale
|
|
2445
2423
|
* @param {Number} semitoneOffset - How many semitones away from the root note
|
|
@@ -2470,6 +2448,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2470
2448
|
if (!audioContext)
|
|
2471
2449
|
audioContext = new (window.AudioContext||webkitAudioContext);
|
|
2472
2450
|
|
|
2451
|
+
// fix stalled audio
|
|
2452
|
+
audioContext.resume();
|
|
2453
|
+
|
|
2454
|
+
// prevent sounds from building up if they can't be played
|
|
2455
|
+
if (audioContext.state != 'running')
|
|
2456
|
+
return;
|
|
2457
|
+
|
|
2473
2458
|
// create buffer and source
|
|
2474
2459
|
const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, zzfxR),
|
|
2475
2460
|
source = audioContext.createBufferSource();
|
|
@@ -2480,11 +2465,18 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
2480
2465
|
source.playbackRate.value = rate;
|
|
2481
2466
|
source.loop = loop;
|
|
2482
2467
|
|
|
2483
|
-
// create
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2468
|
+
// create and connect gain node (createGain is more widley spported then GainNode construtor)
|
|
2469
|
+
const gainNode = audioContext.createGain();
|
|
2470
|
+
gainNode.gain.value = soundVolume*volume;
|
|
2471
|
+
gainNode.connect(audioContext.destination);
|
|
2472
|
+
|
|
2473
|
+
// connect source to gain
|
|
2474
|
+
(
|
|
2475
|
+
window.StereoPannerNode ? // create pan node if possible
|
|
2476
|
+
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
|
|
2477
|
+
: source
|
|
2478
|
+
)
|
|
2479
|
+
.connect(gainNode);
|
|
2488
2480
|
|
|
2489
2481
|
// play and return sound
|
|
2490
2482
|
source.start();
|
|
@@ -2842,8 +2834,9 @@ class TileLayerData
|
|
|
2842
2834
|
/**
|
|
2843
2835
|
* Tile layer object - cached rendering system for tile layers
|
|
2844
2836
|
* <br> - Each Tile layer is rendered to an off screen canvas
|
|
2845
|
-
* <br> -
|
|
2846
|
-
* <br> -
|
|
2837
|
+
* <br> - To allow dynamic modifications, layers are rendered using canvas 2d
|
|
2838
|
+
* <br> - Some devices like mobile phones are limited to 4k texture resolution
|
|
2839
|
+
* <br> - So with 16x16 tiles this limits layers to 256x256 on mobile devices
|
|
2847
2840
|
* @extends EngineObject
|
|
2848
2841
|
* @example
|
|
2849
2842
|
* // create tile collision and visible tile layer
|
|
@@ -2853,11 +2846,11 @@ class TileLayerData
|
|
|
2853
2846
|
class TileLayer extends EngineObject
|
|
2854
2847
|
{
|
|
2855
2848
|
/** Create a tile layer object
|
|
2856
|
-
* @param {Vector2} [position=new Vector2(
|
|
2857
|
-
* @param {Vector2} [size=objectDefaultSize]
|
|
2858
|
-
* @param {Vector2} [tileSize=tileSizeDefault]
|
|
2859
|
-
* @param {Vector2} [scale=new Vector2(1,1)]
|
|
2860
|
-
* @param {Number} [renderOrder=0]
|
|
2849
|
+
* @param {Vector2} [position=new Vector2()] - World space position
|
|
2850
|
+
* @param {Vector2} [size=objectDefaultSize] - World space size
|
|
2851
|
+
* @param {Vector2} [tileSize=tileSizeDefault] - Size of tiles in source pixels
|
|
2852
|
+
* @param {Vector2} [scale=new Vector2(1,1)] - How much to scale this layer when rendered
|
|
2853
|
+
* @param {Number} [renderOrder=0] - Objects sorted by renderOrder before being rendered
|
|
2861
2854
|
*/
|
|
2862
2855
|
constructor(pos, size=tileCollisionSize, tileSize=tileSizeDefault, scale=vec2(1), renderOrder=0)
|
|
2863
2856
|
{
|
|
@@ -2908,7 +2901,7 @@ class TileLayer extends EngineObject
|
|
|
2908
2901
|
// flush and copy gl canvas because tile canvas does not use webgl
|
|
2909
2902
|
glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
2910
2903
|
|
|
2911
|
-
// draw the entire cached level onto the
|
|
2904
|
+
// draw the entire cached level onto the canvas
|
|
2912
2905
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
2913
2906
|
(this.isOverlay ? overlayContext : mainContext).drawImage
|
|
2914
2907
|
(
|
|
@@ -2917,25 +2910,25 @@ class TileLayer extends EngineObject
|
|
|
2917
2910
|
);
|
|
2918
2911
|
}
|
|
2919
2912
|
|
|
2920
|
-
/** Draw all the tile data to an offscreen canvas
|
|
2913
|
+
/** Draw all the tile data to an offscreen canvas
|
|
2914
|
+
* - This may be slow in some browsers
|
|
2915
|
+
*/
|
|
2921
2916
|
redraw()
|
|
2922
2917
|
{
|
|
2923
|
-
this.redrawStart();
|
|
2918
|
+
this.redrawStart(1);
|
|
2924
2919
|
this.drawAllTileData();
|
|
2925
2920
|
this.redrawEnd();
|
|
2926
2921
|
}
|
|
2927
2922
|
|
|
2928
2923
|
/** Call to start the redraw process
|
|
2929
|
-
* @param {Boolean} [clear=
|
|
2930
|
-
redrawStart(clear =
|
|
2924
|
+
* @param {Boolean} [clear=0] - Should it clear the canvas before drawing */
|
|
2925
|
+
redrawStart(clear = 0)
|
|
2931
2926
|
{
|
|
2932
|
-
// clear and set size
|
|
2933
|
-
const width = this.size.x * this.tileSize.x;
|
|
2934
|
-
const height = this.size.y * this.tileSize.y;
|
|
2935
2927
|
if (clear)
|
|
2936
2928
|
{
|
|
2937
|
-
|
|
2938
|
-
this.canvas.
|
|
2929
|
+
// clear and set size
|
|
2930
|
+
this.canvas.width = this.size.x * this.tileSize.x;
|
|
2931
|
+
this.canvas.height = this.size.y * this.tileSize.y;
|
|
2939
2932
|
}
|
|
2940
2933
|
|
|
2941
2934
|
// save current render settings
|
|
@@ -3025,7 +3018,7 @@ class TileLayer extends EngineObject
|
|
|
3025
3018
|
else
|
|
3026
3019
|
{
|
|
3027
3020
|
const cols = tileImage.width/tileSize.x;
|
|
3028
|
-
context.globalAlpha = color.a; //
|
|
3021
|
+
context.globalAlpha = color.a; // only alpha, no color, is supported in this mode
|
|
3029
3022
|
context.drawImage(tileImage,
|
|
3030
3023
|
(tileIndex%cols)*tileSize.x, (tileIndex/cols|0)*tileSize.x,
|
|
3031
3024
|
tileSize.x, tileSize.y, -.5, -.5, 1, 1);
|
|
@@ -3057,9 +3050,9 @@ class TileLayer extends EngineObject
|
|
|
3057
3050
|
* let pos = vec2(2,3);
|
|
3058
3051
|
* let particleEmiter = new ParticleEmitter
|
|
3059
3052
|
* (
|
|
3060
|
-
* pos, 1, 0, 500, PI, // pos, emitSize, emitTime, emitRate, emiteCone
|
|
3053
|
+
* pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
|
|
3061
3054
|
* 0, vec2(16), // tileIndex, tileSize
|
|
3062
|
-
* new Color,
|
|
3055
|
+
* new Color(1,1,1), new Color(0,0,0), // colorStartA, colorStartB
|
|
3063
3056
|
* new Color(1,1,1,0), new Color(0,0,0,0), // colorEndA, colorEndB
|
|
3064
3057
|
* 2, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
3065
3058
|
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate,
|
|
@@ -3070,6 +3063,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3070
3063
|
{
|
|
3071
3064
|
/** Create a particle system with the given settings
|
|
3072
3065
|
* @param {Vector2} position - World space position of the emitter
|
|
3066
|
+
* @param {Number} [angle=0] - Angle to rotate the object
|
|
3073
3067
|
* @param {Number} [emitSize=0] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
3074
3068
|
* @param {Number} [emitTime=0] - How long to stay alive (0 is forever)
|
|
3075
3069
|
* @param {Number} [emitRate=100] - How many particles per second to spawn, does not emit if 0
|
|
@@ -3093,12 +3087,13 @@ class ParticleEmitter extends EngineObject
|
|
|
3093
3087
|
* @param {Number} [randomness=.2] - Apply extra randomness percent
|
|
3094
3088
|
* @param {Boolean} [collideTiles=0] - Do particles collide against tiles
|
|
3095
3089
|
* @param {Boolean} [additive=0] - Should particles use addtive blend
|
|
3096
|
-
* @param {Boolean} [randomColorLinear=
|
|
3090
|
+
* @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
|
|
3097
3091
|
* @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
|
|
3098
3092
|
*/
|
|
3099
3093
|
constructor
|
|
3100
3094
|
(
|
|
3101
3095
|
pos,
|
|
3096
|
+
angle,
|
|
3102
3097
|
emitSize = 0,
|
|
3103
3098
|
emitTime = 0,
|
|
3104
3099
|
emitRate = 100,
|
|
@@ -3126,7 +3121,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3126
3121
|
renderOrder = additive ? 1e9 : 0
|
|
3127
3122
|
)
|
|
3128
3123
|
{
|
|
3129
|
-
super(pos, new Vector2, tileIndex, tileSize,
|
|
3124
|
+
super(pos, new Vector2, tileIndex, tileSize, angle, undefined, renderOrder);
|
|
3130
3125
|
|
|
3131
3126
|
// emitter settings
|
|
3132
3127
|
/** @property {Number} - World space size of the emitter (float for circle diameter, vec2 for rect) */
|
|
@@ -3194,9 +3189,9 @@ class ParticleEmitter extends EngineObject
|
|
|
3194
3189
|
if (!this.emitTime || this.getAliveTime() <= this.emitTime)
|
|
3195
3190
|
{
|
|
3196
3191
|
// emit particles
|
|
3197
|
-
if (this.emitRate)
|
|
3192
|
+
if (this.emitRate * particleEmitRateScale)
|
|
3198
3193
|
{
|
|
3199
|
-
const rate = 1/this.emitRate;
|
|
3194
|
+
const rate = 1/this.emitRate/particleEmitRateScale;
|
|
3200
3195
|
for (this.emitTimeBuffer += timeDelta; this.emitTimeBuffer > 0; this.emitTimeBuffer -= rate)
|
|
3201
3196
|
this.emitParticle();
|
|
3202
3197
|
}
|
|
@@ -3359,7 +3354,7 @@ function medalsInit(saveName)
|
|
|
3359
3354
|
{
|
|
3360
3355
|
// check if medals are unlocked
|
|
3361
3356
|
medalsSaveName = saveName;
|
|
3362
|
-
debugMedals || medals.forEach(medal=> medal.unlocked = localStorage[medal.storageKey()]);
|
|
3357
|
+
debugMedals || medals.forEach(medal=> medal.unlocked = 1 || localStorage[medal.storageKey()]);
|
|
3363
3358
|
}
|
|
3364
3359
|
|
|
3365
3360
|
/**
|
|
@@ -3392,13 +3387,9 @@ class Medal
|
|
|
3392
3387
|
this.name = name;
|
|
3393
3388
|
this.description = description;
|
|
3394
3389
|
this.icon = icon;
|
|
3395
|
-
|
|
3390
|
+
this.image = new Image();
|
|
3396
3391
|
if (src)
|
|
3397
|
-
{
|
|
3398
|
-
// load image
|
|
3399
|
-
this.image = new Image();
|
|
3400
3392
|
this.image.src = src;
|
|
3401
|
-
}
|
|
3402
3393
|
}
|
|
3403
3394
|
|
|
3404
3395
|
/** Unlocks a medal if not already unlocked */
|
|
@@ -3428,21 +3419,23 @@ class Medal
|
|
|
3428
3419
|
const y = -medalDisplayHeight*hidePercent;
|
|
3429
3420
|
|
|
3430
3421
|
// draw containing rect and clip to that region
|
|
3431
|
-
context.
|
|
3422
|
+
context.save();
|
|
3423
|
+
context.beginPath();
|
|
3432
3424
|
context.fillStyle = '#ddd'
|
|
3433
3425
|
context.fill(context.rect(x, y, width, medalDisplayHeight));
|
|
3434
|
-
context.strokeStyle =
|
|
3435
|
-
context.lineWidth =
|
|
3426
|
+
context.strokeStyle = '#000';
|
|
3427
|
+
context.lineWidth = 3;
|
|
3436
3428
|
context.stroke();
|
|
3437
3429
|
context.clip();
|
|
3438
3430
|
|
|
3439
3431
|
// draw the icon and text
|
|
3440
3432
|
this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
|
|
3441
3433
|
context.textAlign = 'left';
|
|
3442
|
-
context.font = '
|
|
3443
|
-
context.fillText(this.name, x+medalDisplayIconSize+
|
|
3444
|
-
context.font = '
|
|
3445
|
-
context.
|
|
3434
|
+
context.font = '38px '+ fontDefault;
|
|
3435
|
+
context.fillText(this.name, x+medalDisplayIconSize+30, y+28);
|
|
3436
|
+
context.font = '24px '+ fontDefault;
|
|
3437
|
+
context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
|
|
3438
|
+
context.restore();
|
|
3446
3439
|
}
|
|
3447
3440
|
|
|
3448
3441
|
/** Render the icon for a medal
|
|
@@ -3454,11 +3447,11 @@ class Medal
|
|
|
3454
3447
|
{
|
|
3455
3448
|
// draw the image or icon
|
|
3456
3449
|
const context = overlayContext;
|
|
3450
|
+
context.fillStyle = '#000';
|
|
3457
3451
|
context.textAlign = 'center';
|
|
3458
3452
|
context.textBaseline = 'middle';
|
|
3459
|
-
context.font = size*.
|
|
3460
|
-
|
|
3461
|
-
if (this.image)
|
|
3453
|
+
context.font = size*.7 + 'px '+ fontDefault;
|
|
3454
|
+
if (this.image.src)
|
|
3462
3455
|
context.drawImage(this.image, x-size/2, y-size/2, size, size);
|
|
3463
3456
|
else
|
|
3464
3457
|
context.fillText(this.icon, x, y); // show icon if there is no image
|
|
@@ -3534,7 +3527,6 @@ class Newgrounds
|
|
|
3534
3527
|
if (medal)
|
|
3535
3528
|
{
|
|
3536
3529
|
// copy newgrounds medal data
|
|
3537
|
-
medal.image = new Image();
|
|
3538
3530
|
medal.image.src = newgroundsMedal['icon'];
|
|
3539
3531
|
medal.name = newgroundsMedal['name'];
|
|
3540
3532
|
medal.description = newgroundsMedal['description'];
|
|
@@ -3562,9 +3554,6 @@ class Newgrounds
|
|
|
3562
3554
|
* @param {Number} value - The score value */
|
|
3563
3555
|
postScore(id, value) { return this.call('ScoreBoard.postScore', {'id':id, 'value':value}, 1); }
|
|
3564
3556
|
|
|
3565
|
-
/** Send message to log a view */
|
|
3566
|
-
logView() { return this.call('App.logView', {'host':this.host}, 1); }
|
|
3567
|
-
|
|
3568
3557
|
/** Get scores from a scoreboard
|
|
3569
3558
|
* @param {Number} id - The scoreboard id
|
|
3570
3559
|
* @param {String} [user=0] - A user's id or name
|
|
@@ -3576,6 +3565,9 @@ class Newgrounds
|
|
|
3576
3565
|
getScores(id, user=0, social=0, skip=0, limit=10)
|
|
3577
3566
|
{ return this.call('ScoreBoard.getScores', {'id':id, 'user':user, 'social':social, 'skip':skip, 'limit':limit}); }
|
|
3578
3567
|
|
|
3568
|
+
/** Send message to log a view */
|
|
3569
|
+
logView() { return this.call('App.logView', {'host':this.host}, 1); }
|
|
3570
|
+
|
|
3579
3571
|
/** Send a message to call a component of the Newgrounds API
|
|
3580
3572
|
* @param {String} component - Name of the component
|
|
3581
3573
|
* @param {Object} [parameters=0] - Parameters to use for call
|
|
@@ -3663,38 +3655,33 @@ function glInit()
|
|
|
3663
3655
|
|
|
3664
3656
|
// create the canvas and tile texture
|
|
3665
3657
|
glCanvas = document.createElement('canvas');
|
|
3666
|
-
glContext = glCanvas.getContext('webgl');
|
|
3667
|
-
|
|
3658
|
+
glContext = glCanvas.getContext('webgl', {antialias: false});
|
|
3659
|
+
glCanvas.style = styleCanvas;
|
|
3668
3660
|
glTileTexture = glCreateTexture(tileImage);
|
|
3669
3661
|
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
3673
|
-
document.body.appendChild(glCanvas);
|
|
3674
|
-
glCanvas.style = mainCanvas.style.cssText;
|
|
3675
|
-
}
|
|
3662
|
+
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
3663
|
+
glOverlay && document.body.appendChild(glCanvas);
|
|
3676
3664
|
|
|
3677
3665
|
// setup vertex and fragment shaders
|
|
3678
3666
|
glShader = glCreateProgram(
|
|
3679
|
-
'precision highp float;'+
|
|
3680
|
-
'uniform mat4 m;'+
|
|
3681
|
-
'attribute
|
|
3682
|
-
'attribute
|
|
3683
|
-
'
|
|
3684
|
-
'varying
|
|
3685
|
-
'
|
|
3686
|
-
'
|
|
3687
|
-
'
|
|
3688
|
-
'
|
|
3689
|
-
'}' // end of shader
|
|
3667
|
+
'precision highp float;'+ // use highp for better accuracy, lowp for better perf
|
|
3668
|
+
'uniform mat4 m;'+ // transform matrix
|
|
3669
|
+
'attribute vec2 p,t;'+ // position, uv
|
|
3670
|
+
'attribute vec4 c,a;'+ // color, additiveColor
|
|
3671
|
+
'varying vec2 v;'+ // return uv
|
|
3672
|
+
'varying vec4 d,e;'+ // return color, additiveColor
|
|
3673
|
+
'void main(){'+ // shader entry point
|
|
3674
|
+
'gl_Position=m*vec4(p,1,1);'+ // transform position
|
|
3675
|
+
'v=t;d=c;e=a;'+ // pass stuff to fragment shader
|
|
3676
|
+
'}' // end of shader
|
|
3690
3677
|
,
|
|
3691
|
-
'precision
|
|
3692
|
-
'varying vec2 v;'+
|
|
3693
|
-
'varying vec4 d,e;'+
|
|
3694
|
-
'uniform sampler2D
|
|
3695
|
-
'void main(){'+
|
|
3696
|
-
'gl_FragColor=texture2D(
|
|
3697
|
-
'}'
|
|
3678
|
+
'precision highp float;'+ // use highp for better accuracy, lowp for better perf
|
|
3679
|
+
'varying vec2 v;'+ // uv
|
|
3680
|
+
'varying vec4 d,e;'+ // color, additiveColor
|
|
3681
|
+
'uniform sampler2D s;'+ // texture
|
|
3682
|
+
'void main(){'+ // shader entry point
|
|
3683
|
+
'gl_FragColor=texture2D(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
3684
|
+
'}' // end of shader
|
|
3698
3685
|
);
|
|
3699
3686
|
|
|
3700
3687
|
// init buffers
|
|
@@ -3704,6 +3691,7 @@ function glInit()
|
|
|
3704
3691
|
glColorData = new Uint32Array(glVertexData);
|
|
3705
3692
|
|
|
3706
3693
|
// setup the vertex data array
|
|
3694
|
+
let offset = glBatchCount = 0;
|
|
3707
3695
|
const initVertexAttribArray = (name, type, typeSize, size, normalize=0)=>
|
|
3708
3696
|
{
|
|
3709
3697
|
const location = glContext.getAttribLocation(glShader, name);
|
|
@@ -3711,13 +3699,10 @@ function glInit()
|
|
|
3711
3699
|
glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
|
|
3712
3700
|
offset += size*typeSize;
|
|
3713
3701
|
}
|
|
3714
|
-
let offset = glBatchCount = 0;
|
|
3715
|
-
initVertexAttribArray('a', gl_FLOAT, 4, 1); // angle
|
|
3716
3702
|
initVertexAttribArray('p', gl_FLOAT, 4, 2); // position
|
|
3717
|
-
initVertexAttribArray('s', gl_FLOAT, 4, 2); // size
|
|
3718
3703
|
initVertexAttribArray('t', gl_FLOAT, 4, 2); // texture coords
|
|
3719
3704
|
initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
|
|
3720
|
-
initVertexAttribArray('
|
|
3705
|
+
initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
3721
3706
|
}
|
|
3722
3707
|
|
|
3723
3708
|
/** Set the WebGl blend mode, normally you should call setBlendMode instead
|
|
@@ -3738,7 +3723,8 @@ function glSetBlendMode(additive)
|
|
|
3738
3723
|
function glSetTexture(texture=glTileTexture)
|
|
3739
3724
|
{
|
|
3740
3725
|
if (!glEnable) return;
|
|
3741
|
-
|
|
3726
|
+
|
|
3727
|
+
// must flush cache with the old texture to set a new one
|
|
3742
3728
|
if (texture != glActiveTexture)
|
|
3743
3729
|
glFlush();
|
|
3744
3730
|
|
|
@@ -3831,6 +3817,7 @@ function glPreRender(width, height, cameraX, cameraY, cameraScale)
|
|
|
3831
3817
|
|
|
3832
3818
|
// clear and set to same size as main canvas
|
|
3833
3819
|
glContext.viewport(0, 0, glCanvas.width = width, glCanvas.height = height);
|
|
3820
|
+
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3834
3821
|
|
|
3835
3822
|
// set up the shader
|
|
3836
3823
|
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = glTileTexture);
|
|
@@ -3874,15 +3861,13 @@ function glFlush()
|
|
|
3874
3861
|
* @memberof WebGL */
|
|
3875
3862
|
function glCopyToContext(context, forceDraw)
|
|
3876
3863
|
{
|
|
3877
|
-
if (!glEnable || !glBatchCount)
|
|
3864
|
+
if (!glEnable || !glBatchCount) return;
|
|
3878
3865
|
|
|
3879
3866
|
glFlush();
|
|
3867
|
+
|
|
3868
|
+
// do not draw in overlay mode because the canvas is visible
|
|
3880
3869
|
if (!glOverlay || forceDraw)
|
|
3881
|
-
{
|
|
3882
|
-
// do not draw/clear in overlay mode because the canvas is visible
|
|
3883
3870
|
context.drawImage(glCanvas, 0, 0);
|
|
3884
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
3885
|
-
}
|
|
3886
3871
|
}
|
|
3887
3872
|
|
|
3888
3873
|
/** Add a sprite to the gl draw list, used by all gl draw functions
|
|
@@ -3905,51 +3890,49 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba=0xffffff
|
|
|
3905
3890
|
// flush if there is no room for more verts or if different blend mode
|
|
3906
3891
|
if (glBatchCount == gl_MAX_BATCH || glBatchAdditive != glAdditive)
|
|
3907
3892
|
glFlush();
|
|
3893
|
+
|
|
3894
|
+
// prepare to create the verts from size and angle
|
|
3895
|
+
const c = Math.cos(angle)/2, s = Math.sin(angle)/2;
|
|
3896
|
+
const cx = c*sizeX, cy = c*sizeY, sx = s*sizeX, sy = s*sizeY;
|
|
3908
3897
|
|
|
3909
3898
|
// setup 2 triangles to form a quad
|
|
3910
3899
|
let offset = glBatchCount++ * gl_VERTICES_PER_QUAD * gl_INDICIES_PER_VERT;
|
|
3911
3900
|
|
|
3912
3901
|
// vertex 0
|
|
3913
|
-
glPositionData[offset++] =
|
|
3914
|
-
glPositionData[offset++] =
|
|
3915
|
-
glPositionData[offset++] =
|
|
3916
|
-
|
|
3917
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3902
|
+
glPositionData[offset++] = x - cx - sy;
|
|
3903
|
+
glPositionData[offset++] = y - cy + sx;
|
|
3904
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3905
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3918
3906
|
|
|
3919
3907
|
// vertex 1
|
|
3920
|
-
glPositionData[offset++] =
|
|
3921
|
-
glPositionData[offset++] =
|
|
3922
|
-
glPositionData[offset++] =
|
|
3923
|
-
|
|
3924
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3908
|
+
glPositionData[offset++] = x + cx + sy;
|
|
3909
|
+
glPositionData[offset++] = y + cy - sx;
|
|
3910
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3911
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3925
3912
|
|
|
3926
3913
|
// vertex 2
|
|
3927
|
-
glPositionData[offset++] =
|
|
3928
|
-
glPositionData[offset++] =
|
|
3929
|
-
glPositionData[offset++] =
|
|
3930
|
-
|
|
3931
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3914
|
+
glPositionData[offset++] = x - cx + sy;
|
|
3915
|
+
glPositionData[offset++] = y + cy + sx;
|
|
3916
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv0Y;
|
|
3917
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3932
3918
|
|
|
3933
3919
|
// vertex 0
|
|
3934
|
-
glPositionData[offset++] =
|
|
3935
|
-
glPositionData[offset++] =
|
|
3936
|
-
glPositionData[offset++] =
|
|
3937
|
-
|
|
3938
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3920
|
+
glPositionData[offset++] = x - cx - sy;
|
|
3921
|
+
glPositionData[offset++] = y - cy + sx;
|
|
3922
|
+
glPositionData[offset++] = uv0X; glPositionData[offset++] = uv1Y;
|
|
3923
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3939
3924
|
|
|
3940
3925
|
// vertex 3
|
|
3941
|
-
glPositionData[offset++] =
|
|
3942
|
-
glPositionData[offset++] =
|
|
3943
|
-
glPositionData[offset++] =
|
|
3944
|
-
|
|
3945
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3926
|
+
glPositionData[offset++] = x + cx - sy;
|
|
3927
|
+
glPositionData[offset++] = y - cy - sx;
|
|
3928
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv1Y;
|
|
3929
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3946
3930
|
|
|
3947
3931
|
// vertex 1
|
|
3948
|
-
glPositionData[offset++] =
|
|
3949
|
-
glPositionData[offset++] =
|
|
3950
|
-
glPositionData[offset++] =
|
|
3951
|
-
|
|
3952
|
-
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3932
|
+
glPositionData[offset++] = x + cx + sy;
|
|
3933
|
+
glPositionData[offset++] = y + cy - sx;
|
|
3934
|
+
glPositionData[offset++] = uv1X; glPositionData[offset++] = uv0Y;
|
|
3935
|
+
glColorData[offset++] = rgba; glColorData[offset++] = rgbaAdditive;
|
|
3953
3936
|
}
|
|
3954
3937
|
|
|
3955
3938
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3981,9 +3964,9 @@ gl_LINK_STATUS = 35714,
|
|
|
3981
3964
|
|
|
3982
3965
|
// constants for batch rendering
|
|
3983
3966
|
gl_VERTICES_PER_QUAD = 6,
|
|
3984
|
-
gl_INDICIES_PER_VERT =
|
|
3967
|
+
gl_INDICIES_PER_VERT = 6,
|
|
3985
3968
|
gl_MAX_BATCH = 1<<16,
|
|
3986
|
-
gl_VERTEX_BYTE_STRIDE =
|
|
3969
|
+
gl_VERTEX_BYTE_STRIDE = (4 * 2) * 2 + (4) * 2; // vec2 * 2 + (char * 4) * 2
|
|
3987
3970
|
/*
|
|
3988
3971
|
LittleJS - The Tiny JavaScript Game Engine That Can!
|
|
3989
3972
|
MIT License - Copyright 2021 Frank Force
|
|
@@ -4008,7 +3991,7 @@ gl_VERTEX_BYTE_STRIDE = 4 + (4 * 2) * 3 + (4) * 2; // float + vec2 * 3 + (char *
|
|
|
4008
3991
|
const engineName = 'LittleJS';
|
|
4009
3992
|
|
|
4010
3993
|
/** Version of engine */
|
|
4011
|
-
const engineVersion = '1.
|
|
3994
|
+
const engineVersion = '1.2.3';
|
|
4012
3995
|
|
|
4013
3996
|
/** Frames per second to update objects
|
|
4014
3997
|
* @default */
|
|
@@ -4021,7 +4004,7 @@ const timeDelta = 1/frameRate;
|
|
|
4021
4004
|
/** Array containing all engine objects */
|
|
4022
4005
|
let engineObjects = [];
|
|
4023
4006
|
|
|
4024
|
-
/** Array containing only objects that are set to collide with other objects (for optimization) */
|
|
4007
|
+
/** Array containing only objects that are set to collide with other objects this frame (for optimization) */
|
|
4025
4008
|
let engineObjectsCollide = [];
|
|
4026
4009
|
|
|
4027
4010
|
/** Current update frame, used to calculate time */
|
|
@@ -4037,8 +4020,15 @@ let timeReal = 0;
|
|
|
4037
4020
|
let paused = 0;
|
|
4038
4021
|
|
|
4039
4022
|
// Engine internal variables not exposed to documentation
|
|
4040
|
-
let frameTimeLastMS = 0, frameTimeBufferMS = 0,
|
|
4041
|
-
|
|
4023
|
+
let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
|
|
4024
|
+
|
|
4025
|
+
// Engine stat tracking, if showWatermark is true
|
|
4026
|
+
let averageFPS, drawCount;
|
|
4027
|
+
|
|
4028
|
+
// css text used for elements created by engine
|
|
4029
|
+
const styleBody = 'margin:0;overflow:hidden;background:#000' +
|
|
4030
|
+
';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none';
|
|
4031
|
+
const styleCanvas = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
4042
4032
|
|
|
4043
4033
|
///////////////////////////////////////////////////////////////////////////////
|
|
4044
4034
|
|
|
@@ -4056,17 +4046,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4056
4046
|
tileImage.onerror = tileImage.onload = ()=>
|
|
4057
4047
|
{
|
|
4058
4048
|
// save tile image info
|
|
4059
|
-
|
|
4049
|
+
tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
4060
4050
|
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
4061
|
-
shrinkTilesX = tileBleedShrinkFix/tileImageSize.x;
|
|
4062
|
-
shrinkTilesY = tileBleedShrinkFix/tileImageSize.y;
|
|
4063
4051
|
|
|
4064
4052
|
// setup html
|
|
4053
|
+
document.body.style = styleBody;
|
|
4065
4054
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
4066
|
-
document.body.style = 'margin:0;overflow:hidden;background:#000' +
|
|
4067
|
-
(touchMouseEnable?';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none':'');
|
|
4068
|
-
mainCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
4069
4055
|
mainContext = mainCanvas.getContext('2d');
|
|
4056
|
+
mainCanvas.style = styleCanvas;
|
|
4070
4057
|
|
|
4071
4058
|
// init stuff and start engine
|
|
4072
4059
|
debugInit();
|
|
@@ -4074,8 +4061,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4074
4061
|
|
|
4075
4062
|
// create overlay canvas for hud to appear above gl canvas
|
|
4076
4063
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
4077
|
-
overlayCanvas.style = mainCanvas.style.cssText;
|
|
4078
4064
|
overlayContext = overlayCanvas.getContext('2d');
|
|
4065
|
+
overlayCanvas.style = styleCanvas;
|
|
4079
4066
|
|
|
4080
4067
|
gameInit();
|
|
4081
4068
|
touchGamepadCreate();
|
|
@@ -4091,7 +4078,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4091
4078
|
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
4092
4079
|
frameTimeLastMS = frameTimeMS;
|
|
4093
4080
|
if (debug || showWatermark)
|
|
4094
|
-
|
|
4081
|
+
averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
|
|
4095
4082
|
if (debug)
|
|
4096
4083
|
frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
|
|
4097
4084
|
timeReal += frameTimeDeltaMS / 1e3;
|
|
@@ -4178,7 +4165,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4178
4165
|
overlayContext.font = '1em monospace';
|
|
4179
4166
|
overlayContext.fillStyle = '#000';
|
|
4180
4167
|
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
4181
|
-
+ drawCount + ' / ' + engineObjects.length + ' / ' +
|
|
4168
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
4169
|
+
+ ' ' + (glEnable ? 'GL' : '2D') ;
|
|
4182
4170
|
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
4183
4171
|
overlayContext.fillStyle = '#fff';
|
|
4184
4172
|
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
@@ -4195,7 +4183,11 @@ function enginePreRender()
|
|
|
4195
4183
|
{
|
|
4196
4184
|
// save canvas size and clear canvases
|
|
4197
4185
|
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
4198
|
-
|
|
4186
|
+
|
|
4187
|
+
// disable smoothing for pixel art
|
|
4188
|
+
mainContext.imageSmoothingEnabled = !cavasPixelated;
|
|
4189
|
+
|
|
4190
|
+
// setup gl rendering if enabled
|
|
4199
4191
|
glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
|
|
4200
4192
|
}
|
|
4201
4193
|
|
|
@@ -4204,6 +4196,9 @@ function enginePreRender()
|
|
|
4204
4196
|
/** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
|
|
4205
4197
|
function engineObjectsUpdate()
|
|
4206
4198
|
{
|
|
4199
|
+
// get list of solid objects for physics optimzation
|
|
4200
|
+
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
4201
|
+
|
|
4207
4202
|
// recursive object update
|
|
4208
4203
|
const updateObject = (o)=>
|
|
4209
4204
|
{
|
|
@@ -4219,17 +4214,16 @@ function engineObjectsUpdate()
|
|
|
4219
4214
|
|
|
4220
4215
|
// remove destroyed objects
|
|
4221
4216
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
4222
|
-
engineObjectsCollide = engineObjectsCollide.filter(o=>!o.destroyed);
|
|
4223
4217
|
|
|
4224
4218
|
// increment frame and update time
|
|
4225
4219
|
time = ++frame / frameRate;
|
|
4226
4220
|
}
|
|
4227
4221
|
|
|
4228
|
-
/**
|
|
4222
|
+
/** Destroy and remove all objects */
|
|
4229
4223
|
function engineObjectsDestroy()
|
|
4230
4224
|
{
|
|
4231
4225
|
for (const o of engineObjects)
|
|
4232
|
-
o.
|
|
4226
|
+
o.parent || o.destroy();
|
|
4233
4227
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
4234
4228
|
}
|
|
4235
4229
|
|
|
@@ -4240,21 +4234,18 @@ function engineObjectsDestroy()
|
|
|
4240
4234
|
* @param {Array} [objects=engineObjects] - List of objects to check */
|
|
4241
4235
|
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
4242
4236
|
{
|
|
4243
|
-
if (!pos)
|
|
4237
|
+
if (!pos) // all objects
|
|
4244
4238
|
{
|
|
4245
|
-
// all objects
|
|
4246
4239
|
for (const o of objects)
|
|
4247
4240
|
callbackFunction(o);
|
|
4248
4241
|
}
|
|
4249
|
-
else if (size.x != undefined)
|
|
4242
|
+
else if (size.x != undefined) // bounding box test
|
|
4250
4243
|
{
|
|
4251
|
-
// aabb test
|
|
4252
4244
|
for (const o of objects)
|
|
4253
4245
|
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
4254
4246
|
}
|
|
4255
|
-
else
|
|
4247
|
+
else // circle test
|
|
4256
4248
|
{
|
|
4257
|
-
// circle test
|
|
4258
4249
|
const sizeSquared = size*size;
|
|
4259
4250
|
for (const o of objects)
|
|
4260
4251
|
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|