littlejsengine 1.12.4 → 1.12.6

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.
@@ -177,6 +177,8 @@ class UIObject
177
177
  this.lineWidth = uiSystem.defaultLineWidth;
178
178
  /** @property {string} */
179
179
  this.font = uiSystem.defaultFont;
180
+ /** @property {number} - override for text height */
181
+ this.textHeight = undefined;
180
182
  /** @property {boolean} */
181
183
  this.visible = true;
182
184
  /** @property {Array<UIObject>} */
@@ -287,7 +289,8 @@ class UIText extends UIObject
287
289
  }
288
290
  render()
289
291
  {
290
- uiSystem.drawText(this.text, this.pos, this.size, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
292
+ const textSize = vec2(this.size.x, this.textHeight || this.size.y);
293
+ uiSystem.drawText(this.text, this.pos, textSize, this.textColor, this.lineWidth, this.lineColor, this.align, this.font);
291
294
  }
292
295
  }
293
296
 
@@ -350,7 +353,9 @@ class UIButton extends UIObject
350
353
  const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
351
354
  const color = this.mouseIsOver? this.hoverColor : this.color;
352
355
  uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor);
353
- const textSize = vec2(this.size.x, this.size.y*.8);
356
+
357
+ const textScale = .8; // scale text to fit in button
358
+ const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
354
359
  uiSystem.drawText(this.text, this.pos, textSize,
355
360
  this.textColor, 0, undefined, this.align, this.font);
356
361
  }
@@ -447,7 +452,8 @@ class UIScrollbar extends UIObject
447
452
  const barColor = this.mouseIsHeld ? this.color : this.handleColor;
448
453
  uiSystem.drawRect(handlePos, handleSize, barColor, this.lineWidth, this.lineColor);
449
454
 
450
- const textSize = vec2(this.size.x, this.size.y*.8);
455
+ const textScale = .8; // scale text to fit in scrollbar
456
+ const textSize = vec2(this.size.x, this.textHeight || this.size.y*textScale);
451
457
  uiSystem.drawText(this.text, this.pos, textSize,
452
458
  this.textColor, 0, undefined, this.align, this.font);
453
459
  }
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.12.4';
33
+ const engineVersion = '1.12.6';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {number}
@@ -112,7 +112,7 @@ function engineAddPlugin(updateFunction, renderFunction)
112
112
  * @param {Array<string>} [imageSources=[]] - List of images to load
113
113
  * @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
114
114
  * @memberof Engine */
115
- function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
115
+ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
116
116
  {
117
117
  ASSERT(!mainContext, 'engine already initialized');
118
118
  ASSERT(Array.isArray(imageSources), 'pass in images as array');
@@ -269,16 +269,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
269
269
  mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
270
270
  }
271
271
 
272
- function startEngine()
272
+ // wait for gameInit to load
273
+ async function startEngine()
273
274
  {
274
- new Promise((resolve) => resolve(gameInit())).then(engineUpdate);
275
+ await gameInit();
276
+ engineUpdate();
275
277
  }
276
-
277
278
  if (headlessMode)
278
- {
279
- startEngine();
280
- return;
281
- }
279
+ return startEngine();
282
280
 
283
281
  // setup html
284
282
  const styleRoot =
@@ -354,8 +352,9 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
354
352
  }));
355
353
  }
356
354
 
357
- // load all of the images
358
- Promise.all(promises).then(startEngine);
355
+ // wait for all the promises to finish
356
+ await Promise.all(promises);
357
+ return startEngine();
359
358
  }
360
359
 
361
360
  /** Update each engine object, remove destroyed objects, and update time
@@ -47,17 +47,17 @@ class Sound
47
47
  {
48
48
  /** Create a sound object and cache the zzfx samples for later use
49
49
  * @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
50
- * @param {number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
50
+ * @param {number} [range=soundDefaultRange] - World space max range of sound
51
51
  * @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
52
52
  */
53
53
  constructor(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
54
54
  {
55
55
  if (!soundEnable || headlessMode) return;
56
56
 
57
- /** @property {number} - World space max range of sound, will not play if camera is farther away */
57
+ /** @property {number} - World space max range of sound */
58
58
  this.range = range;
59
59
 
60
- /** @property {number} - At what percentage of range should it start tapering off */
60
+ /** @property {number} - At what percentage of range should it start tapering */
61
61
  this.taper = taper;
62
62
 
63
63
  /** @property {number} - How much to randomize frequency each time sound plays */
@@ -173,8 +173,8 @@ class SoundWave extends Sound
173
173
  /** Create a sound object and cache the wave file for later use
174
174
  * @param {string} filename - Filename of audio file to load
175
175
  * @param {number} [randomness] - How much to randomize frequency each time sound plays
176
- * @param {number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
177
- * @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
176
+ * @param {number} [range=soundDefaultRange] - World space max range of sound
177
+ * @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
178
178
  * @param {Function} [onloadCallback] - callback function to call when sound is loaded
179
179
  */
180
180
  constructor(filename, randomness=0, range, taper, onloadCallback)
@@ -182,17 +182,26 @@ class SoundWave extends Sound
182
182
  super(undefined, range, taper);
183
183
  if (!soundEnable || headlessMode) return;
184
184
 
185
+ /** @property {Function} - callback function to call when sound is loaded */
186
+ this.onloadCallback = onloadCallback;
185
187
  this.randomness = randomness;
186
- fetch(filename)
187
- .then(response => response.arrayBuffer())
188
- .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
189
- .then(audioBuffer =>
190
- {
191
- this.sampleChannels = [];
192
- for (let i = audioBuffer.numberOfChannels; i--;)
193
- this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
194
- this.sampleRate = audioBuffer.sampleRate;
195
- }).then(() => onloadCallback && onloadCallback(this));
188
+ this.loadSound(filename);
189
+ }
190
+
191
+ /** Loads a sound from a URL and decodes it into sample data. Must be used with await!
192
+ * @param {string} filename
193
+ * @return {Promise<void>} */
194
+ async loadSound(filename)
195
+ {
196
+ const response = await fetch(filename);
197
+ const arrayBuffer = await response.arrayBuffer();
198
+ const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
199
+ this.sampleChannels = [];
200
+ for (let i = audioBuffer.numberOfChannels; i--;)
201
+ this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
202
+ this.sampleRate = audioBuffer.sampleRate;
203
+ if (this.onloadCallback)
204
+ this.onloadCallback();
196
205
  }
197
206
  }
198
207
 
@@ -36,6 +36,11 @@ const enginePluginFiles =
36
36
  `${PLUGIN_FOLDER}/uiSystem.js`,
37
37
  `${PLUGIN_FOLDER}/box2d.js`,
38
38
  ];
39
+ const engineExtraFiles =
40
+ [
41
+ `${PLUGIN_FOLDER}/box2d.wasm.js`,
42
+ `${PLUGIN_FOLDER}/box2d.wasm.wasm`,
43
+ ];
39
44
  const asciiArt =`
40
45
  ~~~~°°°°ooo°oOo°ooOooOooOo.
41
46
  __________ ________ ____'°oO.
@@ -57,6 +62,11 @@ try
57
62
  // Setup build folder
58
63
  fs.rmSync(BUILD_FOLDER, { recursive: true, force: true });
59
64
  fs.mkdirSync(BUILD_FOLDER);
65
+
66
+ // copy extra files to build folder
67
+ for (const file of engineExtraFiles)
68
+ fs.copyFileSync(file, `${BUILD_FOLDER}/${file.substring(file.lastIndexOf('/')+1)}`);
69
+
60
70
  }
61
71
  catch (e) { handleError(e, 'Failed to create build folder!'); }
62
72
 
@@ -140,9 +150,9 @@ function Build(message, outputFile, files=[], buildSteps=[], isPrimaryBuild)
140
150
  // get file content
141
151
  let fileContent = fs.readFileSync(file) + '\n';
142
152
 
143
- // remove first 'use strict' from each file
153
+ // remove first 'use strict' and surrounding new lines
144
154
  if (isPrimaryBuild)
145
- fileContent = fileContent.replace("'use strict';", '');
155
+ fileContent = fileContent.replace(/'use strict';\s*\n/g, '');
146
156
 
147
157
  // add it to the buffer
148
158
  buffer += fileContent;
@@ -149,6 +149,7 @@ export
149
149
  isIntersecting,
150
150
  wave,
151
151
  formatTime,
152
+ fetchJSON,
152
153
 
153
154
  // Random
154
155
  rand,
@@ -61,21 +61,21 @@ function clearInput() { inputData = [[]]; touchGamepadButtons = []; }
61
61
  * @param {number} button
62
62
  * @return {boolean}
63
63
  * @memberof Input */
64
- const mouseIsDown = keyIsDown;
64
+ function mouseIsDown(button) { return keyIsDown(button); }
65
65
 
66
66
  /** Returns true if mouse button was pressed
67
67
  * @function
68
68
  * @param {number} button
69
69
  * @return {boolean}
70
70
  * @memberof Input */
71
- const mouseWasPressed = keyWasPressed;
71
+ function mouseWasPressed(button) { return keyWasPressed(button); }
72
72
 
73
73
  /** Returns true if mouse button was released
74
74
  * @function
75
75
  * @param {number} button
76
76
  * @return {boolean}
77
77
  * @memberof Input */
78
- const mouseWasReleased = keyWasReleased;
78
+ function mouseWasReleased(button) { return keyWasReleased(button); }
79
79
 
80
80
  /** Mouse pos in world space
81
81
  * @type {Vector2}
@@ -189,6 +189,16 @@ function wave(frequency=1, amplitude=1, t=time)
189
189
  * @memberof Utilities */
190
190
  function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
191
191
 
192
+ /** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await!
193
+ * @param {string} url - URL of JSON file
194
+ * @return {Promise<object>}
195
+ * @memberof Utilities */
196
+ async function fetchJSON(url)
197
+ {
198
+ const response = await fetch(url);
199
+ return response.json();
200
+ }
201
+
192
202
  ///////////////////////////////////////////////////////////////////////////////
193
203
 
194
204
  /** Random global functions
@@ -284,6 +294,12 @@ class RandomGenerator
284
294
  /** Randomly returns either -1 or 1 deterministically
285
295
  * @return {number} */
286
296
  sign() { return this.float() > .5 ? 1 : -1; }
297
+
298
+ /** Returns a seeded random value between the two values passed in with a random sign
299
+ * @param {number} [valueA]
300
+ * @param {number} [valueB]
301
+ * @return {number} */
302
+ floatSign(valueA=1, valueB=0) { return this.float(valueA, valueB) * this.sign(); }
287
303
  }
288
304
 
289
305
  ///////////////////////////////////////////////////////////////////////////////
@@ -210,7 +210,19 @@ function glCreateTexture(image)
210
210
  const texture = glContext.createTexture();
211
211
  glContext.bindTexture(glContext.TEXTURE_2D, texture);
212
212
  if (image && image.width)
213
+ {
213
214
  glSetTextureData(texture, image);
215
+
216
+ const isPowerOfTwo = (value)=> !(value & (value - 1));
217
+ if (!tilesPixelated && isPowerOfTwo(image.width) && isPowerOfTwo(image.height))
218
+ {
219
+ // use mipmap filtering
220
+ glContext.generateMipmap(glContext.TEXTURE_2D);
221
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, glContext.LINEAR_MIPMAP_LINEAR);
222
+ glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, glContext.LINEAR);
223
+ return texture;
224
+ }
225
+ }
214
226
  else
215
227
  {
216
228
  // create a white texture
@@ -218,7 +230,7 @@ function glCreateTexture(image)
218
230
  glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
219
231
  }
220
232
 
221
- // use point filtering for pixelated rendering
233
+ // set texture filtering
222
234
  const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
223
235
  glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
224
236
  glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, filter);