littlejsengine 1.1.17 → 1.2.0

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.
@@ -32,7 +32,7 @@ const keyWasReleased = (key, device=0)=> inputData[device] && inputData[device][
32
32
 
33
33
  /** Clears all input
34
34
  * @memberof Input */
35
- const clearInput = ()=> inputData[0] = [];
35
+ const clearInput = ()=> inputData = [[]];
36
36
 
37
37
  /** Returns true if mouse button is down
38
38
  * @param {Number} button
@@ -101,7 +101,9 @@ const gamepadStick = (stick, gamepad=0)=> stickData[gamepad] ? stickData[gamepa
101
101
  ///////////////////////////////////////////////////////////////////////////////
102
102
  // Input update called by engine
103
103
 
104
- const inputData = [[]];
104
+ // store input as a bit field for each key: 1 = isDown, 2 = wasPressed, 4 = wasReleased
105
+ // mouse and keyboard are stored together in device 0, gamepads are in devices > 0
106
+ let inputData = [[]];
105
107
 
106
108
  function inputUpdate()
107
109
  {
@@ -127,36 +129,38 @@ function inputUpdatePost()
127
129
  ///////////////////////////////////////////////////////////////////////////////
128
130
  // Keyboard event handlers
129
131
 
130
- onkeydown = e=>
132
+ onkeydown = (e)=>
131
133
  {
132
134
  if (debug && e.target != document.body) return;
133
135
  e.repeat || (inputData[isUsingGamepad = 0][remapKeyCode(e.keyCode)] = 3);
134
136
  debug || e.preventDefault();
135
137
  }
136
- onkeyup = e=>
138
+ onkeyup = (e)=>
137
139
  {
138
140
  if (debug && e.target != document.body) return;
139
141
  inputData[0][remapKeyCode(e.keyCode)] = 4;
140
142
  }
141
- const remapKeyCode = c=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
143
+ const remapKeyCode = (c)=> inputWASDEmulateDirection ? c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
142
144
 
143
145
  ///////////////////////////////////////////////////////////////////////////////
144
146
  // Mouse event handlers
145
147
 
146
- onmousedown = e=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
147
- onmouseup = e=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
148
- onmousemove = e=>
148
+ onmousedown = (e)=> {inputData[isUsingGamepad = 0][e.button] = 3; onmousemove(e); e.button && e.preventDefault();}
149
+ onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
150
+ onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
151
+ onwheel = (e)=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
152
+ oncontextmenu = (e)=> !1; // prevent right click menu
153
+
154
+ // convert a mouse or touch event position to screen space
155
+ const mouseToScreen = (mousePos)=>
149
156
  {
150
157
  if (!mainCanvas)
151
- return; // fix bug that can occur if user clicks before page loads
158
+ return vec2(); // fix bug that can occur if user clicks before page loads
152
159
 
153
- // convert mouse pos to canvas space
154
160
  const rect = mainCanvas.getBoundingClientRect();
155
- mousePosScreen = mainCanvasSize.multiply(
156
- vec2(percent(e.x, rect.right, rect.left), percent(e.y, rect.bottom, rect.top)));
161
+ return mainCanvasSize.multiply(
162
+ vec2(percent(mousePos.x, rect.left, rect.right), percent(mousePos.y, rect.top, rect.bottom)));
157
163
  }
158
- onwheel = e=> e.ctrlKey || (mouseWheel = sign(e.deltaY));
159
- oncontextmenu = e=> !1; // prevent right click menu
160
164
 
161
165
  ///////////////////////////////////////////////////////////////////////////////
162
166
  // Gamepad input
@@ -164,6 +168,21 @@ oncontextmenu = e=> !1; // prevent right click menu
164
168
  const stickData = [];
165
169
  function gamepadsUpdate()
166
170
  {
171
+ if (touchGamepadEnable && touchGamepadTimer.isSet())
172
+ {
173
+ // read virtual analog stick
174
+ const sticks = stickData[0] || (stickData[0] = []);
175
+ sticks[0] = vec2(touchGamepadStick.x, -touchGamepadStick.y); // flip vertical
176
+
177
+ // read virtual gamepad buttons
178
+ const data = inputData[1] || (inputData[1] = []);
179
+ for (let i=10; i--;)
180
+ {
181
+ const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
182
+ data[j] = touchGamepadButtons[i] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
183
+ }
184
+ }
185
+
167
186
  if (!gamepadsEnable || !navigator.getGamepads || !document.hasFocus() && !debug)
168
187
  return;
169
188
 
@@ -181,8 +200,8 @@ function gamepadsUpdate()
181
200
  // read clamp dead zone of analog sticks
182
201
  const deadZone = .3, deadZoneMax = .8;
183
202
  const applyDeadZone = (v)=>
184
- v > deadZone ? percent( v, deadZoneMax, deadZone) :
185
- v < -deadZone ? -percent(-v, deadZoneMax, deadZone) : 0;
203
+ v > deadZone ? percent( v, deadZone, deadZoneMax) :
204
+ v < -deadZone ? -percent(-v, deadZone, deadZoneMax) : 0;
186
205
 
187
206
  // read analog sticks
188
207
  for (let j = 0; j < gamepad.axes.length-1; j+=2)
@@ -194,6 +213,7 @@ function gamepadsUpdate()
194
213
  const button = gamepad.buttons[j];
195
214
  data[j] = button.pressed ? 1 + 2*!gamepadIsDown(j,i) : 4*gamepadIsDown(j,i);
196
215
  isUsingGamepad |= !i && button.pressed;
216
+ touchGamepadEnable && touchGamepadTimer.unset(); // disable touch gamepad if using real gamepad
197
217
  }
198
218
 
199
219
  if (gamepadDirectionEmulateStick)
@@ -207,6 +227,17 @@ function gamepadsUpdate()
207
227
  }
208
228
  }
209
229
 
230
+ ///////////////////////////////////////////////////////////////////////////////
231
+
232
+ /** Pulse the vibration hardware if it exists
233
+ * @param {Number} [pattern=100] - a single value in miliseconds or vibration interval array
234
+ * @memberof Input */
235
+ const vibrate = (pattern)=> vibrateEnable && Navigator.vibrate && Navigator.vibrate(pattern);
236
+
237
+ /** Cancel any ongoing vibration
238
+ * @memberof Input */
239
+ const vibrateStop = ()=> vibrate(0);
240
+
210
241
  ///////////////////////////////////////////////////////////////////////////////
211
242
  // Touch input
212
243
 
@@ -214,7 +245,9 @@ function gamepadsUpdate()
214
245
  * @const {boolean}
215
246
  * @memberof Input */
216
247
  const isTouchDevice = window.ontouchstart !== undefined;
217
- if (touchMouseEnable && isTouchDevice)
248
+
249
+ // try to enable touch mouse
250
+ if (isTouchDevice)
218
251
  {
219
252
  // handle all touch events the same way
220
253
  let wasTouching, hadTouchInput;
@@ -245,19 +278,25 @@ if (touchMouseEnable && isTouchDevice)
245
278
  }
246
279
 
247
280
  ///////////////////////////////////////////////////////////////////////////////
248
- // Touch gamepad - on screen virtual gamepad
281
+ // touch gamepad, virtual on screen gamepad emulator for touch devices
282
+
283
+ // touch input internal variables
284
+ let touchGamepadTimer = new Timer, touchGamepadButtons = [], touchGamepadStick = vec2(), touchGamepadAnalog = 1;
249
285
 
250
- // create the touch gamepad, called automatically by the engine if touchGamepadSize is set
251
- const touchGamepadTimer = new Timer;
286
+ // create the touch gamepad, called automatically by the engine
252
287
  function touchGamepadCreate()
253
288
  {
254
- if (!touchGamepadSize || !isTouchDevice)
289
+ if (!touchGamepadEnable || !isTouchDevice)
255
290
  return;
256
291
 
257
292
  ontouchstart = ontouchmove = ontouchend = (e)=>
258
293
  {
259
- if (!touchGamepadSize)
294
+ if (!touchGamepadEnable)
260
295
  return;
296
+
297
+ // clear touch gamepad input
298
+ touchGamepadStick = vec2();
299
+ touchGamepadButtons = [];
261
300
 
262
301
  const touching = e.touches.length;
263
302
  if (touching)
@@ -267,6 +306,13 @@ function touchGamepadCreate()
267
306
  // set that gamepad is active
268
307
  isUsingGamepad = 1;
269
308
  touchGamepadTimer.set();
309
+
310
+ if (paused)
311
+ {
312
+ // touch anywhere to press start when paused
313
+ touchGamepadButtons[9] = 1;
314
+ return;
315
+ }
270
316
  }
271
317
 
272
318
  // get center of left and right sides
@@ -274,68 +320,58 @@ function touchGamepadCreate()
274
320
  const buttonCenter = mainCanvasSize.subtract(vec2(touchGamepadSize, touchGamepadSize));
275
321
  const startCenter = mainCanvasSize.scale(.5);
276
322
 
277
- // update virtual gamepad
278
- const data = inputData[1] || (inputData[1] = []);
279
- const sticks = stickData[0] || (stickData[0] = []);
280
- sticks[0] = vec2();
281
-
282
323
  // check each touch point
283
- const buttons = [];
284
324
  for (const touch of e.touches)
285
325
  {
286
- const touchPos = vec2(touch.clientX, touch.clientY);
326
+ const touchPos = mouseToScreen(vec2(touch.clientX, touch.clientY));
287
327
  if (touchPos.distance(stickCenter) < touchGamepadSize)
288
328
  {
289
329
  // virtual analog stick
290
- sticks[0] = touchPos.subtract(stickCenter).clampLength();
291
- sticks[0].y = -sticks[0].y; // flip vertical
330
+ if (touchGamepadAnalog)
331
+ touchGamepadStick = touchPos.subtract(stickCenter).clampLength(touchGamepadSize/2).scale(2/touchGamepadSize);
332
+ else
333
+ {
334
+ // 8 way dpad
335
+ const angle = touchPos.subtract(stickCenter).angle();
336
+ touchGamepadStick.setAngle((angle * 4 / PI + 8.5 | 0) * PI / 4);
337
+ }
292
338
  }
293
339
  else if (touchPos.distance(buttonCenter) < touchGamepadSize)
294
340
  {
295
341
  // virtual face buttons
296
342
  const button = touchPos.subtract(buttonCenter).direction();
297
- buttons[button > 2 ? 2 : button > 1 ? 3 : button] = 1; // fix button locations
343
+ touchGamepadButtons[button] = 1;
298
344
  }
299
345
  else if (touchPos.distance(startCenter) < touchGamepadSize)
300
346
  {
301
- // virtual start button
302
- buttons[9] = 1;
347
+ // virtual start button in center
348
+ touchGamepadButtons[9] = 1;
303
349
  }
304
350
  }
305
-
306
- // read virtual buttons
307
- for (let j=10; j--;)
308
- data[j] = buttons[j] ? 1 + 2*!gamepadIsDown(j,0) : 4*gamepadIsDown(j,0);
309
351
  }
310
352
  }
311
353
 
312
354
  // render the touch gamepad, called automatically by the engine
313
355
  function touchGamepadRender()
314
356
  {
315
- if (!touchGamepadSize || !touchGamepadTimer.isSet())
357
+ if (!touchGamepadEnable || !touchGamepadTimer.isSet())
316
358
  return;
317
359
 
318
- // fade off when not touching
319
- const alpha = percent(touchGamepadTimer.get(), 4, 5);
320
- if (!alpha)
360
+ // fade off when not touching or paused
361
+ const alpha = percent(touchGamepadTimer.get(), 4, 3);
362
+ if (!alpha || paused)
321
363
  return;
322
364
 
323
365
  // setup the canvas
324
366
  overlayContext.save();
325
367
  overlayContext.globalAlpha = alpha*touchGamepadAlpha;
326
- overlayContext.fillStyle = '#fff6';
327
368
  overlayContext.strokeStyle = '#fff';
328
369
  overlayContext.lineWidth = 3;
329
370
 
330
- // draw start button
331
- //overlayContext.beginPath();
332
- //overlayContext.arc(mainCanvasSize.x/2, mainCanvasSize.y/2, touchGamepadSize*.9, 0,9);
333
- //overlayContext.fill();
334
- //overlayContext.stroke();
335
-
336
371
  // draw left analog stick
372
+ overlayContext.fillStyle = gamepadStick(0).length() > 0 ? '#fff' : '#000';
337
373
  overlayContext.beginPath();
338
- overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize*.9, 0,9);
374
+ overlayContext.arc(touchGamepadSize, mainCanvasSize.y-touchGamepadSize, touchGamepadSize/2, 0,9);
339
375
  overlayContext.fill();
340
376
  overlayContext.stroke();
341
377
 
@@ -343,9 +379,10 @@ function touchGamepadRender()
343
379
  const rightCenter = vec2(mainCanvasSize.x-touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
344
380
  for (let i=4; i--;)
345
381
  {
346
- const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize*.6));
382
+ const pos = rightCenter.add((new Vector2).setAngle(i*PI/2, touchGamepadSize/2));
383
+ overlayContext.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
347
384
  overlayContext.beginPath();
348
- overlayContext.arc(pos.x, pos.y, touchGamepadSize*.3, 0,9);
385
+ overlayContext.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
349
386
  overlayContext.fill();
350
387
  overlayContext.stroke();
351
388
  }
@@ -35,7 +35,7 @@ function medalsInit(saveName)
35
35
  {
36
36
  // check if medals are unlocked
37
37
  medalsSaveName = saveName;
38
- debugMedals || medals.forEach(medal=> medal.unlocked = localStorage[medal.storageKey()]);
38
+ debugMedals || medals.forEach(medal=> medal.unlocked = 1 || localStorage[medal.storageKey()]);
39
39
  }
40
40
 
41
41
  /**
@@ -68,13 +68,9 @@ class Medal
68
68
  this.name = name;
69
69
  this.description = description;
70
70
  this.icon = icon;
71
-
71
+ this.image = new Image();
72
72
  if (src)
73
- {
74
- // load image
75
- this.image = new Image();
76
73
  this.image.src = src;
77
- }
78
74
  }
79
75
 
80
76
  /** Unlocks a medal if not already unlocked */
@@ -104,21 +100,23 @@ class Medal
104
100
  const y = -medalDisplayHeight*hidePercent;
105
101
 
106
102
  // draw containing rect and clip to that region
107
- context.beginPath(context.save());
103
+ context.save();
104
+ context.beginPath();
108
105
  context.fillStyle = '#ddd'
109
106
  context.fill(context.rect(x, y, width, medalDisplayHeight));
110
- context.strokeStyle = context.fillStyle = '#000';
111
- context.lineWidth = 2;
107
+ context.strokeStyle = '#000';
108
+ context.lineWidth = 3;
112
109
  context.stroke();
113
110
  context.clip();
114
111
 
115
112
  // draw the icon and text
116
113
  this.renderIcon(x+15+medalDisplayIconSize/2, y+medalDisplayHeight/2);
117
114
  context.textAlign = 'left';
118
- context.font = '40px '+ fontDefault;
119
- context.fillText(this.name, x+medalDisplayIconSize+25, y+35);
120
- context.font = '25px '+ fontDefault;
121
- context.restore(context.fillText(this.description, x+medalDisplayIconSize+25, y+70));
115
+ context.font = '38px '+ fontDefault;
116
+ context.fillText(this.name, x+medalDisplayIconSize+30, y+28);
117
+ context.font = '24px '+ fontDefault;
118
+ context.fillText(this.description, x+medalDisplayIconSize+30, y+60);
119
+ context.restore();
122
120
  }
123
121
 
124
122
  /** Render the icon for a medal
@@ -130,11 +128,11 @@ class Medal
130
128
  {
131
129
  // draw the image or icon
132
130
  const context = overlayContext;
131
+ context.fillStyle = '#000';
133
132
  context.textAlign = 'center';
134
133
  context.textBaseline = 'middle';
135
- context.font = size*.6 + 'px '+ fontDefault;
136
- context.fillStyle = '#000';
137
- if (this.image)
134
+ context.font = size*.7 + 'px '+ fontDefault;
135
+ if (this.image.src)
138
136
  context.drawImage(this.image, x-size/2, y-size/2, size, size);
139
137
  else
140
138
  context.fillText(this.icon, x, y); // show icon if there is no image
@@ -210,7 +208,6 @@ class Newgrounds
210
208
  if (medal)
211
209
  {
212
210
  // copy newgrounds medal data
213
- medal.image = new Image();
214
211
  medal.image.src = newgroundsMedal['icon'];
215
212
  medal.name = newgroundsMedal['name'];
216
213
  medal.description = newgroundsMedal['description'];
@@ -32,7 +32,7 @@
32
32
  class EngineObject
33
33
  {
34
34
  /** Create an engine object and adds it to the list of objects
35
- * @param {Vector2} [position=new Vector2(0,0)] - World space position of the object
35
+ * @param {Vector2} [position=new Vector2()] - World space position of the object
36
36
  * @param {Vector2} [size=objectDefaultSize] - World space size of the object
37
37
  * @param {Number} [tileIndex=-1] - Tile to use to render object, untextured if -1
38
38
  * @param {Vector2} [tileSize=tileSizeDefault] - Size of tile in source pixels
@@ -77,12 +77,15 @@ class EngineObject
77
77
  this.gravityScale = 1;
78
78
  /** @property {Number} [renderOrder=0] - Objects are sorted by render order */
79
79
  this.renderOrder = renderOrder;
80
+ /** @property {Vector2} [velocity=new Vector2()] - Velocity of the object */
81
+ this.velocity = new Vector2();
82
+ /** @property {Number} [angleVelocity=0] - Angular velocity of the object */
83
+ this.angleVelocity = 0;
80
84
 
81
85
  // init other internal object stuff
82
86
  this.spawnTime = time;
83
- this.velocity = vec2(this.collideSolidObjects = this.angleVelocity = 0);
84
- this.collideTiles = 1;
85
87
  this.children = [];
88
+ this.collideTiles = 1;
86
89
 
87
90
  // add to list of objects
88
91
  engineObjects.push(this);
@@ -101,8 +104,8 @@ class EngineObject
101
104
  }
102
105
 
103
106
  // limit max speed to prevent missing collisions
104
- this.velocity.x = clamp(this.velocity.x, objectMaxSpeed, -objectMaxSpeed);
105
- this.velocity.y = clamp(this.velocity.y, objectMaxSpeed, -objectMaxSpeed);
107
+ this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
108
+ this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
106
109
 
107
110
  // apply physics
108
111
  const oldPos = this.pos.copy();
@@ -150,7 +153,7 @@ class EngineObject
150
153
  // if already was touching, try to push away
151
154
  const deltaPos = oldPos.subtract(o.pos);
152
155
  const length = deltaPos.length();
153
- const pushAwayAccel = .001; // push away if alread overlapping
156
+ const pushAwayAccel = .001; // push away if already overlapping
154
157
  const velocity = length < .01 ? randVector(pushAwayAccel) : deltaPos.scale(pushAwayAccel/length);
155
158
  this.velocity = this.velocity.add(velocity);
156
159
  if (o.mass) // push away if not fixed
@@ -192,8 +195,8 @@ class EngineObject
192
195
 
193
196
  // lerp betwen elastic or inelastic based on elasticity
194
197
  const elasticity = max(this.elasticity, o.elasticity);
195
- this.velocity.y = lerp(elasticity, elastic0, inelastic);
196
- o.velocity.y = lerp(elasticity, elastic1, inelastic);
198
+ this.velocity.y = lerp(elasticity, inelastic, elastic0);
199
+ o.velocity.y = lerp(elasticity, inelastic, elastic1);
197
200
  }
198
201
  }
199
202
  if (!smallStepUp && (isBlockedX || !isBlockedY)) // resolve x collision
@@ -213,8 +216,8 @@ class EngineObject
213
216
 
214
217
  // lerp betwen elastic or inelastic based on elasticity
215
218
  const elasticity = max(this.elasticity, o.elasticity);
216
- this.velocity.x = lerp(elasticity, elastic0, inelastic);
217
- o.velocity.x = lerp(elasticity, elastic1, inelastic);
219
+ this.velocity.x = lerp(elasticity, inelastic, elastic0);
220
+ o.velocity.x = lerp(elasticity, inelastic, elastic1);
218
221
  }
219
222
  else // bounce if other object is fixed
220
223
  this.velocity.x *= -this.elasticity;
@@ -244,7 +247,7 @@ class EngineObject
244
247
 
245
248
  // adjust next velocity to settle on ground
246
249
  const o = (oldPos.y - this.size.y/2|0) - (oldPos.y - this.size.y/2);
247
- if (o < 0 && o > -1 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
250
+ if (o < 0 && o > this.damping * this.velocity.y + gravity * this.gravityScale)
248
251
  this.velocity.y = this.damping ? (o - gravity * this.gravityScale) / this.damping : 0;
249
252
 
250
253
  // move to previous position
@@ -345,18 +348,6 @@ class EngineObject
345
348
  {
346
349
  ASSERT(collideSolidObjects || !isSolid); // solid objects must be set to collide
347
350
 
348
- // track collidable objects in separate list
349
- if (collideSolidObjects && !this.collideSolidObjects)
350
- {
351
- ASSERT(!engineObjectsCollide.includes(this));
352
- engineObjectsCollide.push(this);
353
- }
354
- else if (!collideSolidObjects && this.collideSolidObjects)
355
- {
356
- ASSERT(engineObjectsCollide.includes(this))
357
- engineObjectsCollide.splice(engineObjectsCollide.indexOf(this), 1);
358
- }
359
-
360
351
  this.collideSolidObjects = collideSolidObjects;
361
352
  this.isSolid = isSolid;
362
353
  this.collideTiles = collideTiles;
@@ -51,7 +51,7 @@ class ParticleEmitter extends EngineObject
51
51
  * @param {Number} [randomness=.2] - Apply extra randomness percent
52
52
  * @param {Boolean} [collideTiles=0] - Do particles collide against tiles
53
53
  * @param {Boolean} [additive=0] - Should particles use addtive blend
54
- * @param {Boolean} [randomColorLinear=0] - Should color be randomized linearly or across each component
54
+ * @param {Boolean} [randomColorLinear=1] - Should color be randomized linearly or across each component
55
55
  * @param {Number} [renderOrder=0] - Render order for particles (additive is above other stuff by default)
56
56
  */
57
57
  constructor
@@ -152,9 +152,9 @@ class ParticleEmitter extends EngineObject
152
152
  if (!this.emitTime || this.getAliveTime() <= this.emitTime)
153
153
  {
154
154
  // emit particles
155
- if (this.emitRate)
155
+ if (this.emitRate * particleEmitRateScale)
156
156
  {
157
- const rate = 1/this.emitRate;
157
+ const rate = 1/this.emitRate/particleEmitRateScale;
158
158
  for (this.emitTimeBuffer += timeDelta; this.emitTimeBuffer > 0; this.emitTimeBuffer -= rate)
159
159
  this.emitParticle();
160
160
  }
@@ -1,5 +1,7 @@
1
1
  /*
2
- LittleJS - Release build include file
2
+ LittleJS - Release Build
3
+ MIT License - Copyright 2021 Frank Force
4
+
3
5
  - This file is used for release builds in place of engineDebug.js
4
6
  - Debug functionality will be disabled to lower size and increase performance
5
7
  */