littlejsengine 1.1.17 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/build.bat +1 -0
- package/engine/engine.all.js +520 -522
- package/engine/engine.all.min.js +1 -1
- package/engine/engine.all.release.js +444 -278
- package/engine/engine.js +68 -33
- package/engine/engineAudio.js +21 -7
- package/engine/engineBuild.bat +9 -1
- package/engine/engineBuildSetup.bat +2 -1
- package/engine/engineDebug.js +80 -246
- package/engine/engineDraw.js +106 -21
- package/engine/engineInput.js +89 -52
- package/engine/engineMedals.js +14 -17
- package/engine/engineObject.js +14 -23
- package/engine/engineParticles.js +3 -3
- package/engine/engineRelease.js +3 -1
- package/engine/engineSettings.js +39 -29
- package/engine/engineTileLayer.js +26 -24
- package/engine/engineUtilities.js +14 -12
- package/engine/engineWebGL.js +47 -56
- package/engine/index.d.ts +1612 -0
- package/examples/breakout/game.js +9 -7
- package/examples/breakout/gameObjects.js +1 -1
- package/examples/breakout/index.html +6 -4
- package/examples/platformer/game.js +2 -2
- package/examples/platformer/gameEffects.js +8 -11
- package/examples/platformer/gameLevel.js +14 -7
- package/examples/platformer/gameObjects.js +1 -1
- package/examples/platformer/gamePlayer.js +13 -8
- package/examples/platformer/index.html +9 -7
- package/examples/platformer/tiles.png +0 -0
- package/examples/puzzle/game.js +1 -1
- package/examples/puzzle/index.html +5 -3
- package/examples/stress/index.html +6 -4
- package/game.js +5 -4
- package/index.html +16 -14
- package/package.json +1 -1
package/engine/engine.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 */
|
|
@@ -35,7 +35,7 @@ const timeDelta = 1/frameRate;
|
|
|
35
35
|
/** Array containing all engine objects */
|
|
36
36
|
let engineObjects = [];
|
|
37
37
|
|
|
38
|
-
/** Array containing only objects that are set to collide with other objects (for optimization) */
|
|
38
|
+
/** Array containing only objects that are set to collide with other objects this frame (for optimization) */
|
|
39
39
|
let engineObjectsCollide = [];
|
|
40
40
|
|
|
41
41
|
/** Current update frame, used to calculate time */
|
|
@@ -51,8 +51,15 @@ let timeReal = 0;
|
|
|
51
51
|
let paused = 0;
|
|
52
52
|
|
|
53
53
|
// Engine internal variables not exposed to documentation
|
|
54
|
-
let frameTimeLastMS = 0, frameTimeBufferMS = 0,
|
|
55
|
-
|
|
54
|
+
let frameTimeLastMS = 0, frameTimeBufferMS = 0, tileImageSize, tileImageFixBleed;
|
|
55
|
+
|
|
56
|
+
// Engine stat tracking, if showWatermark is true
|
|
57
|
+
let averageFPS, drawCount;
|
|
58
|
+
|
|
59
|
+
// css text used for elements created by engine
|
|
60
|
+
const styleBody = 'margin:0;overflow:hidden;background:#000' +
|
|
61
|
+
';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none';
|
|
62
|
+
const styleCanvas = 'position:absolute';
|
|
56
63
|
|
|
57
64
|
///////////////////////////////////////////////////////////////////////////////
|
|
58
65
|
|
|
@@ -70,17 +77,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
70
77
|
tileImage.onerror = tileImage.onload = ()=>
|
|
71
78
|
{
|
|
72
79
|
// save tile image info
|
|
73
|
-
|
|
80
|
+
tileImageFixBleed = vec2(tileFixBleedScale).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
|
|
74
81
|
debug && (tileImage.onload=()=>ASSERT(1)); // tile sheet can not reloaded
|
|
75
|
-
shrinkTilesX = tileBleedShrinkFix/tileImageSize.x;
|
|
76
|
-
shrinkTilesY = tileBleedShrinkFix/tileImageSize.y;
|
|
77
82
|
|
|
78
83
|
// setup html
|
|
84
|
+
document.body.style = styleBody;
|
|
79
85
|
document.body.appendChild(mainCanvas = document.createElement('canvas'));
|
|
80
|
-
document.body.style = 'margin:0;overflow:hidden;background:#000' +
|
|
81
|
-
(touchMouseEnable?';touch-action:none;user-select:none;-webkit-user-select:none;-moz-user-select:none':'');
|
|
82
|
-
mainCanvas.style = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)';
|
|
83
86
|
mainContext = mainCanvas.getContext('2d');
|
|
87
|
+
mainCanvas.style = styleCanvas;
|
|
84
88
|
|
|
85
89
|
// init stuff and start engine
|
|
86
90
|
debugInit();
|
|
@@ -88,8 +92,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
88
92
|
|
|
89
93
|
// create overlay canvas for hud to appear above gl canvas
|
|
90
94
|
document.body.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
91
|
-
overlayCanvas.style = mainCanvas.style.cssText;
|
|
92
95
|
overlayContext = overlayCanvas.getContext('2d');
|
|
96
|
+
overlayCanvas.style = styleCanvas;
|
|
93
97
|
|
|
94
98
|
gameInit();
|
|
95
99
|
touchGamepadCreate();
|
|
@@ -105,7 +109,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
105
109
|
let frameTimeDeltaMS = frameTimeMS - frameTimeLastMS;
|
|
106
110
|
frameTimeLastMS = frameTimeMS;
|
|
107
111
|
if (debug || showWatermark)
|
|
108
|
-
|
|
112
|
+
averageFPS = lerp(.05, averageFPS || 0, 1e3/(frameTimeDeltaMS||1));
|
|
109
113
|
if (debug)
|
|
110
114
|
frameTimeDeltaMS *= keyIsDown(107) ? 5 : keyIsDown(109) ? .2 : 1; // +/- to speed/slow time
|
|
111
115
|
timeReal += frameTimeDeltaMS / 1e3;
|
|
@@ -156,13 +160,24 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
156
160
|
|
|
157
161
|
// fit to window by adding space on top or bottom if necessary
|
|
158
162
|
const aspect = innerWidth / innerHeight;
|
|
159
|
-
const fixedAspect =
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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)
|
|
163
173
|
{
|
|
164
|
-
|
|
165
|
-
|
|
174
|
+
mainCanvas.style.width = overlayCanvas.style.width = innerWidth;
|
|
175
|
+
mainCanvas.style.top = overlayCanvas.style.top = (innerHeight - innerWidth/fixedAspect)/2;
|
|
176
|
+
}
|
|
177
|
+
else
|
|
178
|
+
{
|
|
179
|
+
mainCanvas.style.height = overlayCanvas.style.height = innerHeight;
|
|
180
|
+
mainCanvas.style.left = overlayCanvas.style.left = (innerWidth - innerHeight*fixedAspect)/2;
|
|
166
181
|
}
|
|
167
182
|
}
|
|
168
183
|
else
|
|
@@ -170,14 +185,22 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
170
185
|
// clear and set size to same as window
|
|
171
186
|
mainCanvas.width = min(innerWidth, canvasMaxSize.x);
|
|
172
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;
|
|
173
200
|
}
|
|
174
201
|
|
|
175
|
-
// save canvas size and clear overlay canvas
|
|
176
|
-
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
177
|
-
mainContext.imageSmoothingEnabled = !cavasPixelated; // disable smoothing for pixel art
|
|
178
|
-
|
|
179
202
|
// render sort then render while removing destroyed objects
|
|
180
|
-
|
|
203
|
+
enginePreRender();
|
|
181
204
|
gameRender();
|
|
182
205
|
engineObjects.sort((a,b)=> a.renderOrder - b.renderOrder);
|
|
183
206
|
for (const o of engineObjects)
|
|
@@ -196,7 +219,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
196
219
|
overlayContext.font = '1em monospace';
|
|
197
220
|
overlayContext.fillStyle = '#000';
|
|
198
221
|
const text = engineName + ' ' + 'v' + engineVersion + ' / '
|
|
199
|
-
+ drawCount + ' / ' + engineObjects.length + ' / ' +
|
|
222
|
+
+ drawCount + ' / ' + engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
223
|
+
+ ' ' + (glEnable ? 'GL' : '2D') ;
|
|
200
224
|
overlayContext.fillText(text, mainCanvas.width-3, 3);
|
|
201
225
|
overlayContext.fillStyle = '#fff';
|
|
202
226
|
overlayContext.fillText(text, mainCanvas.width-2, 2);
|
|
@@ -208,12 +232,27 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
208
232
|
tileImageSource ? tileImage.src = tileImageSource : tileImage.onload();
|
|
209
233
|
}
|
|
210
234
|
|
|
235
|
+
// called by engine to setup render system
|
|
236
|
+
function enginePreRender()
|
|
237
|
+
{
|
|
238
|
+
// save canvas size and clear canvases
|
|
239
|
+
mainCanvasSize = vec2(overlayCanvas.width = mainCanvas.width, overlayCanvas.height = mainCanvas.height);
|
|
240
|
+
|
|
241
|
+
// disable smoothing for pixel art
|
|
242
|
+
mainContext.imageSmoothingEnabled = !cavasPixelated;
|
|
243
|
+
|
|
244
|
+
// setup gl rendering if enabled
|
|
245
|
+
glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
|
|
246
|
+
}
|
|
211
247
|
|
|
212
248
|
///////////////////////////////////////////////////////////////////////////////
|
|
213
249
|
|
|
214
250
|
/** Calls update on each engine object (recursively if child), removes destroyed objects, and updated time */
|
|
215
251
|
function engineObjectsUpdate()
|
|
216
252
|
{
|
|
253
|
+
// get list of solid objects for physics optimzation
|
|
254
|
+
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
255
|
+
|
|
217
256
|
// recursive object update
|
|
218
257
|
const updateObject = (o)=>
|
|
219
258
|
{
|
|
@@ -229,17 +268,16 @@ function engineObjectsUpdate()
|
|
|
229
268
|
|
|
230
269
|
// remove destroyed objects
|
|
231
270
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
232
|
-
engineObjectsCollide = engineObjectsCollide.filter(o=>!o.destroyed);
|
|
233
271
|
|
|
234
272
|
// increment frame and update time
|
|
235
273
|
time = ++frame / frameRate;
|
|
236
274
|
}
|
|
237
275
|
|
|
238
|
-
/**
|
|
276
|
+
/** Destroy and remove all objects */
|
|
239
277
|
function engineObjectsDestroy()
|
|
240
278
|
{
|
|
241
279
|
for (const o of engineObjects)
|
|
242
|
-
o.
|
|
280
|
+
o.parent || o.destroy();
|
|
243
281
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
244
282
|
}
|
|
245
283
|
|
|
@@ -250,21 +288,18 @@ function engineObjectsDestroy()
|
|
|
250
288
|
* @param {Array} [objects=engineObjects] - List of objects to check */
|
|
251
289
|
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
252
290
|
{
|
|
253
|
-
if (!pos)
|
|
291
|
+
if (!pos) // all objects
|
|
254
292
|
{
|
|
255
|
-
// all objects
|
|
256
293
|
for (const o of objects)
|
|
257
294
|
callbackFunction(o);
|
|
258
295
|
}
|
|
259
|
-
else if (size.x != undefined)
|
|
296
|
+
else if (size.x != undefined) // bounding box test
|
|
260
297
|
{
|
|
261
|
-
// aabb test
|
|
262
298
|
for (const o of objects)
|
|
263
299
|
isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
|
|
264
300
|
}
|
|
265
|
-
else
|
|
301
|
+
else // circle test
|
|
266
302
|
{
|
|
267
|
-
// circle test
|
|
268
303
|
const sizeSquared = size*size;
|
|
269
304
|
for (const o of objects)
|
|
270
305
|
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
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
|
|
@@ -195,7 +195,7 @@ function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
|
195
195
|
|
|
196
196
|
/** Stop all queued speech
|
|
197
197
|
* @memberof Audio */
|
|
198
|
-
const
|
|
198
|
+
const speakStop = ()=> speechSynthesis && speechSynthesis.cancel();
|
|
199
199
|
|
|
200
200
|
/** Get frequency of a note on a musical scale
|
|
201
201
|
* @param {Number} semitoneOffset - How many semitones away from the root note
|
|
@@ -226,6 +226,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
226
226
|
if (!audioContext)
|
|
227
227
|
audioContext = new (window.AudioContext||webkitAudioContext);
|
|
228
228
|
|
|
229
|
+
// fix stalled audio
|
|
230
|
+
audioContext.resume();
|
|
231
|
+
|
|
232
|
+
// prevent sounds from building up if they can't be played
|
|
233
|
+
if (audioContext.state != 'running')
|
|
234
|
+
return;
|
|
235
|
+
|
|
229
236
|
// create buffer and source
|
|
230
237
|
const buffer = audioContext.createBuffer(sampleChannels.length, sampleChannels[0].length, zzfxR),
|
|
231
238
|
source = audioContext.createBufferSource();
|
|
@@ -236,11 +243,18 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=0)
|
|
|
236
243
|
source.playbackRate.value = rate;
|
|
237
244
|
source.loop = loop;
|
|
238
245
|
|
|
239
|
-
// create
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
246
|
+
// create and connect gain node (createGain is more widley spported then GainNode construtor)
|
|
247
|
+
const gainNode = audioContext.createGain();
|
|
248
|
+
gainNode.gain.value = soundVolume*volume;
|
|
249
|
+
gainNode.connect(audioContext.destination);
|
|
250
|
+
|
|
251
|
+
// connect source to gain
|
|
252
|
+
(
|
|
253
|
+
window.StereoPannerNode ? // create pan node if possible
|
|
254
|
+
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)}))
|
|
255
|
+
: source
|
|
256
|
+
)
|
|
257
|
+
.connect(gainNode);
|
|
244
258
|
|
|
245
259
|
// play and return sound
|
|
246
260
|
source.start();
|
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%
|