littlejsengine 1.10.2 → 1.10.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.
Files changed (53) hide show
  1. package/README.md +15 -93
  2. package/dist/littlejs.d.ts +79 -17
  3. package/dist/littlejs.esm.js +179 -90
  4. package/dist/littlejs.esm.min.js +1 -1
  5. package/dist/littlejs.js +165 -90
  6. package/dist/littlejs.min.js +1 -1
  7. package/dist/littlejs.release.js +165 -90
  8. package/examples/box2d/game.js +15 -1
  9. package/examples/box2d/gameObjects.js +10 -9
  10. package/examples/box2d/index.html +5 -5
  11. package/examples/box2d/scenes.js +4 -4
  12. package/examples/box2d/tiles.png +0 -0
  13. package/examples/breakout/index.html +3 -3
  14. package/examples/breakoutTutorial/README.md +9 -7
  15. package/examples/breakoutTutorial/game.js +6 -6
  16. package/examples/breakoutTutorial/index.html +2 -2
  17. package/examples/electron/game.js +3 -0
  18. package/examples/electron/index.html +2 -2
  19. package/examples/htmlMenu/index.html +3 -3
  20. package/examples/js13k/game.js +3 -0
  21. package/examples/js13k/index.html +13 -13
  22. package/examples/module/game.js +3 -0
  23. package/examples/module/index.html +1 -1
  24. package/examples/particles/index.html +4 -1
  25. package/examples/platformer/game.js +21 -15
  26. package/examples/platformer/gameCharacter.js +15 -14
  27. package/examples/platformer/gameEffects.js +3 -4
  28. package/examples/platformer/gameLevel.js +18 -33
  29. package/examples/platformer/gameObjects.js +9 -14
  30. package/examples/platformer/index.html +8 -8
  31. package/examples/platformer/tiles.png +0 -0
  32. package/examples/puzzle/index.html +2 -2
  33. package/examples/starter/game.js +3 -0
  34. package/examples/starter/index.html +13 -13
  35. package/examples/stress/index.html +1 -1
  36. package/examples/typescript/game.js +2 -0
  37. package/examples/typescript/game.ts +4 -1
  38. package/examples/typescript/index.html +1 -1
  39. package/examples/uiSystem/game.js +23 -16
  40. package/examples/uiSystem/index.html +3 -14
  41. package/package.json +1 -1
  42. package/plugins/postProcess.js +1 -1
  43. package/plugins/uiSystem.js +84 -24
  44. package/src/engine.js +14 -22
  45. package/src/engineAudio.js +7 -13
  46. package/src/engineDraw.js +20 -19
  47. package/src/engineExport.js +14 -0
  48. package/src/engineInput.js +8 -6
  49. package/src/engineMedals.js +19 -5
  50. package/src/engineObject.js +9 -5
  51. package/src/engineTileLayer.js +10 -8
  52. package/src/engineUtilities.js +60 -10
  53. package/src/engineWebGL.js +17 -2
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.10.2';
33
+ const engineVersion = '1.10.7';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {Number}
@@ -74,13 +74,6 @@ let timeReal = 0;
74
74
  * @default false
75
75
  * @memberof Engine */
76
76
  let paused = false;
77
-
78
- /** The root element that engine is attached to
79
- * @type {HTMLElement}
80
- * @default document.body
81
- * @memberof Engine */
82
- let engineRoot;
83
-
84
77
  /** Set if game is paused
85
78
  * @param {Boolean} isPaused
86
79
  * @memberof Engine */
@@ -100,6 +93,8 @@ const pluginUpdateList = [], pluginRenderList = [];
100
93
  * @memberof Engine */
101
94
  function engineAddPlugin(updateFunction, renderFunction)
102
95
  {
96
+ ASSERT(!pluginUpdateList.includes(updateFunction));
97
+ ASSERT(!pluginRenderList.includes(renderFunction));
103
98
  updateFunction && pluginUpdateList.push(updateFunction);
104
99
  renderFunction && pluginRenderList.push(renderFunction);
105
100
  }
@@ -108,12 +103,12 @@ function engineAddPlugin(updateFunction, renderFunction)
108
103
  // Main engine functions
109
104
 
110
105
  /** Startup LittleJS engine with your callback functions
111
- * @param {Function} gameInit - Called once after the engine starts up, setup the game
112
- * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
113
- * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
114
- * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
115
- * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
116
- * @param {Array} [imageSources=['tiles.png']] - Image to load
106
+ * @param {Function|function():Promise} gameInit - Called once after the engine starts up
107
+ * @param {Function} gameUpdate - Called every frame before objects are updated
108
+ * @param {Function} gameUpdatePost - Called after physics and objects are updated, even when paused
109
+ * @param {Function} gameRender - Called before objects are rendered, for drawing the background
110
+ * @param {Function} gameRenderPost - Called after objects are rendered, useful for drawing UI
111
+ * @param {Array} [imageSources=[]] - List of images to load
117
112
  * @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
118
113
  * @memberof Engine */
119
114
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
@@ -263,8 +258,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
263
258
 
264
259
  function startEngine()
265
260
  {
266
- gameInit();
267
- engineUpdate();
261
+ new Promise((resolve) => resolve(gameInit())).then(engineUpdate);
268
262
  }
269
263
 
270
264
  if (headlessMode)
@@ -279,7 +273,6 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
279
273
  'width:100vw;height:100vh;' + // fill the window
280
274
  'display:flex;' + // use flexbox
281
275
  'align-items:center;' + // horizontal center
282
- (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
283
276
  'justify-content:center;' + // vertical center
284
277
  'background:#000;' + // set background color
285
278
  'user-select:none;' + // prevent hold to select
@@ -287,9 +280,8 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
287
280
  (!touchInputEnable ? '' : // no touch css setttings
288
281
  'touch-action:none;' + // prevent mobile pinch to resize
289
282
  '-webkit-touch-callout:none');// compatibility for ios
290
- engineRoot = rootElement;
291
- engineRoot.style.cssText = styleRoot;
292
- engineRoot.appendChild(mainCanvas = document.createElement('canvas'));
283
+ rootElement.style.cssText = styleRoot;
284
+ rootElement.appendChild(mainCanvas = document.createElement('canvas'));
293
285
  mainContext = mainCanvas.getContext('2d');
294
286
 
295
287
  // init stuff and start engine
@@ -299,7 +291,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
299
291
  glInit();
300
292
 
301
293
  // create overlay canvas for hud to appear above gl canvas
302
- engineRoot.appendChild(overlayCanvas = document.createElement('canvas'));
294
+ rootElement.appendChild(overlayCanvas = document.createElement('canvas'));
303
295
  overlayContext = overlayCanvas.getContext('2d');
304
296
 
305
297
  // set canvas style
@@ -618,4 +610,4 @@ function drawEngineSplashScreen(t)
618
610
  }
619
611
 
620
612
  x.restore();
621
- }
613
+ }
@@ -14,7 +14,7 @@
14
14
  /** Audio context used by the engine
15
15
  * @type {AudioContext}
16
16
  * @memberof Audio */
17
- let audioContext;
17
+ let audioContext = new AudioContext;
18
18
 
19
19
  /** Master gain node for all audio to pass through
20
20
  * @type {GainNode}
@@ -25,14 +25,10 @@ function audioInit()
25
25
  {
26
26
  if (!soundEnable || headlessMode) return;
27
27
 
28
- // create audio context
29
- audioContext = new AudioContext;
30
-
31
- // create and connect gain node
32
28
  // (createGain is more widely spported then GainNode construtor)
33
29
  audioGainNode = audioContext.createGain();
34
30
  audioGainNode.connect(audioContext.destination);
35
- setSoundVolume(soundVolume); // update gain volume
31
+ audioGainNode.gain.value = soundVolume; // set starting value
36
32
  }
37
33
 
38
34
  ///////////////////////////////////////////////////////////////////////////////
@@ -67,12 +63,15 @@ class Sound
67
63
 
68
64
  /** @property {Number} - How much to randomize frequency each time sound plays */
69
65
  this.randomness = 0;
66
+
67
+ /** @property {GainNode} - Gain node for this sound */
68
+ this.gainNode = audioContext.createGain();
70
69
 
71
70
  if (zzfxSound)
72
71
  {
73
72
  // generate zzfx sound now for fast playback
74
73
  const defaultRandomness = .05;
75
- this.randomness = zzfxSound[1] || defaultRandomness;
74
+ this.randomness = zzfxSound[1] != undefined ? zzfxSound[1] : defaultRandomness;
76
75
  zzfxSound[1] = 0; // generate without randomness
77
76
  this.sampleChannels = [zzfxG(...zzfxSound)];
78
77
  this.sampleRate = zzfxR;
@@ -113,18 +112,13 @@ class Sound
113
112
 
114
113
  // play the sound
115
114
  const playbackRate = pitch + pitch * this.randomness*randomnessScale*rand(-1,1);
116
- this.gainNode = audioContext.createGain();
117
115
  return this.source = playSamples(this.sampleChannels, volume, playbackRate, pan, loop, this.sampleRate, this.gainNode);
118
116
  }
119
117
 
120
118
  /** Set the sound volume
121
119
  * @param {Number} [volume] - How much to scale volume by
122
120
  */
123
- setVolume(volume=1)
124
- {
125
- if (this.gainNode)
126
- this.gainNode.gain.value = volume;
127
- }
121
+ setVolume(volume=1) { this.gainNode.gain.value = volume; }
128
122
 
129
123
  /** Stop the last instance of this sound that was played */
130
124
  stop()
package/src/engineDraw.js CHANGED
@@ -70,7 +70,7 @@ let drawCount;
70
70
  * tile(2) // a tile at index 2 using the default tile size of 16
71
71
  * tile(5, 8) // a tile at index 5 using a tile size of 8
72
72
  * tile(1, 16, 3) // a tile at index 1 of size 16 on texture 3
73
- * tile(vec2(4,8), vec2(30,10)) // a tile at pixel location (4,8) with a size of (30,10)
73
+ * tile(vec2(4,8), vec2(30,10)) // a tile at index (4,8) with a size of (30,10)
74
74
  * @memberof Draw
75
75
  */
76
76
  function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
@@ -288,6 +288,22 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
288
288
  drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
289
289
  }
290
290
 
291
+ /** Draw colored line between two points
292
+ * @param {Vector2} posA
293
+ * @param {Vector2} posB
294
+ * @param {Number} [thickness]
295
+ * @param {Color} [color=(1,1,1,1)]
296
+ * @param {Boolean} [useWebGL=glEnable]
297
+ * @param {Boolean} [screenSpace=false]
298
+ * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
299
+ * @memberof Draw */
300
+ function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
301
+ {
302
+ const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
303
+ const size = vec2(thickness, halfDelta.length()*2);
304
+ drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace, context);
305
+ }
306
+
291
307
  /** Draw colored polygon using passed in points
292
308
  * @param {Array} points - Array of Vector2 points
293
309
  * @param {Color} [color=(1,1,1,1)]
@@ -356,22 +372,6 @@ function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth
356
372
  function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
357
373
  { drawEllipse(pos, radius, radius, 0, color, lineWidth, lineColor, screenSpace, context); }
358
374
 
359
- /** Draw colored line between two points
360
- * @param {Vector2} posA
361
- * @param {Vector2} posB
362
- * @param {Number} [thickness]
363
- * @param {Color} [color=(1,1,1,1)]
364
- * @param {Boolean} [useWebGL=glEnable]
365
- * @param {Boolean} [screenSpace=false]
366
- * @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context]
367
- * @memberof Draw */
368
- function drawLine(posA, posB, thickness=.1, color, useWebGL, screenSpace, context)
369
- {
370
- const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
371
- const size = vec2(thickness, halfDelta.length()*2);
372
- drawRect(posA.add(halfDelta), size, color, halfDelta.angle(), useWebGL, screenSpace, context);
373
- }
374
-
375
375
  /** Draw directly to a 2d canvas context in world space
376
376
  * @param {Vector2} pos
377
377
  * @param {Vector2} size
@@ -563,11 +563,12 @@ function isFullscreen() { return !!document.fullscreenElement; }
563
563
  * @memberof Draw */
564
564
  function toggleFullscreen()
565
565
  {
566
+ const rootElement = mainCanvas.parentElement;
566
567
  if (isFullscreen())
567
568
  {
568
569
  if (document.exitFullscreen)
569
570
  document.exitFullscreen();
570
571
  }
571
- else if (engineRoot.requestFullscreen)
572
- engineRoot.requestFullscreen();
572
+ else if (rootElement.requestFullscreen)
573
+ rootElement.requestFullscreen();
573
574
  }
@@ -191,6 +191,9 @@ export {
191
191
  drawTile,
192
192
  drawRect,
193
193
  drawLine,
194
+ drawPoly,
195
+ drawEllipse,
196
+ drawCircle,
194
197
  drawCanvas2D,
195
198
  setBlendMode,
196
199
  drawTextScreen,
@@ -211,6 +214,17 @@ export {
211
214
  glDraw,
212
215
  glFlush,
213
216
  glSetTexture,
217
+ glSetAntialias,
218
+ glAntialias,
219
+ glShader,
220
+ glActiveTexture,
221
+ glArrayBuffer,
222
+ glGeometryBuffer,
223
+ glPositionData,
224
+ glColorData,
225
+ glInstanceCount,
226
+ glAdditive,
227
+ glBatchAdditive,
214
228
 
215
229
  // Input
216
230
  keyIsDown,
@@ -45,7 +45,7 @@ function keyWasReleased(key, device=0)
45
45
 
46
46
  /** Clears all input
47
47
  * @memberof Input */
48
- function clearInput() { inputData = [[]]; }
48
+ function clearInput() { inputData = [[]]; touchGamepadButtons = []; }
49
49
 
50
50
  /** Returns true if mouse button is down
51
51
  * @function
@@ -167,7 +167,6 @@ function inputInit()
167
167
 
168
168
  onkeydown = (e)=>
169
169
  {
170
- if (debug && e.target != engineRoot) return;
171
170
  if (!e.repeat)
172
171
  {
173
172
  isUsingGamepad = false;
@@ -180,7 +179,6 @@ function inputInit()
180
179
 
181
180
  onkeyup = (e)=>
182
181
  {
183
- if (debug && e.target != engineRoot) return;
184
182
  inputData[0][e.code] = 4;
185
183
  if (inputWASDEmulateDirection)
186
184
  inputData[0][remapKey(e.code)] = 4;
@@ -212,6 +210,7 @@ function inputInit()
212
210
  onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
213
211
  onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
214
212
  oncontextmenu = (e)=> false; // prevent right click menu
213
+ onblur = (e) => clearInput(); // reset input when focus is lost
215
214
 
216
215
  // init touch input
217
216
  if (isTouchDevice && touchInputEnable)
@@ -382,7 +381,7 @@ function touchInputInit()
382
381
  // set event pos and pass it along
383
382
  const p = vec2(e.touches[0].clientX, e.touches[0].clientY);
384
383
  mousePosScreen = mouseToScreen(p);
385
- wasTouching ? isUsingGamepad = false : inputData[0][button] = 3;
384
+ wasTouching ? isUsingGamepad = touchGamepadEnable : inputData[0][button] = 3;
386
385
  }
387
386
  else if (wasTouching)
388
387
  inputData[0][button] = inputData[0][button] & 2 | 4;
@@ -410,10 +409,13 @@ function touchInputInit()
410
409
  if (touching)
411
410
  {
412
411
  touchGamepadTimer.set();
413
- if (paused)
412
+ if (paused && !wasTouching)
414
413
  {
415
414
  // touch anywhere to press start when paused
416
415
  touchGamepadButtons[9] = 1;
416
+
417
+ // call default touch handler so normal touch events still work
418
+ handleTouchDefault(e);
417
419
  return;
418
420
  }
419
421
  }
@@ -438,7 +440,7 @@ function touchInputInit()
438
440
  const button = touchPos.subtract(buttonCenter).direction();
439
441
  touchGamepadButtons[button] = 1;
440
442
  }
441
- else if (touchPos.distance(startCenter) < touchGamepadSize)
443
+ else if (touchPos.distance(startCenter) < touchGamepadSize && !wasTouching)
442
444
  {
443
445
  // virtual start button in center
444
446
  touchGamepadButtons[9] = 1;
@@ -28,7 +28,7 @@ function medalsInit(saveName)
28
28
  // check if medals are unlocked
29
29
  medalsSaveName = saveName;
30
30
  if (!debugMedals)
31
- medalsForEach(medal=> medal.unlocked = (localStorage[medal.storageKey()] | 0));
31
+ medalsForEach(medal=> medal.unlocked = !!localStorage[medal.storageKey()]);
32
32
 
33
33
  // engine automatically renders medals
34
34
  engineAddPlugin(undefined, medalsRender);
@@ -91,14 +91,28 @@ class Medal
91
91
  constructor(id, name, description='', icon='🏆', src)
92
92
  {
93
93
  ASSERT(id >= 0 && !medals[id]);
94
-
95
- // save attributes and add to list of medals
96
- medals[this.id = id] = this;
94
+
95
+ /** @property {Number} - The unique identifier of the medal */
96
+ this.id = id;
97
+
98
+ /** @property {String} - Name of the medal */
97
99
  this.name = name;
100
+
101
+ /** @property {String} - Description of the medal */
98
102
  this.description = description;
103
+
104
+ /** @property {String} - Icon for the medal */
99
105
  this.icon = icon;
106
+
107
+ /** @property {Boolean} - Is the medal unlocked? */
108
+ this.unlocked = false;
109
+
110
+ // load the source image if provided
100
111
  if (src)
101
112
  (this.image = new Image).src = src;
113
+
114
+ // add this to list of medals
115
+ medals[id] = this;
102
116
  }
103
117
 
104
118
  /** Unlocks a medal if not already unlocked */
@@ -109,7 +123,7 @@ class Medal
109
123
 
110
124
  // save the medal
111
125
  ASSERT(medalsSaveName, 'save name must be set');
112
- localStorage[this.storageKey()] = this.unlocked = 1;
126
+ localStorage[this.storageKey()] = this.unlocked = true;
113
127
  medalsDisplayQueue.push(this);
114
128
  }
115
129
 
@@ -39,7 +39,7 @@ class EngineObject
39
39
  * @param {Color} [color=(1,1,1,1)] - Color to apply to tile when rendered
40
40
  * @param {Number} [renderOrder] - Objects sorted by renderOrder before being rendered
41
41
  */
42
- constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color, renderOrder=0)
42
+ constructor(pos=vec2(), size=vec2(1), tileInfo, angle=0, color=new Color, renderOrder=0)
43
43
  {
44
44
  // set passed in params
45
45
  ASSERT(isVector2(pos) && isVector2(size), 'ensure pos and size are vec2s');
@@ -153,15 +153,18 @@ class EngineObject
153
153
 
154
154
  // apply physics
155
155
  const oldPos = this.pos.copy();
156
- this.pos.x += this.velocity.x *= this.damping;
157
- this.pos.y += this.velocity.y = this.damping * this.velocity.y
158
- + gravity * this.gravityScale;
156
+ this.velocity.x *= this.damping;
157
+ this.velocity.y *= this.damping;
158
+ if (this.mass) // dont apply gravity to static objects
159
+ this.velocity.y += gravity * this.gravityScale;
160
+ this.pos.x += this.velocity.x;
161
+ this.pos.y += this.velocity.y;
159
162
  this.angle += this.angleVelocity *= this.angleDamping;
160
163
 
161
164
  // physics sanity checks
162
165
  ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
163
166
  ASSERT(this.damping >= 0 && this.damping <= 1);
164
- if (!enablePhysicsSolver || !this.mass) // dont do collision for fixed objects
167
+ if (!enablePhysicsSolver || !this.mass) // dont do collision for static objects
165
168
  return;
166
169
 
167
170
  const wasMovingDown = this.velocity.y < 0;
@@ -307,6 +310,7 @@ class EngineObject
307
310
  this.pos.x = oldPos.x;
308
311
  this.velocity.x *= -this.elasticity;
309
312
  }
313
+ debugOverlay && debugPhysics && debugRect(this.pos, this.size, '#f00');
310
314
  }
311
315
  }
312
316
  }
@@ -12,18 +12,18 @@
12
12
 
13
13
  'use strict';
14
14
 
15
- /** The tile collision layer array, use setTileCollisionData and getTileCollisionData to access
15
+ /** The tile collision layer grid, use setTileCollisionData and getTileCollisionData to access
16
16
  * @type {Array}
17
17
  * @memberof TileCollision */
18
18
  let tileCollision = [];
19
19
 
20
- /** Size of the tile collision layer
20
+ /** Size of the tile collision layer 2d grid
21
21
  * @type {Vector2}
22
22
  * @memberof TileCollision */
23
23
  let tileCollisionSize = vec2();
24
24
 
25
25
  /** Clear and initialize tile collision
26
- * @param {Vector2} size
26
+ * @param {Vector2} size - width and height of tile collision 2d grid
27
27
  * @memberof TileCollision */
28
28
  function initTileCollision(size)
29
29
  {
@@ -33,7 +33,7 @@ function initTileCollision(size)
33
33
  tileCollision[i] = 0;
34
34
  }
35
35
 
36
- /** Set tile collision data
36
+ /** Set tile collision data for a given cell in the grid
37
37
  * @param {Vector2} pos
38
38
  * @param {Number} [data]
39
39
  * @memberof TileCollision */
@@ -42,7 +42,7 @@ function setTileCollisionData(pos, data=0)
42
42
  pos.arrayCheck(tileCollisionSize) && (tileCollision[(pos.y|0)*tileCollisionSize.x+pos.x|0] = data);
43
43
  }
44
44
 
45
- /** Get tile collision data
45
+ /** Get tile collision data for a given cell in the grid
46
46
  * @param {Vector2} pos
47
47
  * @return {Number}
48
48
  * @memberof TileCollision */
@@ -70,9 +70,11 @@ function tileCollisionTest(pos, size=vec2(), object)
70
70
  if (tileData && (!object || object.collideWithTile(tileData, vec2(x, y))))
71
71
  return true;
72
72
  }
73
+ return false;
73
74
  }
74
75
 
75
- /** Return the center of first tile hit (does not return the exact intersection)
76
+ /** Return the center of first tile hit, undefined if nothing was hit.
77
+ * This does not return the exact intersection, but the center of the tile hit.
76
78
  * @param {Vector2} posStart
77
79
  * @param {Vector2} posEnd
78
80
  * @param {EngineObject} [object]
@@ -93,7 +95,7 @@ function tileCollisionRaycast(posStart, posEnd, object)
93
95
  let xi = unit.x * (delta.x < 0 ? posStart.x - pos.x : pos.x - posStart.x + 1);
94
96
  let yi = unit.y * (delta.y < 0 ? posStart.y - pos.y : pos.y - posStart.y + 1);
95
97
 
96
- while (1)
98
+ while (true)
97
99
  {
98
100
  // check for tile collision
99
101
  const tileData = getTileCollisionData(pos);
@@ -317,8 +319,8 @@ class TileLayer extends EngineObject
317
319
  const d = this.getData(layerPos);
318
320
  if (d.tile != undefined)
319
321
  {
320
- const pos = this.pos.add(layerPos).add(vec2(.5));
321
322
  ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
323
+ const pos = layerPos.add(vec2(.5));
322
324
  const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex);
323
325
  drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
324
326
  }
@@ -201,7 +201,8 @@ function formatTime(t) { return (t/60|0) + ':' + (t%60<10?'0':'') + (t%60|0); }
201
201
  * @memberof Random */
202
202
  function rand(valueA=1, valueB=0) { return valueB + Math.random() * (valueA-valueB); }
203
203
 
204
- /** Returns a floored random value the two values passed in
204
+ /** Returns a floored random value between the two values passed in
205
+ * The upper bound is exclusive. (If 2 is passed in, result will be 0 or 1)
205
206
  * @param {Number} valueA
206
207
  * @param {Number} [valueB]
207
208
  * @return {Number}
@@ -330,18 +331,24 @@ class Vector2
330
331
  * @param {Number} [y] - Y axis location */
331
332
  constructor(x=0, y=0)
332
333
  {
333
- ASSERT(typeof x == 'number' && typeof y == 'number');
334
334
  /** @property {Number} - X axis location */
335
335
  this.x = x;
336
336
  /** @property {Number} - Y axis location */
337
337
  this.y = y;
338
+ ASSERT(this.isValid());
338
339
  }
339
340
 
340
341
  /** Sets values of this vector and returns self
341
342
  * @param {Number} [x] - X axis location
342
343
  * @param {Number} [y] - Y axis location
343
344
  * @return {Vector2} */
344
- set(x=0, y=0) { this.x=x; this.y=y; return this; }
345
+ set(x=0, y=0)
346
+ {
347
+ this.x = x;
348
+ this.y = y;
349
+ ASSERT(this.isValid());
350
+ return this;
351
+ }
345
352
 
346
353
  /** Returns a new vector that is a copy of this
347
354
  * @return {Vector2} */
@@ -483,6 +490,7 @@ class Vector2
483
490
  * @param {Number} [length] */
484
491
  setDirection(direction, length=1)
485
492
  {
493
+ direction = mod(direction, 4);
486
494
  ASSERT(direction==0 || direction==1 || direction==2 || direction==3);
487
495
  return vec2(direction%2 ? direction-1 ? -length : length : 0,
488
496
  direction%2 ? 0 : direction ? -length : length);
@@ -532,6 +540,14 @@ class Vector2
532
540
  if (debug)
533
541
  return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
534
542
  }
543
+
544
+ /** Checks if this is a valid vector
545
+ * @return {Boolean} */
546
+ isValid()
547
+ {
548
+ return typeof this.x == 'number' && !isNaN(this.x)
549
+ && typeof this.y == 'number' && !isNaN(this.y);
550
+ }
535
551
  }
536
552
 
537
553
  ///////////////////////////////////////////////////////////////////////////////
@@ -592,6 +608,7 @@ class Color
592
608
  this.b = b;
593
609
  /** @property {Number} - Alpha */
594
610
  this.a = a;
611
+ ASSERT(this.isValid());
595
612
  }
596
613
 
597
614
  /** Sets values of this color and returns self
@@ -601,7 +618,14 @@ class Color
601
618
  * @param {Number} [a] - alpha
602
619
  * @return {Color} */
603
620
  set(r=1, g=1, b=1, a=1)
604
- { this.r=r; this.g=g; this.b=b; this.a=a; return this; }
621
+ {
622
+ this.r = r;
623
+ this.g = g;
624
+ this.b = b;
625
+ this.a = a;
626
+ ASSERT(this.isValid());
627
+ return this;
628
+ }
605
629
 
606
630
  /** Returns a new color that is a copy of this
607
631
  * @return {Color} */
@@ -684,6 +708,7 @@ class Color
684
708
  this.g = f(p, q, h);
685
709
  this.b = f(p, q, h - 1/3);
686
710
  this.a = a;
711
+ ASSERT(this.isValid());
687
712
  return this;
688
713
  }
689
714
 
@@ -711,7 +736,6 @@ class Color
711
736
  else if (b == max)
712
737
  h = (r - g) / d + 4;
713
738
  }
714
-
715
739
  return [h / 6, s, l, a];
716
740
  }
717
741
 
@@ -744,11 +768,27 @@ class Color
744
768
  * @return {Color} */
745
769
  setHex(hex)
746
770
  {
747
- const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
748
- this.r = fromHex(1);
749
- this.g = fromHex(3),
750
- this.b = fromHex(5);
751
- this.a = hex.length > 7 ? fromHex(7) : 1;
771
+ ASSERT(typeof hex == 'string' && hex[0] == '#');
772
+ ASSERT([4,5,7,9].includes(hex.length), 'Invalid hex');
773
+
774
+ if (hex.length < 6)
775
+ {
776
+ const fromHex = (c)=> clamp(parseInt(hex[c],16)/15);
777
+ this.r = fromHex(1);
778
+ this.g = fromHex(2),
779
+ this.b = fromHex(3);
780
+ this.a = hex.length == 5 ? fromHex(4) : 1;
781
+ }
782
+ else
783
+ {
784
+ const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
785
+ this.r = fromHex(1);
786
+ this.g = fromHex(3),
787
+ this.b = fromHex(5);
788
+ this.a = hex.length == 9 ? fromHex(7) : 1;
789
+ }
790
+
791
+ ASSERT(this.isValid());
752
792
  return this;
753
793
  }
754
794
 
@@ -762,6 +802,16 @@ class Color
762
802
  const a = clamp(this.a)*255<<24;
763
803
  return r + g + b + a;
764
804
  }
805
+
806
+ /** Checks if this is a valid color
807
+ * @return {Boolean} */
808
+ isValid()
809
+ {
810
+ return typeof this.r == 'number' && !isNaN(this.r)
811
+ && typeof this.g == 'number' && !isNaN(this.g)
812
+ && typeof this.b == 'number' && !isNaN(this.b)
813
+ && typeof this.a == 'number' && !isNaN(this.a);
814
+ }
765
815
  }
766
816
 
767
817
  ///////////////////////////////////////////////////////////////////////////////
@@ -22,6 +22,11 @@ let glCanvas;
22
22
  * @memberof WebGL */
23
23
  let glContext;
24
24
 
25
+ /** Shoule webgl be setup with antialiasing, must be set before calling engineInit
26
+ * @type {Boolean}
27
+ * @memberof WebGL */
28
+ let glAntialias = true;
29
+
25
30
  // WebGL internal variables not exposed to documentation
26
31
  let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
27
32
 
@@ -34,10 +39,11 @@ function glInit()
34
39
 
35
40
  // create the canvas and textures
36
41
  glCanvas = document.createElement('canvas');
37
- glContext = glCanvas.getContext('webgl2', {antialias:!canvasPixelated});
42
+ glContext = glCanvas.getContext('webgl2', {antialias:glAntialias});
38
43
 
39
44
  // some browsers are much faster without copying the gl buffer so we just overlay it instead
40
- glOverlay && engineRoot.appendChild(glCanvas);
45
+ const rootElement = mainCanvas.parentElement;
46
+ glOverlay && rootElement.appendChild(glCanvas);
41
47
 
42
48
  // setup vertex and fragment shaders
43
49
  glShader = glCreateProgram(
@@ -241,6 +247,15 @@ function glCopyToContext(context, forceDraw=false)
241
247
  context.drawImage(glCanvas, 0, 0);
242
248
  }
243
249
 
250
+ /** Set antialiasing for webgl canvas
251
+ * @param {Boolean} [antialias]
252
+ * @memberof WebGL */
253
+ function glSetAntialias(antialias=true)
254
+ {
255
+ ASSERT(!glCanvas, 'must be called before engineInit');
256
+ glAntialias = antialias;
257
+ }
258
+
244
259
  /** Add a sprite to the gl draw list, used by all gl draw functions
245
260
  * @param {Number} x
246
261
  * @param {Number} y