littlejsengine 1.9.4 → 1.9.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.
@@ -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
  )
@@ -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 */
@@ -72,7 +72,7 @@ function tileCollisionTest(pos, size=vec2(), object)
72
72
  }
73
73
  }
74
74
 
75
- /** Return the center of tile if any that is hit (does not return the exact intersection)
75
+ /** Return the center of first tile hit (does not return the exact intersection)
76
76
  * @param {Vector2} posStart
77
77
  * @param {Vector2} posEnd
78
78
  * @param {EngineObject} [object]
@@ -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)));
@@ -268,15 +279,18 @@ class TileLayer extends EngineObject
268
279
  mainCanvas.height = mainCanvasSize.y;
269
280
  }
270
281
 
271
- // begin a new render for the tile canvas
272
- enginePreRender();
282
+ // disable smoothing for pixel art
283
+ this.context.imageSmoothingEnabled = !canvasPixelated;
284
+
285
+ // setup gl rendering if enabled
286
+ glPreRender();
273
287
  }
274
288
 
275
289
  /** Call to end the redraw process */
276
290
  redrawEnd()
277
291
  {
278
292
  ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
279
- glEnable && glCopyToContext(mainContext, true);
293
+ glCopyToContext(mainContext, true);
280
294
  //debugSaveCanvas(this.canvas);
281
295
 
282
296
  // set stuff back to normal
@@ -64,7 +64,7 @@ function clamp(value, min=0, max=1) { return value < min ? min : value > max ? m
64
64
  * @return {Number}
65
65
  * @memberof Utilities */
66
66
  function percent(value, valueA, valueB)
67
- { return valueB-valueA ? clamp((value-valueA) / (valueB-valueA)) : 0; }
67
+ { return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
68
68
 
69
69
  /** Linearly interpolates between values passed in using percent
70
70
  * @param {Number} percent
@@ -121,16 +121,57 @@ function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
121
121
  function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
122
122
 
123
123
  /** Returns true if two axis aligned bounding boxes are overlapping
124
- * @param {Vector2} pointA - Center of box A
125
- * @param {Vector2} sizeA - Size of box A
126
- * @param {Vector2} pointB - Center of box B
127
- * @param {Vector2} [sizeB=(0,0)] - Size of box B, a point if undefined
128
- * @return {Boolean} - True if overlapping
124
+ * @param {Vector2} posA - Center of box A
125
+ * @param {Vector2} sizeA - Size of box A
126
+ * @param {Vector2} posB - Center of box B
127
+ * @param {Vector2} [sizeB=(0,0)] - Size of box B, a point if undefined
128
+ * @return {Boolean} - True if overlapping
129
129
  * @memberof Utilities */
130
- function isOverlapping(pointA, sizeA, pointB, sizeB=vec2())
130
+ function isOverlapping(posA, sizeA, posB, sizeB=vec2())
131
131
  {
132
- return abs(pointA.x - pointB.x)*2 < sizeA.x + sizeB.x
133
- && abs(pointA.y - pointB.y)*2 < sizeA.y + sizeB.y;
132
+ return abs(posA.x - posB.x)*2 < sizeA.x + sizeB.x
133
+ && abs(posA.y - posB.y)*2 < sizeA.y + sizeB.y;
134
+ }
135
+
136
+ /** Returns true if a line segment is intersecting an axis aligned box
137
+ * @param {Vector2} start - Start of raycast
138
+ * @param {Vector2} end - End of raycast
139
+ * @param {Vector2} pos - Center of box
140
+ * @param {Vector2} size - Size of box
141
+ * @return {Boolean} - True if intersecting
142
+ * @memberof Utilities */
143
+ function isIntersecting(start, end, pos, size)
144
+ {
145
+ // Liang-Barsky algorithm
146
+ const boxMin = pos.subtract(size.scale(.5));
147
+ const boxMax = boxMin.add(size);
148
+ const delta = end.subtract(start);
149
+ const a = start.subtract(boxMin);
150
+ const b = start.subtract(boxMax);
151
+ const p = [-delta.x, delta.x, -delta.y, delta.y];
152
+ const q = [a.x, -b.x, a.y, -b.y];
153
+ let tMin = 0, tMax = 1;
154
+ for (let i = 4; i--;)
155
+ {
156
+ if (p[i])
157
+ {
158
+ const t = q[i] / p[i];
159
+ if (p[i] < 0)
160
+ {
161
+ if (t > tMax) return false;
162
+ tMin = max(t, tMin);
163
+ }
164
+ else
165
+ {
166
+ if (t < tMin) return false;
167
+ tMax = min(t, tMax);
168
+ }
169
+ }
170
+ else if (q[i] < 0)
171
+ return false;
172
+ }
173
+
174
+ return true;
134
175
  }
135
176
 
136
177
  /** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
@@ -230,7 +271,7 @@ class RandomGenerator
230
271
  this.seed ^= this.seed << 13;
231
272
  this.seed ^= this.seed >>> 17;
232
273
  this.seed ^= this.seed << 5;
233
- return valueB + (valueA - valueB) * abs(this.seed % 1e9) / 1e9;
274
+ return valueB + (valueA - valueB) * abs(this.seed % 1e8) / 1e8;
234
275
  }
235
276
 
236
277
  /** Returns a floored seeded random value the two values passed in
@@ -241,7 +282,7 @@ class RandomGenerator
241
282
 
242
283
  /** Randomly returns either -1 or 1 deterministically
243
284
  * @return {Number} */
244
- sign() { return this.int(2) * 2 - 1; }
285
+ sign() { return this.float() > .5 ? 1 : -1; }
245
286
  }
246
287
 
247
288
  ///////////////////////////////////////////////////////////////////////////////
@@ -289,6 +330,7 @@ class Vector2
289
330
  * @param {Number} [y] - Y axis location */
290
331
  constructor(x=0, y=0)
291
332
  {
333
+ ASSERT(typeof x === 'number' && typeof y === 'number');
292
334
  /** @property {Number} - X axis location */
293
335
  this.x = x;
294
336
  /** @property {Number} - Y axis location */
@@ -615,12 +657,14 @@ class Color
615
657
  * @return {Color} */
616
658
  setHSLA(h=0, s=0, l=1, a=1)
617
659
  {
660
+ h = mod(h,1);
661
+ s = clamp(s);
662
+ l = clamp(l);
618
663
  const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
619
664
  f = (p, q, t)=>
620
- (t = ((t%1)+1)%1) < 1/6 ? p+(q-p)*6*t :
621
- t < 1/2 ? q :
622
- t < 2/3 ? p+(q-p)*(2/3-t)*6 : p;
623
-
665
+ (t = mod(t,1))*6 < 1 ? p+(q-p)*6*t :
666
+ t*2 < 1 ? q :
667
+ t*3 < 2 ? p+(q-p)*(4-t*6) : p;
624
668
  this.r = f(p, q, h + 1/3);
625
669
  this.g = f(p, q, h);
626
670
  this.b = f(p, q, h - 1/3);
@@ -671,13 +715,12 @@ class Color
671
715
  ).clamp();
672
716
  }
673
717
 
674
- /** Returns this color expressed as a hex color code
718
+ /** Returns this color expressed as a rgb color code
675
719
  * @param {Boolean} [useAlpha] - if alpha should be included in result
676
720
  * @return {String} */
677
- toString(useAlpha = true)
678
- {
679
- const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
680
- return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
721
+ toString(useAlpha = true)
722
+ {
723
+ return `rgb(${this.r*255},${this.g*255},${this.b*255},${useAlpha ? this.a : 0})`;
681
724
  }
682
725
 
683
726
  /** Set this color from a hex code
@@ -735,11 +778,11 @@ class Timer
735
778
 
736
779
  /** Returns true if set and has not elapsed
737
780
  * @return {Boolean} */
738
- active() { return time <= this.time; }
781
+ active() { return time < this.time; }
739
782
 
740
783
  /** Returns true if set and elapsed
741
784
  * @return {Boolean} */
742
- elapsed() { return time > this.time; }
785
+ elapsed() { return time >= this.time; }
743
786
 
744
787
  /** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
745
788
  * @return {Number} */
@@ -30,6 +30,8 @@ let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData,
30
30
  // Initalize WebGL, called automatically by the engine
31
31
  function glInit()
32
32
  {
33
+ if (!glEnable || headlessMode) return;
34
+
33
35
  // create the canvas and textures
34
36
  glCanvas = document.createElement('canvas');
35
37
  glContext = glCanvas.getContext('webgl2');
@@ -42,11 +44,11 @@ function glInit()
42
44
  '#version 300 es\n' + // specify GLSL ES version
43
45
  'precision highp float;'+ // use highp for better accuracy
44
46
  'uniform mat4 m;'+ // transform matrix
45
- 'in vec2 g;'+ // geometry
46
- 'in vec4 p,u,c,a;'+ // position/size, uvs, color, additiveColor
47
- 'in float r;'+ // rotation
48
- 'out vec2 v;'+ // return uv, color, additiveColor
49
- 'out vec4 d,e;'+ // return uv, color, additiveColor
47
+ 'in vec2 g;'+ // in: geometry
48
+ 'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
49
+ 'in float r;'+ // in: rotation
50
+ 'out vec2 v;'+ // out: uv
51
+ 'out vec4 d,e;'+ // out: color, additiveColor
50
52
  'void main(){'+ // shader entry point
51
53
  'vec2 s=(g-.5)*p.zw;'+ // get size offset
52
54
  'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
@@ -56,10 +58,10 @@ function glInit()
56
58
  ,
57
59
  '#version 300 es\n' + // specify GLSL ES version
58
60
  'precision highp float;'+ // use highp for better accuracy
59
- 'in vec2 v;'+ // uv
60
- 'in vec4 d,e;'+ // color, additiveColor
61
61
  'uniform sampler2D s;'+ // texture
62
- 'out vec4 c;'+ // out color
62
+ 'in vec2 v;'+ // in: uv
63
+ 'in vec4 d,e;'+ // in: color, additiveColor
64
+ 'out vec4 c;'+ // out: color
63
65
  'void main(){'+ // shader entry point
64
66
  'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
65
67
  '}' // end of shader
@@ -81,9 +83,11 @@ function glInit()
81
83
  // Setup render each frame, called automatically by engine
82
84
  function glPreRender()
83
85
  {
86
+ if (!glEnable || headlessMode) return;
87
+
84
88
  // clear and set to same size as main canvas
85
89
  glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
86
- glContext.clear(gl_COLOR_BUFFER_BIT);
90
+ //glContext.clear(gl_COLOR_BUFFER_BIT); // auto cleared when size is set
87
91
 
88
92
  // set up the shader
89
93
  glContext.useProgram(glShader);
@@ -117,12 +121,12 @@ function glPreRender()
117
121
  const s = vec2(2*cameraScale).divide(mainCanvasSize);
118
122
  const p = vec2(-1).subtract(cameraPos.multiply(s));
119
123
  glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
120
- new Float32Array([
124
+ [
121
125
  s.x, 0, 0, 0,
122
126
  0, s.y, 0, 0,
123
127
  1, 1, 1, 1,
124
128
  p.x, p.y, 0, 0
125
- ])
129
+ ]
126
130
  );
127
131
  }
128
132
 
@@ -133,7 +137,7 @@ function glPreRender()
133
137
  function glSetTexture(texture)
134
138
  {
135
139
  // must flush cache with the old texture to set a new one
136
- if (texture == glActiveTexture)
140
+ if (headlessMode || texture == glActiveTexture)
137
141
  return;
138
142
 
139
143
  glFlush();
@@ -193,7 +197,6 @@ function glCreateTexture(image)
193
197
  const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
194
198
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
195
199
  glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
196
-
197
200
  return texture;
198
201
  }
199
202
 
@@ -217,12 +220,12 @@ function glFlush()
217
220
  }
218
221
 
219
222
  /** Draw any sprites still in the buffer, copy to main canvas and clear
220
- * @param {CanvasRenderingContext2D} context
223
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
221
224
  * @param {Boolean} [forceDraw]
222
225
  * @memberof WebGL */
223
226
  function glCopyToContext(context, forceDraw=false)
224
227
  {
225
- if (!glInstanceCount && !forceDraw) return;
228
+ if (!glEnable || !glInstanceCount && !forceDraw) return;
226
229
 
227
230
  glFlush();
228
231
 
@@ -279,7 +282,7 @@ let glPostShader, glPostTexture, glPostIncludeOverlay;
279
282
  function glInitPostProcess(shaderCode, includeOverlay=false)
280
283
  {
281
284
  ASSERT(!glPostShader, 'can only have 1 post effects shader');
282
-
285
+ if (headlessMode) return;
283
286
  if (!shaderCode) // default shader pass through
284
287
  shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
285
288
 
@@ -318,8 +321,7 @@ function glInitPostProcess(shaderCode, includeOverlay=false)
318
321
  // Render the post processing shader, called automatically by the engine
319
322
  function glRenderPostProcess()
320
323
  {
321
- if (!glPostShader)
322
- return;
324
+ if (!glPostShader || headlessMode) return;
323
325
 
324
326
  // prepare to render post process shader
325
327
  if (glEnable)