littlejsengine 1.9.4 → 1.9.5

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/reference.md CHANGED
@@ -13,13 +13,7 @@ To start LittleJS, you need to create a few functions and pass them to engineIni
13
13
 
14
14
  ```javascript
15
15
  // Start up LittleJS engine with your callback functions
16
- engineInit(init, update, updatePost, render, renderPost, imageSources=['tiles.png'])
17
-
18
- // Destroy and remove all objects
19
- engineObjectsDestroy()
20
-
21
- // Trigger a callback for each object within a given area
22
- engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
16
+ engineInit(init, update, updatePost, render, renderPost, imageSources=['tiles.png']);
23
17
  ```
24
18
 
25
19
  ## LittleJS Utilities Classes and Functions
@@ -53,6 +47,7 @@ lerpAngle(percent, angleA, angleB) // Linearly interpolates with wrap
53
47
  smoothStep(percent) // Applies smoothstep function
54
48
  nearestPowerOfTwo(value) // Returns the nearest power of two
55
49
  isOverlapping(pointA, sizeA, pointB, sizeB) // Checks if bounding boxes overlap
50
+ isIntersecting(start, end, pos, size) // Checks if ray intersects box
56
51
  wave(frequency=1, amplitude=1, t=time) // Returns oscillating wave
57
52
  formatTime(t) // Formats seconds for display
58
53
 
@@ -103,7 +98,7 @@ Color.scale(scale, alphaScale=scale) // Scale by a float
103
98
  Color.clamp() // Clamp this color
104
99
  Color.lerp(c, percent) // Interpolate between colors
105
100
  Color.setHSLA(h=0, s=0, l=1, a=1) // Set the color from HSLA values
106
- Color.getHSLA() // Get the color in HSLA format
101
+ Color.HSLA() // Get the color in HSLA format
107
102
  Color.mutate(amount=.05, alphaAmount=0) // Randomly diverge from this color
108
103
  Color.setHex(hex) // Set this color from a hex code
109
104
  Color.rgbaInt() // Get this color as 32 bit RGBA value
@@ -138,7 +133,6 @@ Timer.valueOf() // Get how long since elapsed, 0 if not set
138
133
  // Drawing functions
139
134
  drawTile(pos, size=(1,1), tileInfo, color, angle=0, mirror, additiveColor)
140
135
  drawRect(pos, size=(1,1), color=(1,1,1,1), angle=0)
141
- drawPoly(points, color=(1,1,1,1))
142
136
  drawLine(posA, posB, thickness=.1, color=(1,1,1,1))
143
137
  drawCanvas2D(pos, size, angle, mirror, drawFunction)
144
138
  drawText(text, pos, size=1, color=(1,1,1,1), lineWidth, lineColor)
@@ -153,6 +147,7 @@ TileInfo.pos // Top left corner of tile in pixels
153
147
  TileInfo.size // Size of tile in pixels
154
148
  TileInfo.textureIndex // Texture index to use
155
149
  TileInfo.offset(offset) // Offset this tile by a certain amount in pixels
150
+ TileInfo.frame(frame) // Offset this tile by a number of animation frames
156
151
  TileInfo.getTextureInfo() // Returns texture info for this tile
157
152
 
158
153
  // Texture Info Object
@@ -285,7 +280,6 @@ EngineObject.update() // Update object, called auto
285
280
  EngineObject.render() // Render object, called automatically
286
281
  EngineObject.destroy() // Destroy this object and children
287
282
  EngineObject.collideWithTile(tileData, pos) // Tile collision resolve check
288
- EngineObject.collideWithTileRaycast(tileData, pos) // Check if raycast hit
289
283
  EngineObject.collideWithObject(object) // Object collision resolve check
290
284
  EngineObject.getAliveTime(object) // How long since object was created
291
285
  EngineObject.applyAcceleration(acceleration) // Apply acceleration
@@ -314,7 +308,7 @@ EngineObject.renderOrder // Objects are sorted by render order
314
308
  EngineObject.velocity // Velocity of the object
315
309
  EngineObject.angleVelocity // Angular velocity of the object
316
310
 
317
- // Object settings
311
+ // Engine Object settings
318
312
  enablePhysicsSolver = true // Enable collisions between objects?
319
313
  objectDefaultMass = 1 // Default object mass for collisions
320
314
  objectDefaultDamping = 1 // How much to slow velocity by each frame (0-1)
@@ -323,6 +317,12 @@ objectDefaultElasticity = 0 // How much to bounce when a collision occurs (0-1
323
317
  objectDefaultFriction = .8 // How much to slow when touching (0-1)
324
318
  objectMaxSpeed = 1 // Clamp max speed to avoid fast objects missing collisions
325
319
  gravity = 0 // How much gravity to apply to objects
320
+
321
+ // Engine Object functions
322
+ engineObjectsCollect(pos, size, objects=engineObjects)k
323
+ engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
324
+ engineObjectsRaycast(start, end, objects=engineObjects)
325
+ engineObjectsDestroy()
326
326
  ```
327
327
 
328
328
  ## LittleJS Tile Layer System
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.9.4';
33
+ const engineVersion = '1.9.5';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {Number}
@@ -97,6 +97,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
97
97
  {
98
98
  ASSERT(Array.isArray(imageSources), 'pass in images as array');
99
99
 
100
+ // Called automatically by engine to setup render system
101
+ function enginePreRender()
102
+ {
103
+ // save canvas size
104
+ mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
105
+
106
+ // disable smoothing for pixel art
107
+ mainContext.imageSmoothingEnabled = !canvasPixelated;
108
+
109
+ // setup gl rendering if enabled
110
+ glEnable && glPreRender();
111
+ }
112
+
100
113
  // internal update loop for engine
101
114
  function engineUpdate(frameTimeMS=0)
102
115
  {
@@ -254,7 +267,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
254
267
  }
255
268
  image.src = src;
256
269
  })
257
- )
270
+ );
258
271
 
259
272
  // draw splash screen
260
273
  showSplashScreen && promises.push(new Promise(resolve =>
@@ -279,19 +292,6 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
279
292
  });
280
293
  }
281
294
 
282
- // Called automatically by engine to setup render system
283
- function enginePreRender()
284
- {
285
- // save canvas size
286
- mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
287
-
288
- // disable smoothing for pixel art
289
- mainContext.imageSmoothingEnabled = !canvasPixelated;
290
-
291
- // setup gl rendering if enabled
292
- glEnable && glPreRender();
293
- }
294
-
295
295
  /** Update each engine object, remove destroyed objects, and update time
296
296
  * @memberof Engine */
297
297
  function engineObjectsUpdate()
@@ -325,30 +325,63 @@ function engineObjectsDestroy()
325
325
  engineObjects = engineObjects.filter(o=>!o.destroyed);
326
326
  }
327
327
 
328
- /** Triggers a callback for each object within a given area
329
- * @param {Vector2} [pos] - Center of test area
328
+ /** Collects all object within a given area
329
+ * @param {Vector2} [pos] - Center of test area, or undefined for all objects
330
330
  * @param {Number|Vector2} [size] - Radius of circle if float, rectangle size if Vector2
331
- * @param {Function} [callbackFunction] - Calls this function on every object that passes the test
332
331
  * @param {Array} [objects=engineObjects] - List of objects to check
332
+ * @return {Array} - List of collected objects
333
333
  * @memberof Engine */
334
- function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
334
+ function engineObjectsCollect(pos, size, objects=engineObjects)
335
335
  {
336
+ const collectedObjects = [];
336
337
  if (!pos) // all objects
337
338
  {
338
339
  for (const o of objects)
339
- callbackFunction(o);
340
+ collectedObjects.push(o);
340
341
  }
341
- else if (typeof size === 'object') // bounding box test
342
+ else if (size instanceof Vector2) // bounding box test
342
343
  {
343
344
  for (const o of objects)
344
- isOverlapping(pos, size, o.pos, o.size) && callbackFunction(o);
345
+ isOverlapping(pos, size, o.pos, o.size) && collectedObjects.push(o);
345
346
  }
346
347
  else // circle test
347
348
  {
348
349
  const sizeSquared = size*size;
349
350
  for (const o of objects)
350
- pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
351
+ pos.distanceSquared(o.pos) < sizeSquared && collectedObjects.push(o);
352
+ }
353
+ return collectedObjects;
354
+ }
355
+
356
+ /** Triggers a callback for each object within a given area
357
+ * @param {Vector2} [pos] - Center of test area, or undefined for all objects
358
+ * @param {Number|Vector2} [size] - Radius of circle if float, rectangle size if Vector2
359
+ * @param {Function} [callbackFunction] - Calls this function on every object that passes the test
360
+ * @param {Array} [objects=engineObjects] - List of objects to check
361
+ * @memberof Engine */
362
+ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
363
+ { engineObjectsCollect(pos, size, objects).forEach(o => callbackFunction(o)); }
364
+
365
+ /** Return a list of objects intersecting a ray
366
+ * @param {Vector2} start
367
+ * @param {Vector2} end
368
+ * @param {Array} [objects=engineObjects] - List of objects to check
369
+ * @return {Array} - List of objects hit
370
+ * @memberof Engine */
371
+ function engineObjectsRaycast(start, end, objects=engineObjects)
372
+ {
373
+ const hitObjects = [];
374
+ for (const o of objects)
375
+ {
376
+ if (o.collideRaycast && isIntersecting(start, end, o.pos, o.size))
377
+ {
378
+ debugRaycast && debugRect(o.pos, o.size, '#f00');
379
+ hitObjects.push(o);
380
+ }
351
381
  }
382
+
383
+ debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
384
+ return hitObjects;
352
385
  }
353
386
 
354
387
  ///////////////////////////////////////////////////////////////////////////////
@@ -436,16 +469,16 @@ function drawEngineSplashScreen(t)
436
469
  rect(37,14,9,6);
437
470
 
438
471
  // big stack
439
- rect(50,20,10,-10,color(0,1));
440
- rect(50,20,6.5,-10,color(0,2));
441
- rect(50,20,3.5,-10,color(0,3));
442
- rect(50,20,10,-10);
443
- circle(55,2,11.4,.5,PI-.5,color(3,3));
444
- circle(55,2,11.4,.5,PI/2,color(3,2),1);
445
- circle(55,2,11.4,.5,PI-.5);
446
- rect(45,7,20,-7,color(0,2));
447
- rect(45,0,20,3,color(0,3));
448
- rect(45,0,20,7);
472
+ rect(50,20,10,-8,color(0,1))
473
+ rect(50,20,6.5,-8,color(0,2))
474
+ rect(50,20,3.5,-8,color(0,3))
475
+ rect(50,20,10,-8)
476
+ circle(55,2,11.4,.5,PI-.5,color(3,3))
477
+ circle(55,2,11.4,.5,PI/2,color(3,2),1)
478
+ circle(55,2,11.4,.5,PI-.5)
479
+ rect(45,7,20,-7,color(0,2))
480
+ rect(45,-1,20,4,color(0,3))
481
+ rect(45,-1,20,8)
449
482
 
450
483
  // engine
451
484
  for (let i=5; i--;)
@@ -161,10 +161,10 @@ function typeScriptBuildStep(filename)
161
161
  const tsFilename = `${BUILD_FOLDER}/${ENGINE_NAME}.d.ts`
162
162
  child_process.execSync(`npx tsc ${filename} --declaration --allowJs --emitDeclarationOnly --outFile ${tsFilename}`);
163
163
 
164
- // Remove declare module part
165
- //let fileContent = fs.readFileSync(tsFilename, 'utf8');
166
- //const r = new RegExp(`declare module "${ENGINE_NAME}\.esm" \{([\\s\\S]*?)\}`);
167
- //fs.writeFileSync(tsFilename, fileContent.replace(r, '$1'));
164
+ // Make declare module part use the package name "littlejsengine"
165
+ let fileContent = fs.readFileSync(tsFilename, 'utf8');
166
+ fileContent = fileContent.replace(`${ENGINE_NAME}\.esm`, 'littlejsengine')
167
+ fs.writeFileSync(tsFilename, fileContent);
168
168
 
169
169
  }
170
170
  catch (e) { handleError(e, 'Failed to run TypeScript build step!'); }
@@ -353,7 +353,7 @@ function debugRender()
353
353
  overlayContext.restore();
354
354
  });
355
355
 
356
- // remove expired pritives
356
+ // remove expired primitives
357
357
  debugPrimitives = debugPrimitives.filter(r=>r.time<0);
358
358
  }
359
359
 
@@ -101,6 +101,8 @@ class EngineObject
101
101
  this.collideSolidObjects = false;
102
102
  /** @property {Boolean} - Object collides with and blocks other objects */
103
103
  this.isSolid = false;
104
+ /** @property {Boolean} - Object collides with raycasts */
105
+ this.collideRaycast = false;
104
106
 
105
107
  // add to list of objects
106
108
  engineObjects.push(this);
@@ -305,13 +307,7 @@ class EngineObject
305
307
  * @param {Number} tileData - the value of the tile at the position
306
308
  * @param {Vector2} pos - tile where the collision occured
307
309
  * @return {Boolean} - true if the collision should be resolved */
308
- collideWithTile(tileData, pos) { return tileData > 0; }
309
-
310
- /** Called to check if a tile raycast hit
311
- * @param {Number} tileData - the value of the tile at the position
312
- * @param {Vector2} pos - tile where the raycast is
313
- * @return {Boolean} - true if the raycast should hit */
314
- collideWithTileRaycast(tileData, pos) { return tileData > 0; }
310
+ collideWithTile(tileData, pos) { return tileData > 0; }
315
311
 
316
312
  /** Called to check if a object collision should be resolved
317
313
  * @param {EngineObject} object - the object to test against
@@ -358,16 +354,18 @@ class EngineObject
358
354
  }
359
355
 
360
356
  /** Set how this object collides
361
- * @param {Boolean} [collideSolidObjects] - Does it collide with solid objects
362
- * @param {Boolean} [isSolid] - Does it collide with and block other objects (expensive in large numbers)
363
- * @param {Boolean} [collideTiles] - Does it collide with the tile collision */
364
- setCollision(collideSolidObjects=true, isSolid=true, collideTiles=true)
357
+ * @param {Boolean} [collideSolidObjects] - Does it collide with solid objects?
358
+ * @param {Boolean} [isSolid] - Does it collide with and block other objects? (expensive in large numbers)
359
+ * @param {Boolean} [collideTiles] - Does it collide with the tile collision?
360
+ * @param {Boolean} [collideRaycast] - Does it collide with raycasts? */
361
+ setCollision(collideSolidObjects=true, isSolid=true, collideTiles=true, collideRaycast=true)
365
362
  {
366
363
  ASSERT(collideSolidObjects || !isSolid, 'solid objects must be set to collide');
367
364
 
368
365
  this.collideSolidObjects = collideSolidObjects;
369
366
  this.isSolid = isSolid;
370
367
  this.collideTiles = collideTiles;
368
+ this.collideRaycast = collideRaycast;
371
369
  }
372
370
 
373
371
  /** Returns string containg info about this object for debugging
@@ -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]
@@ -268,8 +268,11 @@ class TileLayer extends EngineObject
268
268
  mainCanvas.height = mainCanvasSize.y;
269
269
  }
270
270
 
271
- // begin a new render for the tile canvas
272
- enginePreRender();
271
+ // disable smoothing for pixel art
272
+ this.context.imageSmoothingEnabled = !canvasPixelated;
273
+
274
+ // setup gl rendering if enabled
275
+ glEnable && glPreRender();
273
276
  }
274
277
 
275
278
  /** Call to end the redraw process */
@@ -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