littlejsengine 1.10.4 → 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 (63) hide show
  1. package/README.md +8 -78
  2. package/dist/littlejs.d.ts +31 -17
  3. package/dist/littlejs.esm.js +121 -55
  4. package/dist/littlejs.esm.min.js +1 -1
  5. package/dist/littlejs.js +121 -55
  6. package/dist/littlejs.min.js +1 -1
  7. package/dist/littlejs.release.js +121 -55
  8. package/examples/box2d/game.js +15 -4
  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/index.html +2 -2
  15. package/examples/electron/game.js +3 -0
  16. package/examples/electron/index.html +2 -2
  17. package/examples/htmlMenu/index.html +3 -3
  18. package/examples/js13k/game.js +3 -0
  19. package/examples/js13k/index.html +13 -13
  20. package/examples/module/game.js +3 -0
  21. package/examples/module/index.html +1 -1
  22. package/examples/particles/index.html +4 -1
  23. package/examples/platformer/game.js +19 -15
  24. package/examples/platformer/gameCharacter.js +3 -3
  25. package/examples/platformer/gameLevel.js +1 -0
  26. package/examples/platformer/index.html +8 -8
  27. package/examples/platformer/tiles.png +0 -0
  28. package/examples/puzzle/index.html +2 -2
  29. package/examples/starter/game.js +3 -0
  30. package/examples/starter/index.html +13 -13
  31. package/examples/stress/index.html +1 -1
  32. package/examples/typescript/game.js +2 -0
  33. package/examples/typescript/game.ts +3 -0
  34. package/examples/typescript/index.html +1 -1
  35. package/examples/uiSystem/game.js +23 -16
  36. package/examples/uiSystem/index.html +3 -14
  37. package/package.json +1 -1
  38. package/plugins/postProcess.js +2 -9
  39. package/plugins/uiSystem.js +84 -24
  40. package/src/engine.js +11 -11
  41. package/src/engineAudio.js +7 -13
  42. package/src/engineDraw.js +1 -1
  43. package/src/engineInput.js +7 -3
  44. package/src/engineMedals.js +19 -5
  45. package/src/engineObject.js +8 -5
  46. package/src/engineTileLayer.js +8 -7
  47. package/src/engineUtilities.js +59 -10
  48. package/examples/js13k/build/index.html +0 -1
  49. package/examples/js13k/build/index.js +0 -1
  50. package/examples/js13k/build/tiles.png +0 -0
  51. package/examples/js13k/game - Copy.zip +0 -0
  52. package/examples/js13k/game.zip +0 -0
  53. package/examples/starter/build/index.html +0 -2
  54. package/examples/starter/build/index.js +0 -1
  55. package/examples/starter/build/tiles.png +0 -0
  56. package/examples/starter/game.zip +0 -0
  57. package/examples/typescript/build/build/littlejs.esm.js +0 -4327
  58. package/examples/typescript/build/dist/littlejs.esm.js +0 -4425
  59. package/examples/typescript/build/examples/typescript/build.js +0 -24
  60. package/examples/typescript/build/examples/typescript/game.js +0 -100
  61. package/examples/typescript/build/examples/typescript/test/build/littlejs.esm.js +0 -3934
  62. package/examples/typescript/build/examples/typescript/test/examples/typescript/build.js +0 -86
  63. package/examples/typescript/build/examples/typescript/test/examples/typescript/game.js +0 -92
@@ -40,7 +40,7 @@ function initUISystem(context=overlayContext)
40
40
  o.pos = o.localPos.add(o.parent.pos);
41
41
  o.update();
42
42
  for(const c of o.children)
43
- updateObject(c)
43
+ updateObject(c);
44
44
  }
45
45
  uiObjects.forEach(o=> o.parent || updateObject(o));
46
46
  }
@@ -50,9 +50,11 @@ function initUISystem(context=overlayContext)
50
50
  {
51
51
  if (!o.visible)
52
52
  return;
53
+ if (o.parent)
54
+ o.pos = o.localPos.add(o.parent.pos);
53
55
  o.render();
54
56
  for(const c of o.children)
55
- renderObject(c)
57
+ renderObject(c);
56
58
  }
57
59
  uiObjects.forEach(o=> o.parent || renderObject(o));
58
60
  }
@@ -82,9 +84,9 @@ function drawUILine(posA, posB, thickness=uiDefaultLineWidth, color=uiDefaultLin
82
84
  uiContext.stroke();
83
85
  }
84
86
 
85
- function drawUITile(pos, size, tileInfo, color=uiDefaultColor, angle=0, mirror=false, additiveColor=BLACK)
87
+ function drawUITile(pos, size, tileInfo, color=uiDefaultColor, angle=0, mirror=false)
86
88
  {
87
- drawTile(pos, size, tileInfo, color, angle, mirror, additiveColor, false, true, uiContext);
89
+ drawTile(pos, size, tileInfo, color, angle, mirror, BLACK, false, true, uiContext);
88
90
  }
89
91
 
90
92
  function drawUIText(text, pos, size, color=uiDefaultColor, lineWidth=uiDefaultLineWidth, lineColor=uiDefaultLineColor, align='center', font=uiDefaultFont)
@@ -96,7 +98,7 @@ function drawUIText(text, pos, size, color=uiDefaultColor, lineWidth=uiDefaultLi
96
98
 
97
99
  class UIObject
98
100
  {
99
- constructor(localPos, size=vec2())
101
+ constructor(localPos=vec2(), size=vec2())
100
102
  {
101
103
  this.localPos = localPos.copy();
102
104
  this.pos = localPos.copy();
@@ -131,21 +133,28 @@ class UIObject
131
133
  {
132
134
  // track mouse input
133
135
  const mouseWasOver = this.mouseIsOver;
134
- this.mouseIsOver = isOverlapping(this.pos, this.size, mousePosScreen);
135
- if (this.mouseIsOver && !mouseWasOver)
136
- this.onEnter();
137
- if (!this.mouseIsOver && mouseWasOver)
138
- this.onLeave();
136
+ const mouseDown = mouseIsDown(0);
137
+ if (!mouseDown || isTouchDevice)
138
+ {
139
+ this.mouseIsOver = isOverlapping(this.pos, this.size, mousePosScreen);
140
+ if (!mouseDown && isTouchDevice)
141
+ this.mouseIsOver = false;
142
+ if (this.mouseIsOver && !mouseWasOver)
143
+ this.onEnter();
144
+ if (!this.mouseIsOver && mouseWasOver)
145
+ this.onLeave();
146
+ }
139
147
  if (mouseWasPressed(0) && this.mouseIsOver)
140
148
  {
141
149
  this.mouseIsHeld = true;
142
150
  this.onPress();
151
+ if (isTouchDevice)
152
+ this.mouseIsOver = false;
143
153
  }
144
- else if (this.mouseIsHeld && !mouseIsDown(0))
154
+ else if (this.mouseIsHeld && !mouseDown)
145
155
  {
146
156
  this.mouseIsHeld = false;
147
- if (this.mouseIsOver)
148
- this.onClick();
157
+ this.onRelease();
149
158
  }
150
159
  }
151
160
  render()
@@ -155,17 +164,18 @@ class UIObject
155
164
  }
156
165
 
157
166
  // callback functions
158
- onEnter() {}
159
- onLeave() {}
160
- onPress() {}
161
- onClick() {}
167
+ onEnter() {}
168
+ onLeave() {}
169
+ onPress() {}
170
+ onRelease() {}
171
+ onChange() {}
162
172
  }
163
173
 
164
174
  ///////////////////////////////////////////////////////////////////////////////
165
175
 
166
176
  class UIText extends UIObject
167
177
  {
168
- constructor(pos, size, text, align='center', font=fontDefault)
178
+ constructor(pos, size, text='', align='center', font=fontDefault)
169
179
  {
170
180
  super(pos, size);
171
181
 
@@ -184,7 +194,7 @@ class UIText extends UIObject
184
194
 
185
195
  class UITile extends UIObject
186
196
  {
187
- constructor(pos, size, tileInfo, color=WHITE, angle=0, mirror=false, additiveColor=BLACK)
197
+ constructor(pos, size, tileInfo, color=WHITE, angle=0, mirror=false)
188
198
  {
189
199
  super(pos, size);
190
200
 
@@ -192,11 +202,10 @@ class UITile extends UIObject
192
202
  this.color = color;
193
203
  this.angle = angle;
194
204
  this.mirror = mirror;
195
- this.additiveColor = additiveColor;
196
205
  }
197
206
  render()
198
207
  {
199
- drawUITile(this.pos, this.size, this.tileInfo, this.color, this.angle, this.mirror, this.additiveColor);
208
+ drawUITile(this.pos, this.size, this.tileInfo, this.color, this.angle, this.mirror);
200
209
  }
201
210
  }
202
211
 
@@ -215,8 +224,9 @@ class UIButton extends UIObject
215
224
  const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
216
225
  const color = this.mouseIsOver? this.hoverColor : this.color;
217
226
  drawUIRect(this.pos, this.size, color, this.lineWidth, lineColor);
218
- drawTextScreen(this.text, this.pos, this.size.y*.8,
219
- this.textColor, undefined, undefined, this.align, this.font, uiContext);
227
+ const textSize = vec2(this.size.x, this.size.y*.8);
228
+ drawUIText(this.text, this.pos, textSize,
229
+ this.textColor, 0, undefined, this.align, this.font);
220
230
  }
221
231
  }
222
232
 
@@ -229,7 +239,11 @@ class UICheckbox extends UIObject
229
239
  super(pos, size);
230
240
  this.checked = checked;
231
241
  }
232
- onClick() { this.checked = !this.checked; }
242
+ onPress()
243
+ {
244
+ this.checked = !this.checked;
245
+ this.onChange();
246
+ }
233
247
  render()
234
248
  {
235
249
  drawUIRect(this.pos, this.size, this.color, this.lineWidth, this.lineColor);
@@ -241,3 +255,49 @@ class UICheckbox extends UIObject
241
255
  }
242
256
  }
243
257
  }
258
+
259
+ ///////////////////////////////////////////////////////////////////////////////
260
+
261
+ class UIScrollbar extends UIObject
262
+ {
263
+ constructor(pos, size, value=.5, text='')
264
+ {
265
+ super(pos, size);
266
+ this.value = value;
267
+ this.text = text;
268
+ this.color = uiDefaultButtonColor;
269
+ this.handleColor = WHITE;
270
+ }
271
+ update()
272
+ {
273
+ super.update();
274
+ if (this.mouseIsHeld)
275
+ {
276
+ const handleSize = vec2(this.size.y);
277
+ const handleWidth = this.size.x - handleSize.x;
278
+ const p1 = this.pos.x - handleWidth/2;
279
+ const p2 = this.pos.x + handleWidth/2;
280
+ const oldValue = this.value;
281
+ this.value = percent(mousePosScreen.x, p1, p2);
282
+ this.value == oldValue || this.onChange();
283
+ }
284
+ }
285
+ render()
286
+ {
287
+ const lineColor = this.mouseIsHeld ? this.color : this.lineColor;
288
+ const color = this.mouseIsOver? this.hoverColor : this.color;
289
+ drawUIRect(this.pos, this.size, color, this.lineWidth, lineColor);
290
+
291
+ const handleSize = vec2(this.size.y);
292
+ const handleWidth = this.size.x - handleSize.x;
293
+ const p1 = this.pos.x - handleWidth/2;
294
+ const p2 = this.pos.x + handleWidth/2;
295
+ const handlePos = vec2(lerp(this.value, p1, p2), this.pos.y);
296
+ const barColor = this.mouseIsHeld ? this.color : this.handleColor;
297
+ drawUIRect(handlePos, handleSize, barColor, this.lineWidth, this.lineColor);
298
+
299
+ const textSize = vec2(this.size.x, this.size.y*.8);
300
+ drawUIText(this.text, this.pos, textSize,
301
+ this.textColor, 0, undefined, this.align, this.font);
302
+ }
303
+ }
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.4';
33
+ const engineVersion = '1.10.7';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {Number}
@@ -93,6 +93,8 @@ const pluginUpdateList = [], pluginRenderList = [];
93
93
  * @memberof Engine */
94
94
  function engineAddPlugin(updateFunction, renderFunction)
95
95
  {
96
+ ASSERT(!pluginUpdateList.includes(updateFunction));
97
+ ASSERT(!pluginRenderList.includes(renderFunction));
96
98
  updateFunction && pluginUpdateList.push(updateFunction);
97
99
  renderFunction && pluginRenderList.push(renderFunction);
98
100
  }
@@ -101,12 +103,12 @@ function engineAddPlugin(updateFunction, renderFunction)
101
103
  // Main engine functions
102
104
 
103
105
  /** Startup LittleJS engine with your callback functions
104
- * @param {Function} gameInit - Called once after the engine starts up, setup the game
105
- * @param {Function} gameUpdate - Called every frame at 60 frames per second, handle input and update the game state
106
- * @param {Function} gameUpdatePost - Called after physics and objects are updated, setup camera and prepare for render
107
- * @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
108
- * @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
109
- * @param {Array} [imageSources=[]] - 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
110
112
  * @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
111
113
  * @memberof Engine */
112
114
  function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
@@ -256,8 +258,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
256
258
 
257
259
  function startEngine()
258
260
  {
259
- gameInit();
260
- engineUpdate();
261
+ new Promise((resolve) => resolve(gameInit())).then(engineUpdate);
261
262
  }
262
263
 
263
264
  if (headlessMode)
@@ -272,7 +273,6 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
272
273
  'width:100vw;height:100vh;' + // fill the window
273
274
  'display:flex;' + // use flexbox
274
275
  'align-items:center;' + // horizontal center
275
- (canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
276
276
  'justify-content:center;' + // vertical center
277
277
  'background:#000;' + // set background color
278
278
  'user-select:none;' + // prevent hold to select
@@ -610,4 +610,4 @@ function drawEngineSplashScreen(t)
610
610
  }
611
611
 
612
612
  x.restore();
613
- }
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)
@@ -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
@@ -210,6 +210,7 @@ function inputInit()
210
210
  onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
211
211
  onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
212
212
  oncontextmenu = (e)=> false; // prevent right click menu
213
+ onblur = (e) => clearInput(); // reset input when focus is lost
213
214
 
214
215
  // init touch input
215
216
  if (isTouchDevice && touchInputEnable)
@@ -408,10 +409,13 @@ function touchInputInit()
408
409
  if (touching)
409
410
  {
410
411
  touchGamepadTimer.set();
411
- if (paused)
412
+ if (paused && !wasTouching)
412
413
  {
413
414
  // touch anywhere to press start when paused
414
415
  touchGamepadButtons[9] = 1;
416
+
417
+ // call default touch handler so normal touch events still work
418
+ handleTouchDefault(e);
415
419
  return;
416
420
  }
417
421
  }
@@ -436,7 +440,7 @@ function touchInputInit()
436
440
  const button = touchPos.subtract(buttonCenter).direction();
437
441
  touchGamepadButtons[button] = 1;
438
442
  }
439
- else if (touchPos.distance(startCenter) < touchGamepadSize)
443
+ else if (touchPos.distance(startCenter) < touchGamepadSize && !wasTouching)
440
444
  {
441
445
  // virtual start button in center
442
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;
@@ -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 */
@@ -73,7 +73,8 @@ function tileCollisionTest(pos, size=vec2(), object)
73
73
  return false;
74
74
  }
75
75
 
76
- /** 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.
77
78
  * @param {Vector2} posStart
78
79
  * @param {Vector2} posEnd
79
80
  * @param {EngineObject} [object]
@@ -318,8 +319,8 @@ class TileLayer extends EngineObject
318
319
  const d = this.getData(layerPos);
319
320
  if (d.tile != undefined)
320
321
  {
321
- const pos = this.pos.add(layerPos).add(vec2(.5));
322
322
  ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
323
+ const pos = layerPos.add(vec2(.5));
323
324
  const tileInfo = tile(d.tile, s, this.tileInfo.textureIndex);
324
325
  drawTile(pos, vec2(1), tileInfo, d.color, d.direction*PI/2, d.mirror);
325
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} */
@@ -533,6 +540,14 @@ class Vector2
533
540
  if (debug)
534
541
  return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`;
535
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
+ }
536
551
  }
537
552
 
538
553
  ///////////////////////////////////////////////////////////////////////////////
@@ -593,6 +608,7 @@ class Color
593
608
  this.b = b;
594
609
  /** @property {Number} - Alpha */
595
610
  this.a = a;
611
+ ASSERT(this.isValid());
596
612
  }
597
613
 
598
614
  /** Sets values of this color and returns self
@@ -602,7 +618,14 @@ class Color
602
618
  * @param {Number} [a] - alpha
603
619
  * @return {Color} */
604
620
  set(r=1, g=1, b=1, a=1)
605
- { 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
+ }
606
629
 
607
630
  /** Returns a new color that is a copy of this
608
631
  * @return {Color} */
@@ -685,6 +708,7 @@ class Color
685
708
  this.g = f(p, q, h);
686
709
  this.b = f(p, q, h - 1/3);
687
710
  this.a = a;
711
+ ASSERT(this.isValid());
688
712
  return this;
689
713
  }
690
714
 
@@ -712,7 +736,6 @@ class Color
712
736
  else if (b == max)
713
737
  h = (r - g) / d + 4;
714
738
  }
715
-
716
739
  return [h / 6, s, l, a];
717
740
  }
718
741
 
@@ -745,11 +768,27 @@ class Color
745
768
  * @return {Color} */
746
769
  setHex(hex)
747
770
  {
748
- const fromHex = (c)=> clamp(parseInt(hex.slice(c,c+2),16)/255);
749
- this.r = fromHex(1);
750
- this.g = fromHex(3),
751
- this.b = fromHex(5);
752
- 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());
753
792
  return this;
754
793
  }
755
794
 
@@ -763,6 +802,16 @@ class Color
763
802
  const a = clamp(this.a)*255<<24;
764
803
  return r + g + b + a;
765
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
+ }
766
815
  }
767
816
 
768
817
  ///////////////////////////////////////////////////////////////////////////////