littlejsengine 1.1.26 → 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 +3 -1
- package/engine/engine.all.js +248 -324
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.release.js +223 -105
- package/engine/engine.js +34 -11
- package/engine/engineAudio.js +2 -2
- package/engine/engineBuild.bat +9 -1
- package/engine/engineBuildSetup.bat +2 -1
- package/engine/engineDebug.js +26 -220
- package/engine/engineDraw.js +98 -9
- package/engine/engineInput.js +13 -6
- package/engine/engineObject.js +13 -10
- package/engine/engineTileLayer.js +11 -9
- package/engine/engineUtilities.js +12 -12
- package/engine/engineWebGL.js +40 -46
- 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 +3 -3
- package/examples/platformer/game.js +1 -1
- package/examples/platformer/gameEffects.js +1 -1
- package/examples/platformer/gameLevel.js +3 -3
- package/examples/platformer/gameObjects.js +1 -1
- package/examples/platformer/gamePlayer.js +11 -6
- package/examples/platformer/index.html +6 -6
- package/examples/platformer/tiles.png +0 -0
- package/examples/puzzle/game.js +1 -1
- package/examples/puzzle/index.html +2 -2
- package/examples/stress/index.html +3 -3
- package/game.js +0 -3
- package/index.html +13 -13
- package/package.json +1 -1
package/engine/engine.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
const engineName = 'LittleJS';
|
|
23
23
|
|
|
24
24
|
/** Version of engine */
|
|
25
|
-
const engineVersion = '1.
|
|
25
|
+
const engineVersion = '1.2.0';
|
|
26
26
|
|
|
27
27
|
/** Frames per second to update objects
|
|
28
28
|
* @default */
|
|
@@ -59,7 +59,7 @@ let averageFPS, drawCount;
|
|
|
59
59
|
// css text used for elements created by engine
|
|
60
60
|
const styleBody = 'margin:0;overflow:hidden;background:#000' +
|
|
61
61
|
';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none';
|
|
62
|
-
const
|
|
62
|
+
const styleCanvas = 'position:absolute';
|
|
63
63
|
|
|
64
64
|
///////////////////////////////////////////////////////////////////////////////
|
|
65
65
|
|
|
@@ -84,7 +84,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
84
84
|
document.body.style = styleBody;
|
|
85
85
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
86
86
|
mainContext = mainCanvas.getContext('2d');
|
|
87
|
-
mainCanvas.style =
|
|
87
|
+
mainCanvas.style = styleCanvas;
|
|
88
88
|
|
|
89
89
|
// init stuff and start engine
|
|
90
90
|
debugInit();
|
|
@@ -93,7 +93,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
93
93
|
// create overlay canvas for hud to appear above gl canvas
|
|
94
94
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
95
95
|
overlayContext = overlayCanvas.getContext('2d');
|
|
96
|
-
overlayCanvas.style =
|
|
96
|
+
overlayCanvas.style = styleCanvas;
|
|
97
97
|
|
|
98
98
|
gameInit();
|
|
99
99
|
touchGamepadCreate();
|
|
@@ -109,7 +109,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
109
109
|
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
110
110
|
frameTimeLastMS = frameTimeMS;
|
|
111
111
|
if (debug || showWatermark)
|
|
112
|
-
averageFPS = lerp(.05, 1e3/(frameTimeDeltaMS||1)
|
|
112
|
+
averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
|
|
113
113
|
if (debug)
|
|
114
114
|
frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
|
|
115
115
|
timeReal += frameTimeDeltaMS / 1e3;
|
|
@@ -160,13 +160,24 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
160
160
|
|
|
161
161
|
// fit to window by adding space on top or bottom if necessary
|
|
162
162
|
const aspect = innerWidth / innerHeight;
|
|
163
|
-
const fixedAspect =
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
const fixedAspect = canvasFixedSize.x / canvasFixedSize.y;
|
|
164
|
+
|
|
165
|
+
// clear css
|
|
166
|
+
mainCanvas.style.width = overlayCanvas.style.width =
|
|
167
|
+
mainCanvas.style.height = overlayCanvas.style.height =
|
|
168
|
+
mainCanvas.style.top = overlayCanvas.style.top =
|
|
169
|
+
mainCanvas.style.left = overlayCanvas.style.left = '';
|
|
170
|
+
|
|
171
|
+
// letterbox center canvas
|
|
172
|
+
if (aspect < fixedAspect)
|
|
173
|
+
{
|
|
174
|
+
mainCanvas.style.width = overlayCanvas.style.width = innerWidth;
|
|
175
|
+
mainCanvas.style.top = overlayCanvas.style.top = (innerHeight - innerWidth/fixedAspect)/2;
|
|
176
|
+
}
|
|
177
|
+
else
|
|
167
178
|
{
|
|
168
|
-
|
|
169
|
-
|
|
179
|
+
mainCanvas.style.height = overlayCanvas.style.height = innerHeight;
|
|
180
|
+
mainCanvas.style.left = overlayCanvas.style.left = (innerWidth - innerHeight*fixedAspect)/2;
|
|
170
181
|
}
|
|
171
182
|
}
|
|
172
183
|
else
|
|
@@ -174,6 +185,18 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
174
185
|
// clear and set size to same as window
|
|
175
186
|
mainCanvas.width = min(innerWidth, canvasMaxSize.x);
|
|
176
187
|
mainCanvas.height = min(innerHeight, canvasMaxSize.y);
|
|
188
|
+
|
|
189
|
+
// center canvas
|
|
190
|
+
overlayCanvas.style.top = mainCanvas.style.top = (innerHeight - mainCanvas.height)/2;
|
|
191
|
+
overlayCanvas.style.left = mainCanvas.style.left = (innerWidth - mainCanvas.width)/2;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (glEnable)
|
|
195
|
+
{
|
|
196
|
+
glCanvas.style.width = mainCanvas.style.width;
|
|
197
|
+
glCanvas.style.height = mainCanvas.style.height;
|
|
198
|
+
glCanvas.style.top = mainCanvas.style.top;
|
|
199
|
+
glCanvas.style.left = mainCanvas.style.left;
|
|
177
200
|
}
|
|
178
201
|
|
|
179
202
|
// render sort then render while removing destroyed objects
|
package/engine/engineAudio.js
CHANGED
|
@@ -69,7 +69,7 @@ class Sound
|
|
|
69
69
|
return; // out of range
|
|
70
70
|
|
|
71
71
|
// attenuate volume by distance
|
|
72
|
-
volume *= percent(lengthSquared**.5, range*this.taper
|
|
72
|
+
volume *= percent(lengthSquared**.5, range, range*this.taper);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// get pan from screen space coords
|
|
@@ -251,7 +251,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
251
251
|
// connect source to gain
|
|
252
252
|
(
|
|
253
253
|
window.StereoPannerNode ? // create pan node if possible
|
|
254
|
-
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, 1,
|
|
254
|
+
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
|
|
255
255
|
: source
|
|
256
256
|
)
|
|
257
257
|
.connect(gainNode);
|
package/engine/engineBuild.bat
CHANGED
|
@@ -87,7 +87,15 @@ if %ERRORLEVEL% NEQ 0 (
|
|
|
87
87
|
)
|
|
88
88
|
|
|
89
89
|
rem lightly minify with uglify
|
|
90
|
-
call uglifyjs -o
|
|
90
|
+
call uglifyjs -o %OUTPUT_FILENAME% -- %OUTPUT_FILENAME%
|
|
91
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
92
|
+
pause
|
|
93
|
+
exit /b %ERRORLEVEL%
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
rem --- BUILD TYPESCRIPT DEFINITIONS ---
|
|
97
|
+
|
|
98
|
+
call tsc engine.all.js --declaration --allowJs --emitDeclarationOnly --outFile index.d.ts
|
|
91
99
|
if %ERRORLEVEL% NEQ 0 (
|
|
92
100
|
pause
|
|
93
101
|
exit /b %ERRORLEVEL%
|
package/engine/engineDebug.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* LittleJS Debug System
|
|
8
8
|
* <br> - Press ~ to show debug overlay with mouse pick
|
|
9
9
|
* <br> - Number keys toggle debug functions
|
|
10
|
+
* <br> - +/- apply time scale
|
|
10
11
|
* <br> - Debug primitive rendering
|
|
11
12
|
* <br> - Save a 2d canvas as an image
|
|
12
13
|
* @namespace Debug
|
|
@@ -164,22 +165,22 @@ const debugUpdate = ()=>
|
|
|
164
165
|
debugOverlay = !debugOverlay;
|
|
165
166
|
if (debugOverlay)
|
|
166
167
|
{
|
|
168
|
+
if (keyWasPressed(48)) // 0
|
|
169
|
+
showWatermark = !showWatermark;
|
|
167
170
|
if (keyWasPressed(49)) // 1
|
|
168
171
|
debugPhysics = !debugPhysics, debugParticles = 0;
|
|
169
172
|
if (keyWasPressed(50)) // 2
|
|
170
173
|
debugParticles = !debugParticles, debugPhysics = 0;
|
|
171
174
|
if (keyWasPressed(51)) // 3
|
|
175
|
+
debugGamepads = !debugGamepads;
|
|
176
|
+
if (keyWasPressed(52)) // 4
|
|
172
177
|
godMode = !godMode;
|
|
173
178
|
if (keyWasPressed(53)) // 5
|
|
174
179
|
debugTakeScreenshot = 1;
|
|
175
180
|
//if (keyWasPressed(54)) // 6
|
|
176
|
-
//
|
|
177
|
-
if (keyWasPressed(55)) // 7
|
|
178
|
-
debugGamepads = !debugGamepads;
|
|
181
|
+
//if (keyWasPressed(55)) // 7
|
|
179
182
|
//if (keyWasPressed(56)) // 8
|
|
180
183
|
//if (keyWasPressed(57)) // 9
|
|
181
|
-
if (keyWasPressed(48)) // 0
|
|
182
|
-
showWatermark = !showWatermark;
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
|
|
@@ -200,21 +201,20 @@ const debugRender = ()=>
|
|
|
200
201
|
|
|
201
202
|
if (debugGamepads && gamepadsEnable && navigator.getGamepads)
|
|
202
203
|
{
|
|
203
|
-
//
|
|
204
|
+
// gamepad debug display
|
|
204
205
|
const gamepads = navigator.getGamepads();
|
|
205
206
|
for (let i = gamepads.length; i--;)
|
|
206
207
|
{
|
|
207
208
|
const gamepad = gamepads[i];
|
|
208
209
|
if (gamepad)
|
|
209
210
|
{
|
|
210
|
-
// gamepad debug display
|
|
211
211
|
const stickScale = 1;
|
|
212
212
|
const buttonScale = .2;
|
|
213
213
|
const centerPos = cameraPos;
|
|
214
214
|
const sticks = stickData[i];
|
|
215
215
|
for (let j = sticks.length; j--;)
|
|
216
216
|
{
|
|
217
|
-
const drawPos = centerPos.add(vec2(j*stickScale*2,i*stickScale*3));
|
|
217
|
+
const drawPos = centerPos.add(vec2(j*stickScale*2, i*stickScale*3));
|
|
218
218
|
const stickPos = drawPos.add(sticks[j].scale(stickScale));
|
|
219
219
|
debugCircle(drawPos, stickScale, '#fff7',0,1);
|
|
220
220
|
debugLine(drawPos, stickPos, '#f00');
|
|
@@ -251,16 +251,14 @@ const debugRender = ()=>
|
|
|
251
251
|
bestObject = o
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
|
|
255
|
-
size.y = max(size.y, .2);
|
|
256
|
-
|
|
254
|
+
// show object info
|
|
257
255
|
const color = new Color(
|
|
258
256
|
o.collideTiles?1:0,
|
|
259
257
|
o.collideSolidObjects?1:0,
|
|
260
258
|
o.isSolid?1:0,
|
|
261
259
|
o.parent ? .2 : .5);
|
|
262
|
-
|
|
263
|
-
|
|
260
|
+
size.x = max(size.x, .2);
|
|
261
|
+
size.y = max(size.y, .2);
|
|
264
262
|
drawRect(o.pos, size, color);
|
|
265
263
|
drawRect(o.pos, size.scale(.8), o.parent ? new Color(1,1,1,.5) : new Color(0,0,0,.8));
|
|
266
264
|
o.parent && drawLine(o.pos, o.parent.pos, .1, new Color(0,0,1,.5));
|
|
@@ -293,16 +291,15 @@ const debugRender = ()=>
|
|
|
293
291
|
}
|
|
294
292
|
|
|
295
293
|
{
|
|
296
|
-
//
|
|
297
|
-
overlayContext.
|
|
294
|
+
// draw debug primitives
|
|
295
|
+
overlayContext.save();
|
|
296
|
+
overlayContext.lineWidth = 2;
|
|
298
297
|
const pointSize = debugPointSize * cameraScale;
|
|
299
298
|
debugPrimitives.forEach(p=>
|
|
300
299
|
{
|
|
301
300
|
// create canvas transform from world space to screen space
|
|
302
301
|
const pos = worldToScreen(p.pos);
|
|
303
302
|
|
|
304
|
-
overlayContext.save();
|
|
305
|
-
overlayContext.lineWidth = 2;
|
|
306
303
|
overlayContext.translate(pos.x|0, pos.y|0);
|
|
307
304
|
overlayContext.rotate(p.angle);
|
|
308
305
|
overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
|
|
@@ -335,15 +332,17 @@ const debugRender = ()=>
|
|
|
335
332
|
p.fill && overlayContext.fill();
|
|
336
333
|
overlayContext.stroke();
|
|
337
334
|
}
|
|
338
|
-
|
|
339
|
-
overlayContext.restore();
|
|
340
335
|
});
|
|
341
336
|
|
|
342
|
-
|
|
337
|
+
// remove expired pritives
|
|
338
|
+
debugPrimitives = debugPrimitives.filter(r=>r.time.get()<0);
|
|
339
|
+
|
|
340
|
+
overlayContext.restore();
|
|
343
341
|
}
|
|
344
342
|
|
|
345
343
|
{
|
|
346
|
-
|
|
344
|
+
// draw debug overlay
|
|
345
|
+
overlayContext.save();
|
|
347
346
|
overlayContext.fillStyle = '#fff';
|
|
348
347
|
overlayContext.textAlign = 'left';
|
|
349
348
|
overlayContext.textBaseline = 'top';
|
|
@@ -351,6 +350,7 @@ const debugRender = ()=>
|
|
|
351
350
|
overlayContext.shadowColor = '#000';
|
|
352
351
|
overlayContext.shadowBlur = 9;
|
|
353
352
|
|
|
353
|
+
let x = 9, y = -20, h = 30;
|
|
354
354
|
if (debugOverlay)
|
|
355
355
|
{
|
|
356
356
|
overlayContext.fillText(engineName, x, y += h);
|
|
@@ -363,14 +363,12 @@ const debugRender = ()=>
|
|
|
363
363
|
overlayContext.fillText('1: Debug Physics', x, y += h);
|
|
364
364
|
overlayContext.fillStyle = debugParticles ? '#f00' : '#fff';
|
|
365
365
|
overlayContext.fillText('2: Debug Particles', x, y += h);
|
|
366
|
+
overlayContext.fillStyle = debugGamepads ? '#f00' : '#fff';
|
|
367
|
+
overlayContext.fillText('3: Debug Gamepads', x, y += h);
|
|
366
368
|
overlayContext.fillStyle = godMode ? '#f00' : '#fff';
|
|
367
|
-
overlayContext.fillText('
|
|
369
|
+
overlayContext.fillText('4: God Mode', x, y += h);
|
|
368
370
|
overlayContext.fillStyle = '#fff';
|
|
369
371
|
overlayContext.fillText('5: Save Screenshot', x, y += h);
|
|
370
|
-
//overlayContext.fillStyle = debugParticleEditor ? '#f00' : '#fff';
|
|
371
|
-
//overlayContext.fillText('6: Particle Editor', x, y += h);
|
|
372
|
-
overlayContext.fillStyle = debugGamepads ? '#f00' : '#fff';
|
|
373
|
-
overlayContext.fillText('7: Debug Gamepads', x, y += h);
|
|
374
372
|
|
|
375
373
|
let keysPressed = '';
|
|
376
374
|
for(const i in inputData[0])
|
|
@@ -388,7 +386,6 @@ const debugRender = ()=>
|
|
|
388
386
|
buttonsPressed += i + ' ' ;
|
|
389
387
|
}
|
|
390
388
|
buttonsPressed && overlayContext.fillText('Gamepad: ' + buttonsPressed, x, y += h);
|
|
391
|
-
|
|
392
389
|
}
|
|
393
390
|
else
|
|
394
391
|
{
|
|
@@ -398,197 +395,6 @@ const debugRender = ()=>
|
|
|
398
395
|
overlayContext.fillText(debugGamepads ? 'Debug Gamepads' : '', x, y += h);
|
|
399
396
|
}
|
|
400
397
|
|
|
401
|
-
overlayContext.
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
debugPrimitives = debugPrimitives.filter(r=>r.time.get()<0);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
408
|
-
// particle system editor (work in progress)
|
|
409
|
-
let debugParticleEditor = 0, debugParticleSystem, debugParticleSystemDiv, particleSystemCode;
|
|
410
|
-
|
|
411
|
-
const debugToggleParticleEditor = ()=>
|
|
412
|
-
{
|
|
413
|
-
debugParticleEditor = !debugParticleEditor;
|
|
414
|
-
|
|
415
|
-
if (debugParticleEditor)
|
|
416
|
-
{
|
|
417
|
-
if (!debugParticleSystem || debugParticleSystem.destroyed)
|
|
418
|
-
debugParticleSystem = new ParticleEmitter(cameraPos);
|
|
419
|
-
}
|
|
420
|
-
else if (debugParticleSystem && !debugParticleSystem.destroyed)
|
|
421
|
-
debugParticleSystem.destroy();
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const colorToHex = (color)=>
|
|
425
|
-
{
|
|
426
|
-
const componentToHex = (c)=>
|
|
427
|
-
{
|
|
428
|
-
const hex = (c*255|0).toString(16);
|
|
429
|
-
return hex.length == 1 ? '0' + hex : hex;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return '#' + componentToHex(color.r) + componentToHex(color.g) + componentToHex(color.b);
|
|
398
|
+
overlayContext.restore();
|
|
433
399
|
}
|
|
434
|
-
|
|
435
|
-
{
|
|
436
|
-
return new Color(
|
|
437
|
-
parseInt(hex.substr(1,2), 16)/255,
|
|
438
|
-
parseInt(hex.substr(3,2), 16)/255,
|
|
439
|
-
parseInt(hex.substr(5,2), 16)/255)
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (!debugParticleSystemDiv)
|
|
443
|
-
{
|
|
444
|
-
const div = debugParticleSystemDiv = document.createElement('div');
|
|
445
|
-
div.innerHTML = '<big><b>Particle Editor';
|
|
446
|
-
div.style = 'position:absolute;top:10;left:10;color:#fff';
|
|
447
|
-
document.body.appendChild(div);
|
|
448
|
-
|
|
449
|
-
for ( const setting of debugParticleSettings)
|
|
450
|
-
{
|
|
451
|
-
const input = setting[2] = document.createElement('input');
|
|
452
|
-
const name = setting[0];
|
|
453
|
-
const type = setting[1];
|
|
454
|
-
if (type)
|
|
455
|
-
{
|
|
456
|
-
if (type == 'color')
|
|
457
|
-
{
|
|
458
|
-
input.type = type;
|
|
459
|
-
const color = debugParticleSystem[name];
|
|
460
|
-
input.value = colorToHex(color);
|
|
461
|
-
}
|
|
462
|
-
else if (type == 'alpha' && name == 'colorStartAlpha')
|
|
463
|
-
input.value = debugParticleSystem.colorStartA.a;
|
|
464
|
-
else if (type == 'alpha' && name == 'colorEndAlpha')
|
|
465
|
-
input.value = debugParticleSystem.colorEndA.a;
|
|
466
|
-
else if (name == 'tileSizeX')
|
|
467
|
-
input.value = debugParticleSystem.tileSize.x;
|
|
468
|
-
else if (name == 'tileSizeY')
|
|
469
|
-
input.value = debugParticleSystem.tileSize.y;
|
|
470
|
-
}
|
|
471
|
-
else
|
|
472
|
-
input.value = debugParticleSystem[name] || '0';
|
|
473
|
-
|
|
474
|
-
input.oninput = (e)=>
|
|
475
|
-
{
|
|
476
|
-
const inputFloat = parseFloat(input.value) || 0;
|
|
477
|
-
if (type)
|
|
478
|
-
{
|
|
479
|
-
if (type == 'color')
|
|
480
|
-
{
|
|
481
|
-
const color = hexToColor(input.value);
|
|
482
|
-
debugParticleSystem[name].r = color.r;
|
|
483
|
-
debugParticleSystem[name].g = color.g;
|
|
484
|
-
debugParticleSystem[name].b = color.b;
|
|
485
|
-
}
|
|
486
|
-
else if (type == 'alpha' && name == 'colorStartAlpha')
|
|
487
|
-
{
|
|
488
|
-
debugParticleSystem.colorStartA.a = clamp(inputFloat);
|
|
489
|
-
debugParticleSystem.colorStartB.a = clamp(inputFloat);
|
|
490
|
-
}
|
|
491
|
-
else if (type == 'alpha' && name == 'colorEndAlpha')
|
|
492
|
-
{
|
|
493
|
-
debugParticleSystem.colorEndA.a = clamp(inputFloat);
|
|
494
|
-
debugParticleSystem.colorEndB.a = clamp(inputFloat);
|
|
495
|
-
}
|
|
496
|
-
else if (name == 'tileSizeX')
|
|
497
|
-
{
|
|
498
|
-
debugParticleSystem.tileSize = vec2(parseInt(input.value), debugParticleSystem.tileSize.y);
|
|
499
|
-
}
|
|
500
|
-
else if (name == 'tileSizeY')
|
|
501
|
-
{
|
|
502
|
-
debugParticleSystem.tileSize.y = vec2(debugParticleSystem.tileSize.x, parseInt(input.value));
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
else
|
|
506
|
-
debugParticleSystem[name] = inputFloat;
|
|
507
|
-
|
|
508
|
-
updateCode();
|
|
509
|
-
}
|
|
510
|
-
div.appendChild(document.createElement('br'));
|
|
511
|
-
div.appendChild(input);
|
|
512
|
-
div.appendChild(document.createTextNode(' ' + name));
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
div.appendChild(document.createElement('br'));
|
|
516
|
-
div.appendChild(document.createElement('br'));
|
|
517
|
-
div.appendChild(particleSystemCode = document.createElement('input'));
|
|
518
|
-
particleSystemCode.disabled = true;
|
|
519
|
-
div.appendChild(document.createTextNode(' code'));
|
|
520
|
-
|
|
521
|
-
div.appendChild(document.createElement('br'));
|
|
522
|
-
const button = document.createElement('button')
|
|
523
|
-
div.appendChild(button);
|
|
524
|
-
button.innerHTML = 'Copy To Clipboard';
|
|
525
|
-
|
|
526
|
-
button.onclick = (e)=> navigator.clipboard.writeText(particleSystemCode.value);
|
|
527
|
-
|
|
528
|
-
const updateCode = ()=>
|
|
529
|
-
{
|
|
530
|
-
let code = '';
|
|
531
|
-
let count = 0;
|
|
532
|
-
for ( const setting of debugParticleSettings)
|
|
533
|
-
{
|
|
534
|
-
const name = setting[0];
|
|
535
|
-
const type = setting[1];
|
|
536
|
-
let value;
|
|
537
|
-
if (name == 'tileSizeX' || type == 'alpha')
|
|
538
|
-
continue;
|
|
539
|
-
|
|
540
|
-
if (count++)
|
|
541
|
-
code += ', ';
|
|
542
|
-
|
|
543
|
-
if (name == 'tileSizeY')
|
|
544
|
-
{
|
|
545
|
-
value = `vec2(${debugParticleSystem.tileSize.x},${debugParticleSystem.tileSize.y})`;
|
|
546
|
-
}
|
|
547
|
-
else if (type == 'color')
|
|
548
|
-
{
|
|
549
|
-
const c = debugParticleSystem[name];
|
|
550
|
-
value = `new Color(${c.r},${c.g},${c.b},${c.a})`;
|
|
551
|
-
}
|
|
552
|
-
else
|
|
553
|
-
value = debugParticleSystem[name];
|
|
554
|
-
code += value;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
particleSystemCode.value = '...[' + code + ']';
|
|
558
|
-
}
|
|
559
|
-
updateCode();
|
|
560
|
-
}
|
|
561
|
-
debugParticleSystemDiv.style.display = debugParticleEditor ? '' : 'none'
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const debugParticleSettings =
|
|
565
|
-
[
|
|
566
|
-
['emitSize'],
|
|
567
|
-
['emitTime'],
|
|
568
|
-
['emitRate'],
|
|
569
|
-
['emitConeAngle'],
|
|
570
|
-
['tileIndex'],
|
|
571
|
-
['tileSizeX', 'tileSize'],
|
|
572
|
-
['tileSizeY', 'tileSize'],
|
|
573
|
-
['colorStartA', 'color'],
|
|
574
|
-
['colorStartB', 'color'],
|
|
575
|
-
['colorStartAlpha', 'alpha'],
|
|
576
|
-
['colorEndA', 'color'],
|
|
577
|
-
['colorEndB', 'color'],
|
|
578
|
-
['colorEndAlpha', 'alpha'],
|
|
579
|
-
['particleTime'],
|
|
580
|
-
['sizeStart'],
|
|
581
|
-
['sizeEnd'],
|
|
582
|
-
['speed'],
|
|
583
|
-
['angleSpeed'],
|
|
584
|
-
['damping'],
|
|
585
|
-
['angleDamping'],
|
|
586
|
-
['gravityScale'],
|
|
587
|
-
['particleConeAngle'],
|
|
588
|
-
['fadeRate'],
|
|
589
|
-
['randomness'],
|
|
590
|
-
['collideTiles'],
|
|
591
|
-
['additive'],
|
|
592
|
-
['randomColorComponents'],
|
|
593
|
-
['renderOrder'],
|
|
594
|
-
];
|
|
400
|
+
}
|
package/engine/engineDraw.js
CHANGED
|
@@ -203,6 +203,18 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, context = mainCont
|
|
|
203
203
|
context.restore();
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
/** Enable normal or additive blend mode
|
|
207
|
+
* @param {Boolean} [additive=0]
|
|
208
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
209
|
+
* @memberof Draw */
|
|
210
|
+
function setBlendMode(additive, useWebGL=glEnable)
|
|
211
|
+
{
|
|
212
|
+
if (glEnable && useWebGL)
|
|
213
|
+
glSetBlendMode(additive);
|
|
214
|
+
else
|
|
215
|
+
mainContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
216
|
+
}
|
|
217
|
+
|
|
206
218
|
/** Draw text on overlay canvas in world space
|
|
207
219
|
* @param {String} text
|
|
208
220
|
* @param {Vector2} pos
|
|
@@ -228,16 +240,93 @@ function drawText(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new
|
|
|
228
240
|
overlayContext.fillText(text, pos.x, pos.y);
|
|
229
241
|
}
|
|
230
242
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
*
|
|
235
|
-
|
|
243
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Font Image Object - Draw text on a 2D canvas by using characters in an image
|
|
247
|
+
* <br> - 96 characters (from space to tilde) are stored in an image
|
|
248
|
+
* <br> - Uses a default 8x8 font if none is supplied
|
|
249
|
+
* <br> - You can also use fonts from the main tile sheet
|
|
250
|
+
* @example
|
|
251
|
+
* // use built in font
|
|
252
|
+
* const font = new ImageFont;
|
|
253
|
+
*
|
|
254
|
+
* // draw text
|
|
255
|
+
* font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
|
|
256
|
+
*/
|
|
257
|
+
class FontImage
|
|
236
258
|
{
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
259
|
+
/** Create an image font
|
|
260
|
+
* @param {Image} [image] - The image the font is stored in, if undefined the default font is used
|
|
261
|
+
* @param {Vector2} [tileSize=vec2(8)] - The size of the font source tiles
|
|
262
|
+
* @param {Vector2} [paddingSize=vec2(0,1)] - How much extra space to add between characters
|
|
263
|
+
* @param {Number} [startTileIndex=0] - Tile index in image where font starts
|
|
264
|
+
* @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
|
|
265
|
+
*/
|
|
266
|
+
constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), startTileIndex=0, context=overlayContext)
|
|
267
|
+
{
|
|
268
|
+
if (!image)
|
|
269
|
+
{
|
|
270
|
+
// use default font image
|
|
271
|
+
image = new Image();
|
|
272
|
+
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';
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
this.image = image;
|
|
276
|
+
this.tileSize = tileSize;
|
|
277
|
+
this.paddingSize = paddingSize;
|
|
278
|
+
this.startTileIndex = startTileIndex;
|
|
279
|
+
this.context = context;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Draw text in screen space using the image font
|
|
283
|
+
* @param {String} text
|
|
284
|
+
* @param {Vector2} pos
|
|
285
|
+
* @param {Number} [scale=4]
|
|
286
|
+
* @param {Boolean} [center]
|
|
287
|
+
*/
|
|
288
|
+
drawTextScreen(text, pos, scale=4, center)
|
|
289
|
+
{
|
|
290
|
+
const context = this.context;
|
|
291
|
+
context.save();
|
|
292
|
+
context.imageSmoothingEnabled = !cavasPixelated;
|
|
293
|
+
|
|
294
|
+
const size = this.tileSize;
|
|
295
|
+
const drawSize = size.add(this.paddingSize).scale(scale);
|
|
296
|
+
const cols = this.image.width / this.tileSize.x |0;
|
|
297
|
+
text.split('\n').forEach((line, i)=>
|
|
298
|
+
{
|
|
299
|
+
const centerOffset = center ? line.length * size.x * scale / 2 |0 : 0;
|
|
300
|
+
for(let j=line.length; j--;)
|
|
301
|
+
{
|
|
302
|
+
// draw each character
|
|
303
|
+
let charCode = line[j].charCodeAt();
|
|
304
|
+
if (charCode < 32 || charCode > 127)
|
|
305
|
+
charCode = 127; // unknown character
|
|
306
|
+
|
|
307
|
+
// get the character source location and draw it
|
|
308
|
+
const tile = this.startTileIndex + charCode - 32;
|
|
309
|
+
const x = tile % cols;
|
|
310
|
+
const y = tile / cols |0;
|
|
311
|
+
const drawPos = pos.add(vec2(j,i).multiply(drawSize));
|
|
312
|
+
context.drawImage(this.image, x * size.x, y * size.y, size.x, size.y,
|
|
313
|
+
drawPos.x - centerOffset, drawPos.y, size.x * scale, size.y * scale);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
context.restore();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Draw text in world space using the image font
|
|
321
|
+
* @param {String} text
|
|
322
|
+
* @param {Vector2} pos
|
|
323
|
+
* @param {Number} [scale=.25]
|
|
324
|
+
* @param {Boolean} [center]
|
|
325
|
+
*/
|
|
326
|
+
drawText(text, pos, scale=1, center)
|
|
327
|
+
{
|
|
328
|
+
this.drawTextScreen(text, worldToScreen(pos).floor(), scale*cameraScale|0, center);
|
|
329
|
+
}
|
|
241
330
|
}
|
|
242
331
|
|
|
243
332
|
///////////////////////////////////////////////////////////////////////////////
|
package/engine/engineInput.js
CHANGED
|
@@ -159,7 +159,7 @@ const mouseToScreen = (mousePos)=>
|
|
|
159
159
|
|
|
160
160
|
const rect = mainCanvas.getBoundingClientRect();
|
|
161
161
|
return mainCanvasSize.multiply(
|
|
162
|
-
vec2(percent(mousePos.x, rect.
|
|
162
|
+
vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -200,8 +200,8 @@ function gamepadsUpdate()
|
|
|
200
200
|
// read clamp dead zone of analog sticks
|
|
201
201
|
const deadZone = .3, deadZoneMax = .8;
|
|
202
202
|
const applyDeadZone = (v)=>
|
|
203
|
-
v > deadZone ? percent( v,
|
|
204
|
-
v < -deadZone ? -percent(-v,
|
|
203
|
+
v > deadZone ? percent( v, deadZone, deadZoneMax) :
|
|
204
|
+
v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
|
|
205
205
|
|
|
206
206
|
// read analog sticks
|
|
207
207
|
for (let j = 0; j < gamepad.axes.length-1; j+=2)
|
|
@@ -281,7 +281,7 @@ if (isTouchDevice)
|
|
|
281
281
|
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
282
282
|
|
|
283
283
|
// touch input internal variables
|
|
284
|
-
let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2();
|
|
284
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2(), touchGamepadAnalog = 1;
|
|
285
285
|
|
|
286
286
|
// create the touch gamepad, called automatically by the engine
|
|
287
287
|
function touchGamepadCreate()
|
|
@@ -327,7 +327,14 @@ function touchGamepadCreate()
|
|
|
327
327
|
if (touchPos.distance(stickCenter) < touchGamepadSize)
|
|
328
328
|
{
|
|
329
329
|
// virtual analog stick
|
|
330
|
-
|
|
330
|
+
if (touchGamepadAnalog)
|
|
331
|
+
touchGamepadStick = touchPos.subtract(stickCenter).clampLength(touchGamepadSize/2).scale(2/touchGamepadSize);
|
|
332
|
+
else
|
|
333
|
+
{
|
|
334
|
+
// 8 way dpad
|
|
335
|
+
const angle = touchPos.subtract(stickCenter).angle();
|
|
336
|
+
touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
|
|
337
|
+
}
|
|
331
338
|
}
|
|
332
339
|
else if (touchPos.distance(buttonCenter) < touchGamepadSize)
|
|
333
340
|
{
|
|
@@ -351,7 +358,7 @@ function touchGamepadRender()
|
|
|
351
358
|
return;
|
|
352
359
|
|
|
353
360
|
// fade off when not touching or paused
|
|
354
|
-
const alpha = percent(touchGamepadTimer.get(),
|
|
361
|
+
const alpha = percent(touchGamepadTimer.get(), 4, 3);
|
|
355
362
|
if (!alpha || paused)
|
|
356
363
|
return;
|
|
357
364
|
|