littlejsengine 1.9.9 → 1.10.2
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 +6 -0
- package/dist/littlejs.d.ts +107 -14
- package/dist/littlejs.esm.js +250 -102
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +226 -98
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +208 -95
- package/examples/box2d/game.js +29 -10
- package/examples/box2d/gameObjects.js +485 -0
- package/examples/box2d/index.html +7 -5
- package/examples/box2d/scenes.js +28 -247
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +1 -1
- package/examples/breakout/index.html +5 -4
- package/examples/breakoutTutorial/index.html +4 -3
- package/examples/electron/game.js +1 -1
- package/examples/electron/index.html +4 -3
- package/examples/empty/game.js +1 -1
- package/examples/empty/index.html +2 -1
- package/examples/htmlMenu/game.js +67 -0
- package/examples/htmlMenu/index.html +56 -0
- package/examples/htmlMenu/tiles.png +0 -0
- package/examples/index.html +33 -0
- package/examples/js13k/game.js +1 -1
- package/examples/js13k/index.html +17 -14
- package/examples/module/game.js +1 -1
- package/examples/module/index.html +3 -2
- package/examples/particles/index.html +4 -3
- package/examples/platformer/game.js +1 -1
- package/examples/platformer/gameCharacter.js +1 -0
- package/examples/platformer/gameEffects.js +0 -2
- package/examples/platformer/gameLevel.js +2 -2
- package/examples/platformer/gameObjects.js +1 -2
- package/examples/platformer/index.html +10 -9
- package/examples/puzzle/game.js +1 -1
- package/examples/puzzle/index.html +4 -3
- package/examples/starter/game.js +5 -5
- package/examples/starter/index.html +15 -14
- package/examples/stress/index.html +3 -2
- package/examples/typescript/game.js +1 -1
- package/examples/typescript/index.html +3 -2
- package/examples/uiSystem/game.js +134 -0
- package/examples/uiSystem/index.html +25 -0
- package/examples/uiSystem/tiles.png +0 -0
- package/jsconfig.json +11 -0
- package/package.json +1 -1
- package/plugins/box2d.js +116 -42
- package/plugins/postProcess.js +6 -7
- package/plugins/uiSystem.js +243 -0
- package/src/engine.js +48 -20
- package/src/engineAudio.js +22 -25
- package/src/engineDebug.js +18 -3
- package/src/engineDraw.js +101 -27
- package/src/engineExport.js +23 -4
- package/src/engineInput.js +9 -5
- package/src/engineObject.js +15 -12
- package/src/engineSettings.js +1 -1
- package/src/engineUtilities.js +1 -1
- package/src/engineWebGL.js +11 -4
package/src/engine.js
CHANGED
|
@@ -30,7 +30,7 @@ const engineName = 'LittleJS';
|
|
|
30
30
|
* @type {String}
|
|
31
31
|
* @default
|
|
32
32
|
* @memberof Engine */
|
|
33
|
-
const engineVersion = '1.
|
|
33
|
+
const engineVersion = '1.10.2';
|
|
34
34
|
|
|
35
35
|
/** Frames per second to update
|
|
36
36
|
* @type {Number}
|
|
@@ -75,6 +75,12 @@ let timeReal = 0;
|
|
|
75
75
|
* @memberof Engine */
|
|
76
76
|
let paused = false;
|
|
77
77
|
|
|
78
|
+
/** The root element that engine is attached to
|
|
79
|
+
* @type {HTMLElement}
|
|
80
|
+
* @default document.body
|
|
81
|
+
* @memberof Engine */
|
|
82
|
+
let engineRoot;
|
|
83
|
+
|
|
78
84
|
/** Set if game is paused
|
|
79
85
|
* @param {Boolean} isPaused
|
|
80
86
|
* @memberof Engine */
|
|
@@ -108,8 +114,9 @@ function engineAddPlugin(updateFunction, renderFunction)
|
|
|
108
114
|
* @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
|
|
109
115
|
* @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
|
|
110
116
|
* @param {Array} [imageSources=['tiles.png']] - Image to load
|
|
117
|
+
* @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
|
|
111
118
|
* @memberof Engine */
|
|
112
|
-
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[
|
|
119
|
+
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
113
120
|
{
|
|
114
121
|
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
115
122
|
|
|
@@ -151,6 +158,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
151
158
|
for (const o of engineObjects)
|
|
152
159
|
o.parent || o.updateTransforms();
|
|
153
160
|
inputUpdate();
|
|
161
|
+
pluginUpdateList.forEach(f=>f());
|
|
154
162
|
debugUpdate();
|
|
155
163
|
gameUpdatePost();
|
|
156
164
|
inputUpdatePost();
|
|
@@ -266,16 +274,22 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
266
274
|
}
|
|
267
275
|
|
|
268
276
|
// setup html
|
|
269
|
-
const
|
|
277
|
+
const styleRoot =
|
|
270
278
|
'margin:0;overflow:hidden;' + // fill the window
|
|
279
|
+
'width:100vw;height:100vh;' + // fill the window
|
|
280
|
+
'display:flex;' + // use flexbox
|
|
281
|
+
'align-items:center;' + // horizontal center
|
|
282
|
+
(canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
|
|
283
|
+
'justify-content:center;' + // vertical center
|
|
271
284
|
'background:#000;' + // set background color
|
|
272
285
|
'user-select:none;' + // prevent hold to select
|
|
273
286
|
'-webkit-user-select:none;' + // compatibility for ios
|
|
274
287
|
(!touchInputEnable ? '' : // no touch css setttings
|
|
275
288
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
276
289
|
'-webkit-touch-callout:none');// compatibility for ios
|
|
277
|
-
|
|
278
|
-
|
|
290
|
+
engineRoot = rootElement;
|
|
291
|
+
engineRoot.style.cssText = styleRoot;
|
|
292
|
+
engineRoot.appendChild(mainCanvas = document.createElement('canvas'));
|
|
279
293
|
mainContext = mainCanvas.getContext('2d');
|
|
280
294
|
|
|
281
295
|
// init stuff and start engine
|
|
@@ -285,13 +299,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
285
299
|
glInit();
|
|
286
300
|
|
|
287
301
|
// create overlay canvas for hud to appear above gl canvas
|
|
288
|
-
|
|
302
|
+
engineRoot.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
289
303
|
overlayContext = overlayCanvas.getContext('2d');
|
|
290
304
|
|
|
291
305
|
// set canvas style
|
|
292
|
-
const styleCanvas = 'position:absolute;
|
|
293
|
-
|
|
294
|
-
(glCanvas
|
|
306
|
+
const styleCanvas = 'position:absolute'; // allow canvases to overlap
|
|
307
|
+
mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
|
|
308
|
+
if (glCanvas)
|
|
309
|
+
glCanvas.style.cssText = styleCanvas;
|
|
295
310
|
updateCanvas();
|
|
296
311
|
|
|
297
312
|
// create promises for loading images
|
|
@@ -308,19 +323,32 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
308
323
|
})
|
|
309
324
|
);
|
|
310
325
|
|
|
311
|
-
|
|
312
|
-
showSplashScreen && promises.push(new Promise(resolve =>
|
|
326
|
+
if (!imageSources.length)
|
|
313
327
|
{
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
updateSplash();
|
|
317
|
-
function updateSplash()
|
|
328
|
+
// no images to load
|
|
329
|
+
promises.push(new Promise(resolve =>
|
|
318
330
|
{
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
331
|
+
textureInfos[0] = new TextureInfo(new Image);
|
|
332
|
+
resolve();
|
|
333
|
+
}));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (showSplashScreen)
|
|
337
|
+
{
|
|
338
|
+
// draw splash screen
|
|
339
|
+
promises.push(new Promise(resolve =>
|
|
340
|
+
{
|
|
341
|
+
let t = 0;
|
|
342
|
+
console.log(`${engineName} Engine v${engineVersion}`);
|
|
343
|
+
updateSplash();
|
|
344
|
+
function updateSplash()
|
|
345
|
+
{
|
|
346
|
+
clearInput();
|
|
347
|
+
drawEngineSplashScreen(t+=.01);
|
|
348
|
+
t>1 ? resolve() : setTimeout(updateSplash, 16);
|
|
349
|
+
}
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
324
352
|
|
|
325
353
|
// load all of the images
|
|
326
354
|
Promise.all(promises).then(startEngine);
|
package/src/engineAudio.js
CHANGED
|
@@ -73,6 +73,7 @@ class Sound
|
|
|
73
73
|
// generate zzfx sound now for fast playback
|
|
74
74
|
const defaultRandomness = .05;
|
|
75
75
|
this.randomness = zzfxSound[1] || defaultRandomness;
|
|
76
|
+
zzfxSound[1] = 0; // generate without randomness
|
|
76
77
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
77
78
|
this.sampleRate = zzfxR;
|
|
78
79
|
}
|
|
@@ -305,10 +306,6 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
305
306
|
|
|
306
307
|
///////////////////////////////////////////////////////////////////////////////
|
|
307
308
|
|
|
308
|
-
// internal tracking if audio was suspended when last sound was played
|
|
309
|
-
// allows first suspended sound to play when audio is resumed
|
|
310
|
-
let audioSuspended = false;
|
|
311
|
-
|
|
312
309
|
/** Play cached audio samples with given settings
|
|
313
310
|
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
|
|
314
311
|
* @param {Number} [volume] - How much to scale volume by
|
|
@@ -323,21 +320,11 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
323
320
|
{
|
|
324
321
|
if (!soundEnable || headlessMode) return;
|
|
325
322
|
|
|
326
|
-
// prevent sounds from building up if they can't be played
|
|
327
|
-
const audioWasSuspended = audioSuspended;
|
|
328
|
-
if (audioSuspended = audioContext.state != 'running')
|
|
329
|
-
{
|
|
330
|
-
// fix stalled audio
|
|
331
|
-
audioContext.resume();
|
|
332
|
-
|
|
333
|
-
// prevent suspended sounds from building up
|
|
334
|
-
if (audioWasSuspended)
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
323
|
// create buffer and source
|
|
339
|
-
const
|
|
340
|
-
|
|
324
|
+
const channelCount = sampleChannels.length;
|
|
325
|
+
const sampleLength = sampleChannels[0].length;
|
|
326
|
+
const buffer = audioContext.createBuffer(channelCount, sampleLength, sampleRate);
|
|
327
|
+
const source = audioContext.createBufferSource();
|
|
341
328
|
|
|
342
329
|
// copy samples to buffer and setup source
|
|
343
330
|
sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
|
|
@@ -351,10 +338,19 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
351
338
|
gainNode.connect(audioGainNode);
|
|
352
339
|
|
|
353
340
|
// connect source to stereo panner and gain
|
|
354
|
-
|
|
341
|
+
const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
|
|
342
|
+
source.connect(pannerNode).connect(gainNode);
|
|
343
|
+
|
|
344
|
+
// play the sound
|
|
345
|
+
if (audioContext.state != 'running')
|
|
346
|
+
{
|
|
347
|
+
// fix stalled audio and play
|
|
348
|
+
audioContext.resume().then(()=>source.start());
|
|
349
|
+
}
|
|
350
|
+
else
|
|
351
|
+
source.start();
|
|
355
352
|
|
|
356
|
-
//
|
|
357
|
-
source.start();
|
|
353
|
+
// return sound
|
|
358
354
|
return source;
|
|
359
355
|
}
|
|
360
356
|
|
|
@@ -367,7 +363,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
367
363
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
368
364
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
369
365
|
* @memberof Audio */
|
|
370
|
-
function zzfx(...zzfxSound) { return
|
|
366
|
+
function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
|
|
371
367
|
|
|
372
368
|
/** Sample rate used for all ZzFX sounds
|
|
373
369
|
* @default 44100
|
|
@@ -376,7 +372,7 @@ const zzfxR = 44100;
|
|
|
376
372
|
|
|
377
373
|
/** Generate samples for a ZzFX sound
|
|
378
374
|
* @param {Number} [volume] - Volume scale (percent)
|
|
379
|
-
* @param {Number} [randomness] -
|
|
375
|
+
* @param {Number} [randomness] - How much to randomize frequency (percent Hz)
|
|
380
376
|
* @param {Number} [frequency] - Frequency of sound (Hz)
|
|
381
377
|
* @param {Number} [attack] - Attack time, how fast sound starts (seconds)
|
|
382
378
|
* @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
|
|
@@ -402,7 +398,7 @@ const zzfxR = 44100;
|
|
|
402
398
|
function zzfxG
|
|
403
399
|
(
|
|
404
400
|
// parameters
|
|
405
|
-
volume = 1, randomness =
|
|
401
|
+
volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
|
|
406
402
|
release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
|
|
407
403
|
pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
|
|
408
404
|
bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
|
|
@@ -413,7 +409,8 @@ function zzfxG
|
|
|
413
409
|
// init parameters
|
|
414
410
|
let PI2 = PI*2, sampleRate = zzfxR,
|
|
415
411
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
416
|
-
startFrequency = frequency *=
|
|
412
|
+
startFrequency = frequency *=
|
|
413
|
+
rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
|
|
417
414
|
b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
|
|
418
415
|
|
|
419
416
|
// biquad LP/HP filter
|
package/src/engineDebug.js
CHANGED
|
@@ -124,7 +124,6 @@ function debugPoint(pos, color, time, angle)
|
|
|
124
124
|
* @memberof Debug */
|
|
125
125
|
function debugLine(posA, posB, color, thickness=.1, time)
|
|
126
126
|
{
|
|
127
|
-
ASSERT(typeof color == 'string', 'pass in css color strings');
|
|
128
127
|
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
129
128
|
const size = vec2(thickness, halfDelta.length()*2);
|
|
130
129
|
debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
|
|
@@ -190,8 +189,24 @@ function debugSaveDataURL(dataURL, filename)
|
|
|
190
189
|
downloadLink.click();
|
|
191
190
|
}
|
|
192
191
|
|
|
192
|
+
/** Show error as full page of red text
|
|
193
|
+
* @memberof Debug */
|
|
194
|
+
function debugShowErrors()
|
|
195
|
+
{
|
|
196
|
+
onunhandledrejection = (event)=>showError(event.reason);
|
|
197
|
+
onerror = (event, source, lineno, colno)=>
|
|
198
|
+
showError(`${event}\n${source}\nLn ${lineno}, Col ${colno}`);
|
|
199
|
+
|
|
200
|
+
const showError = (message)=>
|
|
201
|
+
{
|
|
202
|
+
// replace entire page with error message
|
|
203
|
+
document.body.style.backgroundColor = '#111';
|
|
204
|
+
document.body.innerHTML = `<pre style=color:#f00;font-size:50px>` + message;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
193
208
|
///////////////////////////////////////////////////////////////////////////////
|
|
194
|
-
// Engine debug
|
|
209
|
+
// Engine debug functions (called automatically)
|
|
195
210
|
|
|
196
211
|
function debugInit()
|
|
197
212
|
{
|
|
@@ -322,7 +337,7 @@ function debugRender()
|
|
|
322
337
|
const pos = worldToScreen(p.pos);
|
|
323
338
|
overlayContext.translate(pos.x|0, pos.y|0);
|
|
324
339
|
overlayContext.rotate(p.angle);
|
|
325
|
-
overlayContext.scale(1, -1);
|
|
340
|
+
overlayContext.scale(1, p.text ? 1 : -1);
|
|
326
341
|
overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
|
|
327
342
|
|
|
328
343
|
if (p.text != undefined)
|
package/src/engineDraw.js
CHANGED
|
@@ -58,12 +58,13 @@ let drawCount;
|
|
|
58
58
|
///////////////////////////////////////////////////////////////////////////////
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* Create a tile info object
|
|
61
|
+
* Create a tile info object using a grid based system
|
|
62
62
|
* - This can take vecs or floats for easier use and conversion
|
|
63
63
|
* - If an index is passed in, the tile size and index will determine the position
|
|
64
|
-
* @param {(Number|Vector2)} [pos=
|
|
64
|
+
* @param {(Number|Vector2)} [pos=0] - Index of tile in sheet
|
|
65
65
|
* @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
|
|
66
66
|
* @param {Number} [textureIndex] - Texture index to use
|
|
67
|
+
* @param {Number} [padding] - How many pixels padding around tiles
|
|
67
68
|
* @return {TileInfo}
|
|
68
69
|
* @example
|
|
69
70
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
@@ -72,7 +73,7 @@ let drawCount;
|
|
|
72
73
|
* tile(vec2(4,8), vec2(30,10)) // a tile at pixel location (4,8) with a size of (30,10)
|
|
73
74
|
* @memberof Draw
|
|
74
75
|
*/
|
|
75
|
-
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
76
|
+
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
|
|
76
77
|
{
|
|
77
78
|
if (headlessMode)
|
|
78
79
|
return new TileInfo;
|
|
@@ -84,17 +85,17 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
|
84
85
|
size = vec2(size);
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
//
|
|
88
|
+
// use pos as a tile index
|
|
89
|
+
const textureInfo = textureInfos[textureIndex];
|
|
90
|
+
ASSERT(textureInfo, 'Texture not loaded');
|
|
91
|
+
const sizePadded = size.add(vec2(padding*2));
|
|
92
|
+
const cols = textureInfo.size.x / sizePadded.x |0;
|
|
88
93
|
if (typeof pos === 'number')
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
ASSERT(textureInfo, 'Texture not loaded');
|
|
92
|
-
const cols = textureInfo.size.x / size.x |0;
|
|
93
|
-
pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
|
|
94
|
-
}
|
|
94
|
+
pos = vec2(pos%cols, pos/cols|0);
|
|
95
|
+
pos = vec2(pos.x*sizePadded.x+padding, pos.y*sizePadded.y+padding);
|
|
95
96
|
|
|
96
97
|
// return a tile info object
|
|
97
|
-
return new TileInfo(pos, size, textureIndex);
|
|
98
|
+
return new TileInfo(pos, size, textureIndex, padding);
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
/**
|
|
@@ -106,8 +107,9 @@ class TileInfo
|
|
|
106
107
|
* @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
|
|
107
108
|
* @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
|
|
108
109
|
* @param {Number} [textureIndex] - Texture index to use
|
|
110
|
+
* @param {Number} [padding] - How many pixels padding around tiles
|
|
109
111
|
*/
|
|
110
|
-
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
112
|
+
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
|
|
111
113
|
{
|
|
112
114
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
113
115
|
this.pos = pos.copy();
|
|
@@ -115,6 +117,8 @@ class TileInfo
|
|
|
115
117
|
this.size = size.copy();
|
|
116
118
|
/** @property {Number} - Texture index to use */
|
|
117
119
|
this.textureIndex = textureIndex;
|
|
120
|
+
/** @property {Number} - How many pixels padding around tiles */
|
|
121
|
+
this.padding = padding;
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
/** Returns a copy of this tile offset by a vector
|
|
@@ -131,7 +135,7 @@ class TileInfo
|
|
|
131
135
|
frame(frame)
|
|
132
136
|
{
|
|
133
137
|
ASSERT(typeof frame == 'number');
|
|
134
|
-
return this.offset(vec2(frame*this.size.x, 0));
|
|
138
|
+
return this.offset(vec2(frame*(this.size.x+this.padding*2), 0));
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
/** Returns the texture info for this tile
|
|
@@ -156,8 +160,6 @@ class TextureInfo
|
|
|
156
160
|
this.size = vec2(image.width, image.height);
|
|
157
161
|
/** @property {WebGLTexture} - webgl texture */
|
|
158
162
|
this.glTexture = glEnable && glCreateTexture(image);
|
|
159
|
-
/** @property {Vector2} - size to adjust tile to fix bleeding */
|
|
160
|
-
this.fixBleedSize = vec2(tileFixBleedScale).divide(this.size);
|
|
161
163
|
}
|
|
162
164
|
}
|
|
163
165
|
|
|
@@ -226,11 +228,12 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
226
228
|
if (textureInfo)
|
|
227
229
|
{
|
|
228
230
|
// calculate uvs and render
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
const
|
|
231
|
+
const sizeInverse = vec2(1).divide(textureInfo.size);
|
|
232
|
+
const x = tileInfo.pos.x * sizeInverse.x;
|
|
233
|
+
const y = tileInfo.pos.y * sizeInverse.y;
|
|
234
|
+
const w = tileInfo.size.x * sizeInverse.x;
|
|
235
|
+
const h = tileInfo.size.y * sizeInverse.y;
|
|
236
|
+
const tileImageFixBleed = sizeInverse.scale(tileFixBleedScale);
|
|
234
237
|
glSetTexture(textureInfo.glTexture);
|
|
235
238
|
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
236
239
|
x + tileImageFixBleed.x, y + tileImageFixBleed.y,
|
|
@@ -247,6 +250,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
247
250
|
{
|
|
248
251
|
// normal canvas 2D rendering method (slower)
|
|
249
252
|
showWatermark && ++drawCount;
|
|
253
|
+
size = vec2(size.x, -size.y); // fix upside down sprites
|
|
250
254
|
drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
251
255
|
{
|
|
252
256
|
if (textureInfo)
|
|
@@ -284,6 +288,74 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
284
288
|
drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
|
|
285
289
|
}
|
|
286
290
|
|
|
291
|
+
/** Draw colored polygon using passed in points
|
|
292
|
+
* @param {Array} points - Array of Vector2 points
|
|
293
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
294
|
+
* @param {Number} [lineWidth=0]
|
|
295
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
296
|
+
* @param {Boolean} [screenSpace=false]
|
|
297
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
298
|
+
* @memberof Draw */
|
|
299
|
+
function drawPoly(points, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
300
|
+
{
|
|
301
|
+
context.fillStyle = color.toString();
|
|
302
|
+
context.beginPath();
|
|
303
|
+
for (const point of screenSpace ? points : points.map(worldToScreen))
|
|
304
|
+
context.lineTo(point.x, point.y);
|
|
305
|
+
context.closePath();
|
|
306
|
+
context.fill();
|
|
307
|
+
if (lineWidth)
|
|
308
|
+
{
|
|
309
|
+
context.strokeStyle = lineColor.toString();
|
|
310
|
+
context.lineWidth = screenSpace ? lineWidth : lineWidth*cameraScale;
|
|
311
|
+
context.stroke();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Draw colored ellipse using passed in point
|
|
316
|
+
* @param {Vector2} pos
|
|
317
|
+
* @param {Number} [width=1]
|
|
318
|
+
* @param {Number} [height=1]
|
|
319
|
+
* @param {Number} [angle=0]
|
|
320
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
321
|
+
* @param {Number} [lineWidth=0]
|
|
322
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
323
|
+
* @param {Boolean} [screenSpace=false]
|
|
324
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
325
|
+
* @memberof Draw */
|
|
326
|
+
function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
327
|
+
{
|
|
328
|
+
if (!screenSpace)
|
|
329
|
+
{
|
|
330
|
+
pos = worldToScreen(pos);
|
|
331
|
+
width *= cameraScale;
|
|
332
|
+
height *= cameraScale;
|
|
333
|
+
lineWidth *= cameraScale;
|
|
334
|
+
}
|
|
335
|
+
context.fillStyle = color.toString();
|
|
336
|
+
context.beginPath();
|
|
337
|
+
context.ellipse(pos.x, pos.y, width, height, angle, 0, 9);
|
|
338
|
+
context.fill();
|
|
339
|
+
if (lineWidth)
|
|
340
|
+
{
|
|
341
|
+
context.strokeStyle = lineColor.toString();
|
|
342
|
+
context.lineWidth = lineWidth;
|
|
343
|
+
context.stroke();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Draw colored circle using passed in point
|
|
348
|
+
* @param {Vector2} pos
|
|
349
|
+
* @param {Number} [radius=1]
|
|
350
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
351
|
+
* @param {Number} [lineWidth=0]
|
|
352
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
353
|
+
* @param {Boolean} [screenSpace=false]
|
|
354
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
355
|
+
* @memberof Draw */
|
|
356
|
+
function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
357
|
+
{ drawEllipse(pos, radius, radius, 0, color, lineWidth, lineColor, screenSpace, context); }
|
|
358
|
+
|
|
287
359
|
/** Draw colored line between two points
|
|
288
360
|
* @param {Vector2} posA
|
|
289
361
|
* @param {Vector2} posB
|
|
@@ -354,10 +426,11 @@ function setBlendMode(additive, useWebGL=glEnable, context)
|
|
|
354
426
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
355
427
|
* @param {String} [font=fontDefault]
|
|
356
428
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
429
|
+
* @param {Number} [maxWidth]
|
|
357
430
|
* @memberof Draw */
|
|
358
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
|
|
431
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context, maxWidth)
|
|
359
432
|
{
|
|
360
|
-
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context);
|
|
433
|
+
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context, maxWidth);
|
|
361
434
|
}
|
|
362
435
|
|
|
363
436
|
/** Draw text on overlay canvas in screen space
|
|
@@ -371,8 +444,9 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
371
444
|
* @param {CanvasTextAlign} [textAlign]
|
|
372
445
|
* @param {String} [font=fontDefault]
|
|
373
446
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
447
|
+
* @param {Number} [maxWidth]
|
|
374
448
|
* @memberof Draw */
|
|
375
|
-
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
|
|
449
|
+
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext, maxWidth=undefined)
|
|
376
450
|
{
|
|
377
451
|
context.fillStyle = color.toString();
|
|
378
452
|
context.lineWidth = lineWidth;
|
|
@@ -385,8 +459,8 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
|
|
|
385
459
|
pos = pos.copy();
|
|
386
460
|
(text+'').split('\n').forEach(line=>
|
|
387
461
|
{
|
|
388
|
-
lineWidth && context.strokeText(line, pos.x, pos.y);
|
|
389
|
-
context.fillText(line, pos.x, pos.y);
|
|
462
|
+
lineWidth && context.strokeText(line, pos.x, pos.y, maxWidth);
|
|
463
|
+
context.fillText(line, pos.x, pos.y, maxWidth);
|
|
390
464
|
pos.y += size;
|
|
391
465
|
});
|
|
392
466
|
}
|
|
@@ -494,6 +568,6 @@ function toggleFullscreen()
|
|
|
494
568
|
if (document.exitFullscreen)
|
|
495
569
|
document.exitFullscreen();
|
|
496
570
|
}
|
|
497
|
-
else if (
|
|
498
|
-
|
|
571
|
+
else if (engineRoot.requestFullscreen)
|
|
572
|
+
engineRoot.requestFullscreen();
|
|
499
573
|
}
|
package/src/engineExport.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
2
|
* LittleJS Module Export
|
|
3
3
|
* - Export engine as a module
|
|
4
4
|
*/
|
|
@@ -20,8 +20,9 @@ export {
|
|
|
20
20
|
engineObjectsUpdate,
|
|
21
21
|
engineObjectsDestroy,
|
|
22
22
|
engineObjectsCallback,
|
|
23
|
+
engineObjectsRaycast,
|
|
23
24
|
engineAddPlugin,
|
|
24
|
-
|
|
25
|
+
|
|
25
26
|
// Globals
|
|
26
27
|
debug,
|
|
27
28
|
debugOverlay,
|
|
@@ -140,6 +141,7 @@ export {
|
|
|
140
141
|
smoothStep,
|
|
141
142
|
nearestPowerOfTwo,
|
|
142
143
|
isOverlapping,
|
|
144
|
+
isIntersecting,
|
|
143
145
|
wave,
|
|
144
146
|
formatTime,
|
|
145
147
|
|
|
@@ -159,6 +161,20 @@ export {
|
|
|
159
161
|
vec2,
|
|
160
162
|
rgb,
|
|
161
163
|
hsl,
|
|
164
|
+
isColor,
|
|
165
|
+
|
|
166
|
+
// Default Colors
|
|
167
|
+
WHITE,
|
|
168
|
+
BLACK,
|
|
169
|
+
GRAY,
|
|
170
|
+
RED,
|
|
171
|
+
ORANGE,
|
|
172
|
+
YELLOW,
|
|
173
|
+
GREEN,
|
|
174
|
+
CYAN,
|
|
175
|
+
BLUE,
|
|
176
|
+
PURPLE,
|
|
177
|
+
MAGENTA,
|
|
162
178
|
|
|
163
179
|
// Draw
|
|
164
180
|
textureInfos,
|
|
@@ -188,10 +204,13 @@ export {
|
|
|
188
204
|
// WebGL
|
|
189
205
|
glCanvas,
|
|
190
206
|
glContext,
|
|
191
|
-
glSetTexture,
|
|
192
207
|
glCompileShader,
|
|
208
|
+
glCopyToContext,
|
|
193
209
|
glCreateProgram,
|
|
194
210
|
glCreateTexture,
|
|
211
|
+
glDraw,
|
|
212
|
+
glFlush,
|
|
213
|
+
glSetTexture,
|
|
195
214
|
|
|
196
215
|
// Input
|
|
197
216
|
keyIsDown,
|
|
@@ -251,4 +270,4 @@ export {
|
|
|
251
270
|
medalsPreventUnlock,
|
|
252
271
|
medalsInit,
|
|
253
272
|
Medal,
|
|
254
|
-
};
|
|
273
|
+
};
|
package/src/engineInput.js
CHANGED
|
@@ -167,7 +167,7 @@ function inputInit()
|
|
|
167
167
|
|
|
168
168
|
onkeydown = (e)=>
|
|
169
169
|
{
|
|
170
|
-
if (debug && e.target !=
|
|
170
|
+
if (debug && e.target != engineRoot) return;
|
|
171
171
|
if (!e.repeat)
|
|
172
172
|
{
|
|
173
173
|
isUsingGamepad = false;
|
|
@@ -180,7 +180,7 @@ function inputInit()
|
|
|
180
180
|
|
|
181
181
|
onkeyup = (e)=>
|
|
182
182
|
{
|
|
183
|
-
if (debug && e.target !=
|
|
183
|
+
if (debug && e.target != engineRoot) return;
|
|
184
184
|
inputData[0][e.code] = 4;
|
|
185
185
|
if (inputWASDEmulateDirection)
|
|
186
186
|
inputData[0][remapKey(e.code)] = 4;
|
|
@@ -199,6 +199,10 @@ function inputInit()
|
|
|
199
199
|
// mouse event handlers
|
|
200
200
|
onmousedown = (e)=>
|
|
201
201
|
{
|
|
202
|
+
// fix stalled audio requiring user interaction
|
|
203
|
+
if (soundEnable && !headlessMode && audioContext && audioContext.state != 'running')
|
|
204
|
+
audioContext.resume();
|
|
205
|
+
|
|
202
206
|
isUsingGamepad = false;
|
|
203
207
|
inputData[0][e.button] = 3;
|
|
204
208
|
mousePosScreen = mouseToScreen(e);
|
|
@@ -210,7 +214,7 @@ function inputInit()
|
|
|
210
214
|
oncontextmenu = (e)=> false; // prevent right click menu
|
|
211
215
|
|
|
212
216
|
// init touch input
|
|
213
|
-
if (isTouchDevice && touchInputEnable
|
|
217
|
+
if (isTouchDevice && touchInputEnable)
|
|
214
218
|
touchInputInit();
|
|
215
219
|
}
|
|
216
220
|
|
|
@@ -367,8 +371,8 @@ function touchInputInit()
|
|
|
367
371
|
function handleTouchDefault(e)
|
|
368
372
|
{
|
|
369
373
|
// fix stalled audio requiring user interaction
|
|
370
|
-
if (soundEnable && audioContext && audioContext.state != 'running')
|
|
371
|
-
|
|
374
|
+
if (soundEnable && !headlessMode && audioContext && audioContext.state != 'running')
|
|
375
|
+
audioContext.resume();
|
|
372
376
|
|
|
373
377
|
// check if touching and pass to mouse events
|
|
374
378
|
const touching = e.touches.length;
|
package/src/engineObject.js
CHANGED
|
@@ -153,9 +153,9 @@ class EngineObject
|
|
|
153
153
|
|
|
154
154
|
// apply physics
|
|
155
155
|
const oldPos = this.pos.copy();
|
|
156
|
-
this.velocity.y += gravity * this.gravityScale;
|
|
157
156
|
this.pos.x += this.velocity.x *= this.damping;
|
|
158
|
-
this.pos.y += this.velocity.y
|
|
157
|
+
this.pos.y += this.velocity.y = this.damping * this.velocity.y
|
|
158
|
+
+ gravity * this.gravityScale;
|
|
159
159
|
this.angle += this.angleVelocity *= this.angleDamping;
|
|
160
160
|
|
|
161
161
|
// physics sanity checks
|
|
@@ -284,19 +284,22 @@ class EngineObject
|
|
|
284
284
|
const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
285
285
|
if (isBlockedY || !isBlockedX)
|
|
286
286
|
{
|
|
287
|
-
// set if landed on ground
|
|
288
|
-
this.groundObject = wasMovingDown;
|
|
289
|
-
|
|
290
287
|
// bounce velocity
|
|
291
288
|
this.velocity.y *= -this.elasticity;
|
|
292
289
|
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
290
|
+
// set if landed on ground
|
|
291
|
+
if (this.groundObject = wasMovingDown)
|
|
292
|
+
{
|
|
293
|
+
// adjust position to slightly above nearest tile boundary
|
|
294
|
+
// this prevents gap between object and ground
|
|
295
|
+
const epsilon = .0001;
|
|
296
|
+
this.pos.y = (oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;
|
|
297
|
+
}
|
|
298
|
+
else
|
|
299
|
+
{
|
|
300
|
+
// move to previous position
|
|
301
|
+
this.pos.y = oldPos.y;
|
|
302
|
+
}
|
|
300
303
|
}
|
|
301
304
|
if (isBlockedX)
|
|
302
305
|
{
|
package/src/engineSettings.js
CHANGED
|
@@ -89,7 +89,7 @@ let tileSizeDefault = vec2(16);
|
|
|
89
89
|
* @type {Number}
|
|
90
90
|
* @default
|
|
91
91
|
* @memberof Settings */
|
|
92
|
-
let tileFixBleedScale =
|
|
92
|
+
let tileFixBleedScale = 0;
|
|
93
93
|
|
|
94
94
|
///////////////////////////////////////////////////////////////////////////////
|
|
95
95
|
// Object settings
|