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/engine/engine.js CHANGED
@@ -22,7 +22,7 @@
22
22
  const engineName = 'LittleJS';
23
23
 
24
24
  /** Version of engine */
25
- const engineVersion = '1.1.17';
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, debugFPS = 0,
55
- shrinkTilesX, shrinkTilesY, drawCount, tileImageSize, tileImageSizeInverse;
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
- tileImageSizeInverse = vec2(1).divide(tileImageSize = vec2(tileImage.width, tileImage.height));
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
- debugFPS = lerp(.05, 1e3/(frameTimeDeltaMS||1), debugFPS);
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 = mainCanvas.width / mainCanvas.height;
160
- mainCanvas.style.width = overlayCanvas.style.width = aspect < fixedAspect ? '100%' : '';
161
- mainCanvas.style.height = overlayCanvas.style.height = aspect < fixedAspect ? '' : '100%';
162
- if (glCanvas)
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
- glCanvas.style.width = mainCanvas.style.width;
165
- glCanvas.style.height = mainCanvas.style.height;
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
- glPreRender(mainCanvas.width, mainCanvas.height, cameraPos.x, cameraPos.y, cameraScale);
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 + ' / ' + debugFPS.toFixed(1);
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
- /** Detroy and remove all objects that are not persistent or descendants of a persistent object */
276
+ /** Destroy and remove all objects */
239
277
  function engineObjectsDestroy()
240
278
  {
241
279
  for (const o of engineObjects)
242
- o.persistent || o.parent || o.destroy();
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);
@@ -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, range);
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 stopSpeech = ()=> speechSynthesis && speechSynthesis.cancel();
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 pan and gain nodes
240
- source
241
- .connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, 1, -1)}))
242
- .connect(new GainNode(audioContext, {'gain':soundVolume*volume}))
243
- .connect(audioContext.destination);
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();
@@ -87,7 +87,15 @@ if %ERRORLEVEL% NEQ 0 (
87
87
  )
88
88
 
89
89
  rem lightly minify with uglify
90
- call uglifyjs -o %OUTPUT_FILENAME% -- %OUTPUT_FILENAME%
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%
@@ -3,4 +3,5 @@ rem Install these tools to build LittleJS
3
3
  call npm install -g google-closure-compiler
4
4
  call npm install -g uglify-js
5
5
  call npm install -g roadroller
6
- call npm install -g ect-bin
6
+ call npm install -g ect-bin
7
+ call npm install -g typescript