littlejsengine 1.9.5 → 1.9.7

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/src/engineDraw.js CHANGED
@@ -74,6 +74,9 @@ let drawCount;
74
74
  */
75
75
  function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
76
76
  {
77
+ if (headlessMode)
78
+ return new TileInfo;
79
+
77
80
  // if size is a number, make it a vector
78
81
  if (typeof size === 'number')
79
82
  {
@@ -107,9 +110,9 @@ class TileInfo
107
110
  constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
108
111
  {
109
112
  /** @property {Vector2} - Top left corner of tile in pixels */
110
- this.pos = pos;
113
+ this.pos = pos.copy();
111
114
  /** @property {Vector2} - Size of tile in pixels */
112
- this.size = size;
115
+ this.size = size.copy();
113
116
  /** @property {Number} - Texture index to use */
114
117
  this.textureIndex = textureIndex;
115
118
  }
@@ -201,7 +204,7 @@ function getCameraSize() { return mainCanvasSize.scale(1/cameraScale); }
201
204
  * @param {Color} [additiveColor=(0,0,0,0)] - Additive color to be applied
202
205
  * @param {Boolean} [useWebGL=glEnable] - Use accelerated WebGL rendering
203
206
  * @param {Boolean} [screenSpace=false] - If true the pos and size are in screen space
204
- * @param {CanvasRenderingContext2D} [context] - Canvas 2D context to draw to
207
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context] - Canvas 2D context to draw to
205
208
  * @memberof Draw */
206
209
  function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
207
210
  angle=0, mirror, additiveColor=new Color(0,0,0,0), useWebGL=glEnable, screenSpace, context)
@@ -274,7 +277,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
274
277
  * @param {Number} [angle]
275
278
  * @param {Boolean} [useWebGL=glEnable]
276
279
  * @param {Boolean} [screenSpace=false]
277
- * @param {CanvasRenderingContext2D} [context]
280
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
278
281
  * @memberof Draw */
279
282
  function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
280
283
  {
@@ -288,7 +291,7 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
288
291
  * @param {Color} [color=(1,1,1,1)]
289
292
  * @param {Boolean} [useWebGL=glEnable]
290
293
  * @param {Boolean} [screenSpace=false]
291
- * @param {CanvasRenderingContext2D} [context]
294
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
292
295
  * @memberof Draw */
293
296
  function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
294
297
  {
@@ -304,7 +307,7 @@ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, contex
304
307
  * @param {Boolean} mirror
305
308
  * @param {Function} drawFunction
306
309
  * @param {Boolean} [screenSpace=false]
307
- * @param {CanvasRenderingContext2D} [context=mainContext]
310
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
308
311
  * @memberof Draw */
309
312
  function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, context=mainContext)
310
313
  {
@@ -325,7 +328,7 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
325
328
  /** Enable normal or additive blend mode
326
329
  * @param {Boolean} [additive]
327
330
  * @param {Boolean} [useWebGL=glEnable]
328
- * @param {CanvasRenderingContext2D} [context=mainContext]
331
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
329
332
  * @memberof Draw */
330
333
  function setBlendMode(additive, useWebGL=glEnable, context)
331
334
  {
@@ -350,7 +353,7 @@ function setBlendMode(additive, useWebGL=glEnable, context)
350
353
  * @param {Color} [lineColor=(0,0,0,1)]
351
354
  * @param {CanvasTextAlign} [textAlign='center']
352
355
  * @param {String} [font=fontDefault]
353
- * @param {CanvasRenderingContext2D} [context=overlayContext]
356
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
354
357
  * @memberof Draw */
355
358
  function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
356
359
  {
@@ -367,7 +370,7 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
367
370
  * @param {Color} [lineColor=(0,0,0,1)]
368
371
  * @param {CanvasTextAlign} [textAlign]
369
372
  * @param {String} [font=fontDefault]
370
- * @param {CanvasRenderingContext2D} [context=overlayContext]
373
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
371
374
  * @memberof Draw */
372
375
  function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
373
376
  {
@@ -410,7 +413,7 @@ class FontImage
410
413
  * @param {HTMLImageElement} [image] - Image for the font, if undefined default font is used
411
414
  * @param {Vector2} [tileSize=(8,8)] - Size of the font source tiles
412
415
  * @param {Vector2} [paddingSize=(0,1)] - How much extra space to add between characters
413
- * @param {CanvasRenderingContext2D} [context=overlayContext] - context to draw to
416
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext] - context to draw to
414
417
  */
415
418
  constructor(image, tileSize=vec2(8), paddingSize=vec2(0,1), context=overlayContext)
416
419
  {
@@ -45,6 +45,7 @@ export {
45
45
  canvasPixelated,
46
46
  fontDefault,
47
47
  showSplashScreen,
48
+ headlessMode,
48
49
  tileSizeDefault,
49
50
  tileFixBleedScale,
50
51
  enablePhysicsSolver,
@@ -83,6 +84,7 @@ export {
83
84
  setCanvasPixelated,
84
85
  setFontDefault,
85
86
  setShowSplashScreen,
87
+ setHeadlessMode,
86
88
  setGlEnable,
87
89
  setGlOverlay,
88
90
  setTileSizeDefault,
@@ -176,6 +178,7 @@ export {
176
178
  FontImage,
177
179
  isFullscreen,
178
180
  toggleFullscreen,
181
+ getCameraSize,
179
182
 
180
183
  // WebGL
181
184
  glCanvas,
@@ -122,7 +122,7 @@ function gamepadWasReleased(button, gamepad=0)
122
122
  * @return {Vector2}
123
123
  * @memberof Input */
124
124
  function gamepadStick(stick, gamepad=0)
125
- { return stickData[gamepad] ? stickData[gamepad][stick] || vec2() : vec2(); }
125
+ { return gamepadStickData[gamepad] ? gamepadStickData[gamepad][stick] || vec2() : vec2(); }
126
126
 
127
127
  ///////////////////////////////////////////////////////////////////////////////
128
128
  // Input update called by engine
@@ -133,6 +133,8 @@ let inputData = [[]];
133
133
 
134
134
  function inputUpdate()
135
135
  {
136
+ if (headlessMode) return;
137
+
136
138
  // clear input when lost focus (prevent stuck keys)
137
139
  isTouchDevice || document.hasFocus() || clearInput();
138
140
 
@@ -145,6 +147,8 @@ function inputUpdate()
145
147
 
146
148
  function inputUpdatePost()
147
149
  {
150
+ if (headlessMode) return;
151
+
148
152
  // clear input to prepare for next frame
149
153
  for (const deviceInputData of inputData)
150
154
  for (const i in deviceInputData)
@@ -153,9 +157,12 @@ function inputUpdatePost()
153
157
  }
154
158
 
155
159
  ///////////////////////////////////////////////////////////////////////////////
156
- // Keyboard event handlers
160
+ // Input event handlers
157
161
 
162
+ function inputInit()
158
163
  {
164
+ if (headlessMode) return;
165
+
159
166
  onkeydown = (e)=>
160
167
  {
161
168
  if (debug && e.target != document.body) return;
@@ -186,21 +193,29 @@ function inputUpdatePost()
186
193
  c == 'KeyA' ? 'ArrowLeft' :
187
194
  c == 'KeyD' ? 'ArrowRight' : c : c;
188
195
  }
196
+
197
+ // mouse event handlers
198
+ onmousedown = (e)=>
199
+ {
200
+ isUsingGamepad = false;
201
+ inputData[0][e.button] = 3;
202
+ mousePosScreen = mouseToScreen(e);
203
+ e.button && e.preventDefault();
204
+ }
205
+ onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
206
+ onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
207
+ onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
208
+ oncontextmenu = (e)=> false; // prevent right click menu
209
+
210
+ // init touch input
211
+ if (isTouchDevice)
212
+ touchInputInit();
189
213
  }
190
214
 
191
- ///////////////////////////////////////////////////////////////////////////////
192
- // Mouse event handlers
193
-
194
- onmousedown = (e)=> {isUsingGamepad = false; inputData[0][e.button] = 3; mousePosScreen = mouseToScreen(e); e.button && e.preventDefault();}
195
- onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
196
- onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
197
- onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
198
- oncontextmenu = (e)=> false; // prevent right click menu
199
-
200
215
  // convert a mouse or touch event position to screen space
201
216
  function mouseToScreen(mousePos)
202
217
  {
203
- if (!mainCanvas)
218
+ if (!mainCanvas || headlessMode)
204
219
  return vec2(); // fix bug that can occur if user clicks before page loads
205
220
 
206
221
  const rect = mainCanvas.getBoundingClientRect();
@@ -212,7 +227,7 @@ function mouseToScreen(mousePos)
212
227
  // Gamepad input
213
228
 
214
229
  // gamepad internal variables
215
- const stickData = [];
230
+ const gamepadStickData = [];
216
231
 
217
232
  // gamepads are updated by engine every frame automatically
218
233
  function gamepadsUpdate()
@@ -229,14 +244,11 @@ function gamepadsUpdate()
229
244
  // update touch gamepad if enabled
230
245
  if (touchGamepadEnable && isTouchDevice)
231
246
  {
232
- // create the touch gamepad if it doesn't exist
233
- if (!touchGamepadButtons)
234
- createTouchGamepad();
235
-
247
+ ASSERT(touchGamepadButtons, 'set touchGamepadEnable before calling init!');
236
248
  if (touchGamepadTimer.isSet())
237
249
  {
238
250
  // read virtual analog stick
239
- const sticks = stickData[0] || (stickData[0] = []);
251
+ const sticks = gamepadStickData[0] || (gamepadStickData[0] = []);
240
252
  sticks[0] = vec2();
241
253
  if (touchGamepadAnalog)
242
254
  sticks[0] = applyDeadZones(touchGamepadStick);
@@ -253,7 +265,8 @@ function gamepadsUpdate()
253
265
  for (let i=10; i--;)
254
266
  {
255
267
  const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
256
- data[j] = touchGamepadButtons[i] ? gamepadIsDown(j,0) ? 1 : 3 : gamepadIsDown(j,0) ? 4 : 0;
268
+ const wasDown = gamepadIsDown(j,0);
269
+ data[j] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
257
270
  }
258
271
  }
259
272
  }
@@ -273,7 +286,7 @@ function gamepadsUpdate()
273
286
  // get or create gamepad data
274
287
  const gamepad = gamepads[i];
275
288
  const data = inputData[i+1] || (inputData[i+1] = []);
276
- const sticks = stickData[i] || (stickData[i] = []);
289
+ const sticks = gamepadStickData[i] || (gamepadStickData[i] = []);
277
290
 
278
291
  if (gamepad)
279
292
  {
@@ -312,28 +325,44 @@ function gamepadsUpdate()
312
325
  * @param {Number|Array} [pattern] - single value in ms or vibration interval array
313
326
  * @memberof Input */
314
327
  function vibrate(pattern=100)
315
- { vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
328
+ { vibrateEnable && !headlessMode && navigator && navigator.vibrate && navigator.vibrate(pattern); }
316
329
 
317
330
  /** Cancel any ongoing vibration
318
331
  * @memberof Input */
319
332
  function vibrateStop() { vibrate(0); }
320
333
 
321
334
  ///////////////////////////////////////////////////////////////////////////////
322
- // Touch input
335
+ // Touch input & virtual on screen gamepad
323
336
 
324
337
  /** True if a touch device has been detected
325
338
  * @memberof Input */
326
- const isTouchDevice = window.ontouchstart !== undefined;
339
+ const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
340
+
341
+ // touch gamepad internal variables
342
+ let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
327
343
 
328
344
  // try to enable touch mouse
329
- if (isTouchDevice)
345
+ function touchInputInit()
330
346
  {
347
+ // add non passive touch event listeners
348
+ let handleTouch = handleTouchDefault;
349
+ if (touchGamepadEnable)
350
+ {
351
+ // touch input internal variables
352
+ handleTouch = handleTouchGamepad;
353
+ touchGamepadButtons = [];
354
+ touchGamepadStick = vec2();
355
+ }
356
+ document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
357
+ document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
358
+ document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
359
+
331
360
  // override mouse events
332
- let wasTouching;
333
361
  onmousedown = onmouseup = ()=> 0;
334
362
 
335
363
  // handle all touch events the same way
336
- ontouchstart = ontouchmove = ontouchend = (e)=>
364
+ let wasTouching;
365
+ function handleTouchDefault(e)
337
366
  {
338
367
  // fix stalled audio requiring user interaction
339
368
  if (soundEnable && audioContext && audioContext.state != 'running')
@@ -362,27 +391,14 @@ if (isTouchDevice)
362
391
  // must return true so the document will get focus
363
392
  return true;
364
393
  }
365
- }
366
-
367
- ///////////////////////////////////////////////////////////////////////////////
368
- // touch gamepad, virtual on screen gamepad emulator for touch devices
369
-
370
- // touch input internal variables
371
- let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
372
394
 
373
- // create the touch gamepad, called automatically by the engine
374
- function createTouchGamepad()
375
- {
376
- // touch input internal variables
377
- touchGamepadButtons = [];
378
- touchGamepadStick = vec2();
379
-
380
- const touchHandler = ontouchstart;
381
- ontouchstart = ontouchmove = ontouchend = (e)=>
395
+ // special handling for virtual gamepad mode
396
+ function handleTouchGamepad(e)
382
397
  {
383
398
  // clear touch gamepad input
384
399
  touchGamepadStick = vec2();
385
400
  touchGamepadButtons = [];
401
+ isUsingGamepad = true;
386
402
 
387
403
  const touching = e.touches.length;
388
404
  if (touching)
@@ -423,9 +439,8 @@ function createTouchGamepad()
423
439
  }
424
440
  }
425
441
 
426
- // call default touch handler and set to using gamepad
427
- touchHandler.bind(window)(e);
428
- isUsingGamepad = true;
442
+ // call default touch handler so normal touch events still work
443
+ handleTouchDefault(e);
429
444
 
430
445
  // must return true so the document will get focus
431
446
  return true;
@@ -444,32 +459,33 @@ function touchGamepadRender()
444
459
  return;
445
460
 
446
461
  // setup the canvas
447
- overlayContext.save();
448
- overlayContext.globalAlpha = alpha*touchGamepadAlpha;
449
- overlayContext.strokeStyle = '#fff';
450
- overlayContext.lineWidth = 3;
462
+ const context = overlayContext;
463
+ context.save();
464
+ context.globalAlpha = alpha*touchGamepadAlpha;
465
+ context.strokeStyle = '#fff';
466
+ context.lineWidth = 3;
451
467
 
452
468
  // draw left analog stick
453
- overlayContext.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
454
- overlayContext.beginPath();
469
+ context.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
470
+ context.beginPath();
455
471
 
456
472
  const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
457
473
  if (touchGamepadAnalog) // draw circle shaped gamepad
458
474
  {
459
- overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
460
- overlayContext.fill();
461
- overlayContext.stroke();
475
+ context.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
476
+ context.fill();
477
+ context.stroke();
462
478
  }
463
479
  else // draw cross shaped gamepad
464
480
  {
465
481
  for(let i=10; i--;)
466
482
  {
467
483
  const angle = i*PI/4;
468
- overlayContext.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
469
- i%2 && overlayContext.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
470
- i==1 && overlayContext.fill();
484
+ context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
485
+ i%2 && context.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
486
+ i==1 && context.fill();
471
487
  }
472
- overlayContext.stroke();
488
+ context.stroke();
473
489
  }
474
490
 
475
491
  // draw right face buttons
@@ -477,13 +493,13 @@ function touchGamepadRender()
477
493
  for (let i=4; i--;)
478
494
  {
479
495
  const pos = rightCenter.add(vec2().setDirection(i, touchGamepadSize/2));
480
- overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
481
- overlayContext.beginPath();
482
- overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
483
- overlayContext.fill();
484
- overlayContext.stroke();
496
+ context.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
497
+ context.beginPath();
498
+ context.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
499
+ context.fill();
500
+ context.stroke();
485
501
  }
486
502
 
487
503
  // set canvas back to normal
488
- overlayContext.restore();
504
+ context.restore();
489
505
  }
@@ -85,6 +85,8 @@ class EngineObject
85
85
  this.spawnTime = time;
86
86
  /** @property {Array} - List of children of this object */
87
87
  this.children = [];
88
+ /** @property {Boolean} - Limit object speed using linear or circular math */
89
+ this.clampSpeedLinear = true;
88
90
 
89
91
  // parent child system
90
92
  /** @property {EngineObject} - Parent of object if in local space */
@@ -108,21 +110,46 @@ class EngineObject
108
110
  engineObjects.push(this);
109
111
  }
110
112
 
111
- /** Update the object transform and physics, called automatically by engine once each frame */
112
- update()
113
+ /** Update the object transform, called automatically by engine even when paused */
114
+ updateTransforms()
113
115
  {
114
116
  const parent = this.parent;
115
117
  if (parent)
116
118
  {
117
119
  // copy parent pos/angle
118
- this.pos = this.localPos.multiply(vec2(parent.getMirrorSign(),1)).rotate(-parent.angle).add(parent.pos);
119
- this.angle = parent.getMirrorSign()*this.localAngle + parent.angle;
120
- return;
120
+ const mirror = parent.getMirrorSign();
121
+ this.pos = this.localPos.multiply(vec2(mirror,1)).rotate(-parent.angle).add(parent.pos);
122
+ this.angle = mirror*this.localAngle + parent.angle;
121
123
  }
122
124
 
125
+ // update children
126
+ for (const child of this.children)
127
+ child.updateTransforms();
128
+ }
129
+
130
+ /** Update the object physics, called automatically by engine once each frame */
131
+ update()
132
+ {
133
+ // child objects do not have physics
134
+ if (this.parent)
135
+ return;
136
+
123
137
  // limit max speed to prevent missing collisions
124
- this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
125
- this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
138
+ if (this.clampSpeedLinear)
139
+ {
140
+ this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
141
+ this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
142
+ }
143
+ else
144
+ {
145
+ const length2 = this.velocity.lengthSquared();
146
+ if (length2 > objectMaxSpeed*objectMaxSpeed)
147
+ {
148
+ const s = objectMaxSpeed / length2**.5;
149
+ this.velocity.x *= s;
150
+ this.velocity.y *= s;
151
+ }
152
+ }
126
153
 
127
154
  // apply physics
128
155
  const oldPos = this.pos.copy();
@@ -134,8 +161,7 @@ class EngineObject
134
161
  // physics sanity checks
135
162
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
136
163
  ASSERT(this.damping >= 0 && this.damping <= 1);
137
-
138
- if (!enablePhysicsSolver || !this.mass) // do not update collision for fixed objects
164
+ if (!enablePhysicsSolver || !this.mass) // dont do collision for fixed objects
139
165
  return;
140
166
 
141
167
  const wasMovingDown = this.velocity.y < 0;
@@ -233,20 +233,21 @@ class ParticleEmitter extends EngineObject
233
233
  class Particle extends EngineObject
234
234
  {
235
235
  /**
236
- * Create a particle with the given shis.colorStart = undefined;ettings
237
- * @param {Vector2} position - World space position of the particle
238
- * @param {TileInfo} [tileInfo] - Tile info to render particles
239
- * @param {Number} [angle] - Angle to rotate the particle
240
- * @param {Color} [colorStart] - Color at start of life
241
- * @param {Color} [colorEnd] - Color at end of life
242
- * @param {Number} [lifeTime] - How long to live for
243
- * @param {Number} [sizeStart] - Angle to rotate the particle
244
- * @param {Number} [sizeEnd] - Angle to rotate the particle
245
- * @param {Number} [fadeRate] - Angle to rotate the particle
246
- * @param {Boolean} [additive] - Angle to rotate the particle
247
- * @param {Number} [trailScale] - If a trail, how long to make it
236
+ * Create a particle with the passed in settings
237
+ * Typically this is created automatically by a ParticleEmitter
238
+ * @param {Vector2} position - World space position of the particle
239
+ * @param {TileInfo} tileInfo - Tile info to render particles
240
+ * @param {Number} angle - Angle to rotate the particle
241
+ * @param {Color} colorStart - Color at start of life
242
+ * @param {Color} colorEnd - Color at end of life
243
+ * @param {Number} lifeTime - How long to live for
244
+ * @param {Number} sizeStart - Size at start of life
245
+ * @param {Number} sizeEnd - Size at end of life
246
+ * @param {Number} fadeRate - How quick to fade in/out
247
+ * @param {Boolean} additive - Does it use additive blend mode
248
+ * @param {Number} trailScale - If a trail, how long to make it
248
249
  * @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
249
- * @param {Function} [destroyCallback] - Called when particle dies
250
+ * @param {Function} [destroyCallback] - Callback when particle dies
250
251
  */
251
252
  constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
252
253
  )
@@ -273,6 +274,9 @@ class Particle extends EngineObject
273
274
  this.localSpaceEmitter = localSpaceEmitter;
274
275
  /** @property {Function} - Called when particle dies */
275
276
  this.destroyCallback = destroyCallback;
277
+
278
+ // particles use circular clamped speed
279
+ this.clampSpeedLinear = false;
276
280
  }
277
281
 
278
282
  /** Render the particle, automatically called each frame, sorted by renderOrder */
@@ -55,6 +55,12 @@ let fontDefault = 'arial';
55
55
  * @memberof Settings */
56
56
  let showSplashScreen = false;
57
57
 
58
+ /** Disables all rendering, audio, and input for servers
59
+ * @type {Boolean}
60
+ * @default
61
+ * @memberof Settings */
62
+ let headlessMode = false;
63
+
58
64
  ///////////////////////////////////////////////////////////////////////////////
59
65
  // WebGL settings
60
66
 
@@ -83,7 +89,7 @@ let tileSizeDefault = vec2(16);
83
89
  * @type {Number}
84
90
  * @default
85
91
  * @memberof Settings */
86
- let tileFixBleedScale = .1;
92
+ let tileFixBleedScale = .5;
87
93
 
88
94
  ///////////////////////////////////////////////////////////////////////////////
89
95
  // Object settings
@@ -208,7 +214,7 @@ let soundEnable = true;
208
214
  * @type {Number}
209
215
  * @default
210
216
  * @memberof Settings */
211
- let soundVolume = .5;
217
+ let soundVolume = .3;
212
218
 
213
219
  /** Default range where sound no longer plays
214
220
  * @type {Number}
@@ -293,6 +299,11 @@ function setFontDefault(font) { fontDefault = font; }
293
299
  * @memberof Settings */
294
300
  function setShowSplashScreen(show) { showSplashScreen = show; }
295
301
 
302
+ /** Set to disalbe rendering, audio, and input for servers
303
+ * @param {Boolean} headless
304
+ * @memberof Settings */
305
+ function setHeadlessMode(headless) { headlessMode = headless; }
306
+
296
307
  /** Set if webgl rendering is enabled
297
308
  * @param {Boolean} enable
298
309
  * @memberof Settings */
@@ -406,7 +417,12 @@ function setSoundEnable(enable) { soundEnable = enable; }
406
417
  /** Set volume scale to apply to all sound, music and speech
407
418
  * @param {Number} volume
408
419
  * @memberof Settings */
409
- function setSoundVolume(volume) { soundVolume = volume; }
420
+ function setSoundVolume(volume)
421
+ {
422
+ soundVolume = volume;
423
+ if (soundEnable && !headlessMode && audioGainNode)
424
+ audioGainNode.gain.value = volume; // update gain immediatly
425
+ }
410
426
 
411
427
  /** Set default range where sound no longer plays
412
428
  * @param {Number} range
@@ -181,7 +181,7 @@ class TileLayer extends EngineObject
181
181
 
182
182
  /** @property {HTMLCanvasElement} - The canvas used by this tile layer */
183
183
  this.canvas = document.createElement('canvas');
184
- /** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
184
+ /** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
185
185
  this.context = this.canvas.getContext('2d');
186
186
  /** @property {Vector2} - How much to scale this layer when rendered */
187
187
  this.scale = scale;
@@ -192,6 +192,17 @@ class TileLayer extends EngineObject
192
192
  this.data = [];
193
193
  for (let j = this.size.area(); j--;)
194
194
  this.data.push(new TileLayerData);
195
+
196
+ if (headlessMode)
197
+ {
198
+ // disable rendering
199
+ this.redraw = () => {};
200
+ this.render = () => {};
201
+ this.redrawStart = () => {};
202
+ this.redrawEnd = () => {};
203
+ this.drawTileData = () => {};
204
+ this.drawCanvas2D = () => {};
205
+ }
195
206
  }
196
207
 
197
208
  /** Set data at a given position in the array
@@ -222,7 +233,7 @@ class TileLayer extends EngineObject
222
233
  ASSERT(mainContext != this.context, 'must call redrawEnd() after drawing tiles');
223
234
 
224
235
  // flush and copy gl canvas because tile canvas does not use webgl
225
- glEnable && !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
236
+ !glOverlay && !this.isOverlay && glCopyToContext(mainContext);
226
237
 
227
238
  // draw the entire cached level onto the canvas
228
239
  const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
@@ -272,14 +283,14 @@ class TileLayer extends EngineObject
272
283
  this.context.imageSmoothingEnabled = !canvasPixelated;
273
284
 
274
285
  // setup gl rendering if enabled
275
- glEnable && glPreRender();
286
+ glPreRender();
276
287
  }
277
288
 
278
289
  /** Call to end the redraw process */
279
290
  redrawEnd()
280
291
  {
281
292
  ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
282
- glEnable && glCopyToContext(mainContext, true);
293
+ glCopyToContext(mainContext, true);
283
294
  //debugSaveCanvas(this.canvas);
284
295
 
285
296
  // set stuff back to normal