littlejsengine 1.9.5 → 1.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/littlejs.d.ts +57 -45
- package/dist/littlejs.esm.js +277 -192
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +275 -192
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +272 -188
- package/examples/breakout/index.html +3 -3
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/electron/index.html +2 -2
- package/examples/js13k/build.js +1 -0
- package/examples/js13k/index.html +13 -13
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +1 -1
- package/examples/platformer/index.html +8 -8
- package/examples/puzzle/index.html +2 -2
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +1 -1
- package/examples/typescript/index.html +1 -1
- package/package.json +1 -1
- package/src/engine.js +63 -40
- package/src/engineAudio.js +20 -16
- package/src/engineDebug.js +3 -4
- package/src/engineDraw.js +13 -10
- package/src/engineExport.js +2 -0
- package/src/engineInput.js +80 -64
- package/src/engineObject.js +18 -7
- package/src/engineParticles.js +14 -13
- package/src/engineSettings.js +13 -2
- package/src/engineTileLayer.js +15 -4
- package/src/engineUtilities.js +16 -14
- package/src/engineWebGL.js +20 -18
- package/LittleJS.zip +0 -0
package/src/engineInput.js
CHANGED
|
@@ -122,7 +122,7 @@ function gamepadWasReleased(button, gamepad=0)
|
|
|
122
122
|
* @return {Vector2}
|
|
123
123
|
* @memberof Input */
|
|
124
124
|
function gamepadStick(stick, gamepad=0)
|
|
125
|
-
{ return
|
|
125
|
+
{ return gamepadStickData[gamepad] ? gamepadStickData[gamepad][stick] || vec2() : vec2(); }
|
|
126
126
|
|
|
127
127
|
///////////////////////////////////////////////////////////////////////////////
|
|
128
128
|
// Input update called by engine
|
|
@@ -133,6 +133,8 @@ let inputData = [[]];
|
|
|
133
133
|
|
|
134
134
|
function inputUpdate()
|
|
135
135
|
{
|
|
136
|
+
if (headlessMode) return;
|
|
137
|
+
|
|
136
138
|
// clear input when lost focus (prevent stuck keys)
|
|
137
139
|
isTouchDevice || document.hasFocus() || clearInput();
|
|
138
140
|
|
|
@@ -145,6 +147,8 @@ function inputUpdate()
|
|
|
145
147
|
|
|
146
148
|
function inputUpdatePost()
|
|
147
149
|
{
|
|
150
|
+
if (headlessMode) return;
|
|
151
|
+
|
|
148
152
|
// clear input to prepare for next frame
|
|
149
153
|
for (const deviceInputData of inputData)
|
|
150
154
|
for (const i in deviceInputData)
|
|
@@ -153,9 +157,12 @@ function inputUpdatePost()
|
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
///////////////////////////////////////////////////////////////////////////////
|
|
156
|
-
//
|
|
160
|
+
// Input event handlers
|
|
157
161
|
|
|
162
|
+
function inputInit()
|
|
158
163
|
{
|
|
164
|
+
if (headlessMode) return;
|
|
165
|
+
|
|
159
166
|
onkeydown = (e)=>
|
|
160
167
|
{
|
|
161
168
|
if (debug && e.target != document.body) return;
|
|
@@ -186,21 +193,29 @@ function inputUpdatePost()
|
|
|
186
193
|
c == 'KeyA' ? 'ArrowLeft' :
|
|
187
194
|
c == 'KeyD' ? 'ArrowRight' : c : c;
|
|
188
195
|
}
|
|
196
|
+
|
|
197
|
+
// mouse event handlers
|
|
198
|
+
onmousedown = (e)=>
|
|
199
|
+
{
|
|
200
|
+
isUsingGamepad = false;
|
|
201
|
+
inputData[0][e.button] = 3;
|
|
202
|
+
mousePosScreen = mouseToScreen(e);
|
|
203
|
+
e.button && e.preventDefault();
|
|
204
|
+
}
|
|
205
|
+
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
206
|
+
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
207
|
+
onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
208
|
+
oncontextmenu = (e)=> false; // prevent right click menu
|
|
209
|
+
|
|
210
|
+
// init touch input
|
|
211
|
+
if (isTouchDevice)
|
|
212
|
+
touchInputInit();
|
|
189
213
|
}
|
|
190
214
|
|
|
191
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
192
|
-
// Mouse event handlers
|
|
193
|
-
|
|
194
|
-
onmousedown = (e)=> {isUsingGamepad = false; inputData[0][e.button] = 3; mousePosScreen = mouseToScreen(e); e.button && e.preventDefault();}
|
|
195
|
-
onmouseup = (e)=> inputData[0][e.button] = inputData[0][e.button] & 2 | 4;
|
|
196
|
-
onmousemove = (e)=> mousePosScreen = mouseToScreen(e);
|
|
197
|
-
onwheel = (e)=> mouseWheel = e.ctrlKey ? 0 : sign(e.deltaY);
|
|
198
|
-
oncontextmenu = (e)=> false; // prevent right click menu
|
|
199
|
-
|
|
200
215
|
// convert a mouse or touch event position to screen space
|
|
201
216
|
function mouseToScreen(mousePos)
|
|
202
217
|
{
|
|
203
|
-
if (!mainCanvas)
|
|
218
|
+
if (!mainCanvas || headlessMode)
|
|
204
219
|
return vec2(); // fix bug that can occur if user clicks before page loads
|
|
205
220
|
|
|
206
221
|
const rect = mainCanvas.getBoundingClientRect();
|
|
@@ -212,7 +227,7 @@ function mouseToScreen(mousePos)
|
|
|
212
227
|
// Gamepad input
|
|
213
228
|
|
|
214
229
|
// gamepad internal variables
|
|
215
|
-
const
|
|
230
|
+
const gamepadStickData = [];
|
|
216
231
|
|
|
217
232
|
// gamepads are updated by engine every frame automatically
|
|
218
233
|
function gamepadsUpdate()
|
|
@@ -229,14 +244,11 @@ function gamepadsUpdate()
|
|
|
229
244
|
// update touch gamepad if enabled
|
|
230
245
|
if (touchGamepadEnable && isTouchDevice)
|
|
231
246
|
{
|
|
232
|
-
|
|
233
|
-
if (!touchGamepadButtons)
|
|
234
|
-
createTouchGamepad();
|
|
235
|
-
|
|
247
|
+
ASSERT(touchGamepadButtons, 'set touchGamepadEnable before calling init!');
|
|
236
248
|
if (touchGamepadTimer.isSet())
|
|
237
249
|
{
|
|
238
250
|
// read virtual analog stick
|
|
239
|
-
const sticks =
|
|
251
|
+
const sticks = gamepadStickData[0] || (gamepadStickData[0] = []);
|
|
240
252
|
sticks[0] = vec2();
|
|
241
253
|
if (touchGamepadAnalog)
|
|
242
254
|
sticks[0] = applyDeadZones(touchGamepadStick);
|
|
@@ -253,7 +265,8 @@ function gamepadsUpdate()
|
|
|
253
265
|
for (let i=10; i--;)
|
|
254
266
|
{
|
|
255
267
|
const j = i == 3 ? 2 : i == 2 ? 3 : i; // fix button locations
|
|
256
|
-
|
|
268
|
+
const wasDown = gamepadIsDown(j,0);
|
|
269
|
+
data[j] = touchGamepadButtons[i] ? wasDown ? 1 : 3 : wasDown ? 4 : 0;
|
|
257
270
|
}
|
|
258
271
|
}
|
|
259
272
|
}
|
|
@@ -273,7 +286,7 @@ function gamepadsUpdate()
|
|
|
273
286
|
// get or create gamepad data
|
|
274
287
|
const gamepad = gamepads[i];
|
|
275
288
|
const data = inputData[i+1] || (inputData[i+1] = []);
|
|
276
|
-
const sticks =
|
|
289
|
+
const sticks = gamepadStickData[i] || (gamepadStickData[i] = []);
|
|
277
290
|
|
|
278
291
|
if (gamepad)
|
|
279
292
|
{
|
|
@@ -312,28 +325,44 @@ function gamepadsUpdate()
|
|
|
312
325
|
* @param {Number|Array} [pattern] - single value in ms or vibration interval array
|
|
313
326
|
* @memberof Input */
|
|
314
327
|
function vibrate(pattern=100)
|
|
315
|
-
{ vibrateEnable && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
328
|
+
{ vibrateEnable && !headlessMode && navigator && navigator.vibrate && navigator.vibrate(pattern); }
|
|
316
329
|
|
|
317
330
|
/** Cancel any ongoing vibration
|
|
318
331
|
* @memberof Input */
|
|
319
332
|
function vibrateStop() { vibrate(0); }
|
|
320
333
|
|
|
321
334
|
///////////////////////////////////////////////////////////////////////////////
|
|
322
|
-
// Touch input
|
|
335
|
+
// Touch input & virtual on screen gamepad
|
|
323
336
|
|
|
324
337
|
/** True if a touch device has been detected
|
|
325
338
|
* @memberof Input */
|
|
326
|
-
const isTouchDevice = window.ontouchstart !== undefined;
|
|
339
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
340
|
+
|
|
341
|
+
// touch gamepad internal variables
|
|
342
|
+
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
327
343
|
|
|
328
344
|
// try to enable touch mouse
|
|
329
|
-
|
|
345
|
+
function touchInputInit()
|
|
330
346
|
{
|
|
347
|
+
// add non passive touch event listeners
|
|
348
|
+
let handleTouch = handleTouchDefault;
|
|
349
|
+
if (touchGamepadEnable)
|
|
350
|
+
{
|
|
351
|
+
// touch input internal variables
|
|
352
|
+
handleTouch = handleTouchGamepad;
|
|
353
|
+
touchGamepadButtons = [];
|
|
354
|
+
touchGamepadStick = vec2();
|
|
355
|
+
}
|
|
356
|
+
document.addEventListener('touchstart', (e) => handleTouch(e), { passive: false });
|
|
357
|
+
document.addEventListener('touchmove', (e) => handleTouch(e), { passive: false });
|
|
358
|
+
document.addEventListener('touchend', (e) => handleTouch(e), { passive: false });
|
|
359
|
+
|
|
331
360
|
// override mouse events
|
|
332
|
-
let wasTouching;
|
|
333
361
|
onmousedown = onmouseup = ()=> 0;
|
|
334
362
|
|
|
335
363
|
// handle all touch events the same way
|
|
336
|
-
|
|
364
|
+
let wasTouching;
|
|
365
|
+
function handleTouchDefault(e)
|
|
337
366
|
{
|
|
338
367
|
// fix stalled audio requiring user interaction
|
|
339
368
|
if (soundEnable && audioContext && audioContext.state != 'running')
|
|
@@ -362,27 +391,14 @@ if (isTouchDevice)
|
|
|
362
391
|
// must return true so the document will get focus
|
|
363
392
|
return true;
|
|
364
393
|
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
368
|
-
// touch gamepad, virtual on screen gamepad emulator for touch devices
|
|
369
|
-
|
|
370
|
-
// touch input internal variables
|
|
371
|
-
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
372
394
|
|
|
373
|
-
//
|
|
374
|
-
function
|
|
375
|
-
{
|
|
376
|
-
// touch input internal variables
|
|
377
|
-
touchGamepadButtons = [];
|
|
378
|
-
touchGamepadStick = vec2();
|
|
379
|
-
|
|
380
|
-
const touchHandler = ontouchstart;
|
|
381
|
-
ontouchstart = ontouchmove = ontouchend = (e)=>
|
|
395
|
+
// special handling for virtual gamepad mode
|
|
396
|
+
function handleTouchGamepad(e)
|
|
382
397
|
{
|
|
383
398
|
// clear touch gamepad input
|
|
384
399
|
touchGamepadStick = vec2();
|
|
385
400
|
touchGamepadButtons = [];
|
|
401
|
+
isUsingGamepad = true;
|
|
386
402
|
|
|
387
403
|
const touching = e.touches.length;
|
|
388
404
|
if (touching)
|
|
@@ -423,9 +439,8 @@ function createTouchGamepad()
|
|
|
423
439
|
}
|
|
424
440
|
}
|
|
425
441
|
|
|
426
|
-
// call default touch handler
|
|
427
|
-
|
|
428
|
-
isUsingGamepad = true;
|
|
442
|
+
// call default touch handler so normal touch events still work
|
|
443
|
+
handleTouchDefault(e);
|
|
429
444
|
|
|
430
445
|
// must return true so the document will get focus
|
|
431
446
|
return true;
|
|
@@ -444,32 +459,33 @@ function touchGamepadRender()
|
|
|
444
459
|
return;
|
|
445
460
|
|
|
446
461
|
// setup the canvas
|
|
447
|
-
overlayContext
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
462
|
+
const context = overlayContext;
|
|
463
|
+
context.save();
|
|
464
|
+
context.globalAlpha = alpha*touchGamepadAlpha;
|
|
465
|
+
context.strokeStyle = '#fff';
|
|
466
|
+
context.lineWidth = 3;
|
|
451
467
|
|
|
452
468
|
// draw left analog stick
|
|
453
|
-
|
|
454
|
-
|
|
469
|
+
context.fillStyle = touchGamepadStick.lengthSquared() > 0 ? '#fff' : '#000';
|
|
470
|
+
context.beginPath();
|
|
455
471
|
|
|
456
472
|
const leftCenter = vec2(touchGamepadSize, mainCanvasSize.y-touchGamepadSize);
|
|
457
473
|
if (touchGamepadAnalog) // draw circle shaped gamepad
|
|
458
474
|
{
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
475
|
+
context.arc(leftCenter.x, leftCenter.y, touchGamepadSize/2, 0, 9);
|
|
476
|
+
context.fill();
|
|
477
|
+
context.stroke();
|
|
462
478
|
}
|
|
463
479
|
else // draw cross shaped gamepad
|
|
464
480
|
{
|
|
465
481
|
for(let i=10; i--;)
|
|
466
482
|
{
|
|
467
483
|
const angle = i*PI/4;
|
|
468
|
-
|
|
469
|
-
i%2 &&
|
|
470
|
-
i==1 &&
|
|
484
|
+
context.arc(leftCenter.x, leftCenter.y,touchGamepadSize*.6, angle + PI/8, angle + PI/8);
|
|
485
|
+
i%2 && context.arc(leftCenter.x, leftCenter.y, touchGamepadSize*.33, angle, angle);
|
|
486
|
+
i==1 && context.fill();
|
|
471
487
|
}
|
|
472
|
-
|
|
488
|
+
context.stroke();
|
|
473
489
|
}
|
|
474
490
|
|
|
475
491
|
// draw right face buttons
|
|
@@ -477,13 +493,13 @@ function touchGamepadRender()
|
|
|
477
493
|
for (let i=4; i--;)
|
|
478
494
|
{
|
|
479
495
|
const pos = rightCenter.add(vec2().setDirection(i, touchGamepadSize/2));
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
496
|
+
context.fillStyle = touchGamepadButtons[i] ? '#fff' : '#000';
|
|
497
|
+
context.beginPath();
|
|
498
|
+
context.arc(pos.x, pos.y, touchGamepadSize/4, 0,9);
|
|
499
|
+
context.fill();
|
|
500
|
+
context.stroke();
|
|
485
501
|
}
|
|
486
502
|
|
|
487
503
|
// set canvas back to normal
|
|
488
|
-
|
|
504
|
+
context.restore();
|
|
489
505
|
}
|
package/src/engineObject.js
CHANGED
|
@@ -108,18 +108,30 @@ class EngineObject
|
|
|
108
108
|
engineObjects.push(this);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
/** Update the object transform
|
|
112
|
-
|
|
111
|
+
/** Update the object transform, called automatically by engine even when paused */
|
|
112
|
+
updateTransforms()
|
|
113
113
|
{
|
|
114
114
|
const parent = this.parent;
|
|
115
115
|
if (parent)
|
|
116
116
|
{
|
|
117
117
|
// copy parent pos/angle
|
|
118
|
-
|
|
119
|
-
this.
|
|
120
|
-
|
|
118
|
+
const mirror = parent.getMirrorSign();
|
|
119
|
+
this.pos = this.localPos.multiply(vec2(mirror,1)).rotate(-parent.angle).add(parent.pos);
|
|
120
|
+
this.angle = mirror*this.localAngle + parent.angle;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// update children
|
|
124
|
+
for (const child of this.children)
|
|
125
|
+
child.updateTransforms();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Update the object physics, called automatically by engine once each frame */
|
|
129
|
+
update()
|
|
130
|
+
{
|
|
131
|
+
// child objects do not have physics
|
|
132
|
+
if (this.parent)
|
|
133
|
+
return;
|
|
134
|
+
|
|
123
135
|
// limit max speed to prevent missing collisions
|
|
124
136
|
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
125
137
|
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
@@ -134,8 +146,7 @@ class EngineObject
|
|
|
134
146
|
// physics sanity checks
|
|
135
147
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
136
148
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
137
|
-
|
|
138
|
-
if (!enablePhysicsSolver || !this.mass) // do not update collision for fixed objects
|
|
149
|
+
if (!enablePhysicsSolver || !this.mass) // dont do collision for fixed objects
|
|
139
150
|
return;
|
|
140
151
|
|
|
141
152
|
const wasMovingDown = this.velocity.y < 0;
|
package/src/engineParticles.js
CHANGED
|
@@ -233,20 +233,21 @@ class ParticleEmitter extends EngineObject
|
|
|
233
233
|
class Particle extends EngineObject
|
|
234
234
|
{
|
|
235
235
|
/**
|
|
236
|
-
* Create a particle with the
|
|
237
|
-
*
|
|
238
|
-
* @param {
|
|
239
|
-
* @param {
|
|
240
|
-
* @param {
|
|
241
|
-
* @param {Color}
|
|
242
|
-
* @param {
|
|
243
|
-
* @param {Number}
|
|
244
|
-
* @param {Number}
|
|
245
|
-
* @param {Number}
|
|
246
|
-
* @param {
|
|
247
|
-
* @param {
|
|
236
|
+
* Create a particle with the passed in settings
|
|
237
|
+
* Typically this is created automatically by a ParticleEmitter
|
|
238
|
+
* @param {Vector2} position - World space position of the particle
|
|
239
|
+
* @param {TileInfo} tileInfo - Tile info to render particles
|
|
240
|
+
* @param {Number} angle - Angle to rotate the particle
|
|
241
|
+
* @param {Color} colorStart - Color at start of life
|
|
242
|
+
* @param {Color} colorEnd - Color at end of life
|
|
243
|
+
* @param {Number} lifeTime - How long to live for
|
|
244
|
+
* @param {Number} sizeStart - Size at start of life
|
|
245
|
+
* @param {Number} sizeEnd - Size at end of life
|
|
246
|
+
* @param {Number} fadeRate - How quick to fade in/out
|
|
247
|
+
* @param {Boolean} additive - Does it use additive blend mode
|
|
248
|
+
* @param {Number} trailScale - If a trail, how long to make it
|
|
248
249
|
* @param {ParticleEmitter} [localSpaceEmitter] - Parent emitter if local space
|
|
249
|
-
* @param {Function}
|
|
250
|
+
* @param {Function} [destroyCallback] - Callback when particle dies
|
|
250
251
|
*/
|
|
251
252
|
constructor(position, tileInfo, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, fadeRate, additive, trailScale, localSpaceEmitter, destroyCallback
|
|
252
253
|
)
|
package/src/engineSettings.js
CHANGED
|
@@ -55,6 +55,12 @@ let fontDefault = 'arial';
|
|
|
55
55
|
* @memberof Settings */
|
|
56
56
|
let showSplashScreen = false;
|
|
57
57
|
|
|
58
|
+
/** Disables all rendering, audio, and input for servers
|
|
59
|
+
* @type {Boolean}
|
|
60
|
+
* @default
|
|
61
|
+
* @memberof Settings */
|
|
62
|
+
let headlessMode = false;
|
|
63
|
+
|
|
58
64
|
///////////////////////////////////////////////////////////////////////////////
|
|
59
65
|
// WebGL settings
|
|
60
66
|
|
|
@@ -83,7 +89,7 @@ let tileSizeDefault = vec2(16);
|
|
|
83
89
|
* @type {Number}
|
|
84
90
|
* @default
|
|
85
91
|
* @memberof Settings */
|
|
86
|
-
let tileFixBleedScale = .
|
|
92
|
+
let tileFixBleedScale = .5;
|
|
87
93
|
|
|
88
94
|
///////////////////////////////////////////////////////////////////////////////
|
|
89
95
|
// Object settings
|
|
@@ -208,7 +214,7 @@ let soundEnable = true;
|
|
|
208
214
|
* @type {Number}
|
|
209
215
|
* @default
|
|
210
216
|
* @memberof Settings */
|
|
211
|
-
let soundVolume = .
|
|
217
|
+
let soundVolume = .3;
|
|
212
218
|
|
|
213
219
|
/** Default range where sound no longer plays
|
|
214
220
|
* @type {Number}
|
|
@@ -293,6 +299,11 @@ function setFontDefault(font) { fontDefault = font; }
|
|
|
293
299
|
* @memberof Settings */
|
|
294
300
|
function setShowSplashScreen(show) { showSplashScreen = show; }
|
|
295
301
|
|
|
302
|
+
/** Set to disalbe rendering, audio, and input for servers
|
|
303
|
+
* @param {Boolean} headless
|
|
304
|
+
* @memberof Settings */
|
|
305
|
+
function setHeadlessMode(headless) { headlessMode = headless; }
|
|
306
|
+
|
|
296
307
|
/** Set if webgl rendering is enabled
|
|
297
308
|
* @param {Boolean} enable
|
|
298
309
|
* @memberof Settings */
|
package/src/engineTileLayer.js
CHANGED
|
@@ -181,7 +181,7 @@ class TileLayer extends EngineObject
|
|
|
181
181
|
|
|
182
182
|
/** @property {HTMLCanvasElement} - The canvas used by this tile layer */
|
|
183
183
|
this.canvas = document.createElement('canvas');
|
|
184
|
-
/** @property {CanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
184
|
+
/** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - The 2D canvas context used by this tile layer */
|
|
185
185
|
this.context = this.canvas.getContext('2d');
|
|
186
186
|
/** @property {Vector2} - How much to scale this layer when rendered */
|
|
187
187
|
this.scale = scale;
|
|
@@ -192,6 +192,17 @@ class TileLayer extends EngineObject
|
|
|
192
192
|
this.data = [];
|
|
193
193
|
for (let j = this.size.area(); j--;)
|
|
194
194
|
this.data.push(new TileLayerData);
|
|
195
|
+
|
|
196
|
+
if (headlessMode)
|
|
197
|
+
{
|
|
198
|
+
// disable rendering
|
|
199
|
+
this.redraw = () => {};
|
|
200
|
+
this.render = () => {};
|
|
201
|
+
this.redrawStart = () => {};
|
|
202
|
+
this.redrawEnd = () => {};
|
|
203
|
+
this.drawTileData = () => {};
|
|
204
|
+
this.drawCanvas2D = () => {};
|
|
205
|
+
}
|
|
195
206
|
}
|
|
196
207
|
|
|
197
208
|
/** Set data at a given position in the array
|
|
@@ -222,7 +233,7 @@ class TileLayer extends EngineObject
|
|
|
222
233
|
ASSERT(mainContext != this.context, 'must call redrawEnd() after drawing tiles');
|
|
223
234
|
|
|
224
235
|
// flush and copy gl canvas because tile canvas does not use webgl
|
|
225
|
-
|
|
236
|
+
!glOverlay && !this.isOverlay && glCopyToContext(mainContext);
|
|
226
237
|
|
|
227
238
|
// draw the entire cached level onto the canvas
|
|
228
239
|
const pos = worldToScreen(this.pos.add(vec2(0,this.size.y*this.scale.y)));
|
|
@@ -272,14 +283,14 @@ class TileLayer extends EngineObject
|
|
|
272
283
|
this.context.imageSmoothingEnabled = !canvasPixelated;
|
|
273
284
|
|
|
274
285
|
// setup gl rendering if enabled
|
|
275
|
-
|
|
286
|
+
glPreRender();
|
|
276
287
|
}
|
|
277
288
|
|
|
278
289
|
/** Call to end the redraw process */
|
|
279
290
|
redrawEnd()
|
|
280
291
|
{
|
|
281
292
|
ASSERT(mainContext == this.context, 'must call redrawStart() before drawing tiles');
|
|
282
|
-
|
|
293
|
+
glCopyToContext(mainContext, true);
|
|
283
294
|
//debugSaveCanvas(this.canvas);
|
|
284
295
|
|
|
285
296
|
// set stuff back to normal
|
package/src/engineUtilities.js
CHANGED
|
@@ -64,7 +64,7 @@ function clamp(value, min=0, max=1) { return value < min ? min : value > max ? m
|
|
|
64
64
|
* @return {Number}
|
|
65
65
|
* @memberof Utilities */
|
|
66
66
|
function percent(value, valueA, valueB)
|
|
67
|
-
{ return valueB
|
|
67
|
+
{ return (valueB-=valueA) ? clamp((value-valueA)/valueB) : 0; }
|
|
68
68
|
|
|
69
69
|
/** Linearly interpolates between values passed in using percent
|
|
70
70
|
* @param {Number} percent
|
|
@@ -271,7 +271,7 @@ class RandomGenerator
|
|
|
271
271
|
this.seed ^= this.seed << 13;
|
|
272
272
|
this.seed ^= this.seed >>> 17;
|
|
273
273
|
this.seed ^= this.seed << 5;
|
|
274
|
-
return valueB + (valueA - valueB) * abs(this.seed %
|
|
274
|
+
return valueB + (valueA - valueB) * abs(this.seed % 1e8) / 1e8;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
/** Returns a floored seeded random value the two values passed in
|
|
@@ -282,7 +282,7 @@ class RandomGenerator
|
|
|
282
282
|
|
|
283
283
|
/** Randomly returns either -1 or 1 deterministically
|
|
284
284
|
* @return {Number} */
|
|
285
|
-
sign() { return this.
|
|
285
|
+
sign() { return this.float() > .5 ? 1 : -1; }
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -330,6 +330,7 @@ class Vector2
|
|
|
330
330
|
* @param {Number} [y] - Y axis location */
|
|
331
331
|
constructor(x=0, y=0)
|
|
332
332
|
{
|
|
333
|
+
ASSERT(typeof x === 'number' && typeof y === 'number');
|
|
333
334
|
/** @property {Number} - X axis location */
|
|
334
335
|
this.x = x;
|
|
335
336
|
/** @property {Number} - Y axis location */
|
|
@@ -656,12 +657,14 @@ class Color
|
|
|
656
657
|
* @return {Color} */
|
|
657
658
|
setHSLA(h=0, s=0, l=1, a=1)
|
|
658
659
|
{
|
|
660
|
+
h = mod(h,1);
|
|
661
|
+
s = clamp(s);
|
|
662
|
+
l = clamp(l);
|
|
659
663
|
const q = l < .5 ? l*(1+s) : l+s-l*s, p = 2*l-q,
|
|
660
664
|
f = (p, q, t)=>
|
|
661
|
-
(t = (
|
|
662
|
-
t < 1
|
|
663
|
-
t < 2
|
|
664
|
-
|
|
665
|
+
(t = mod(t,1))*6 < 1 ? p+(q-p)*6*t :
|
|
666
|
+
t*2 < 1 ? q :
|
|
667
|
+
t*3 < 2 ? p+(q-p)*(4-t*6) : p;
|
|
665
668
|
this.r = f(p, q, h + 1/3);
|
|
666
669
|
this.g = f(p, q, h);
|
|
667
670
|
this.b = f(p, q, h - 1/3);
|
|
@@ -712,13 +715,12 @@ class Color
|
|
|
712
715
|
).clamp();
|
|
713
716
|
}
|
|
714
717
|
|
|
715
|
-
/** Returns this color expressed as a
|
|
718
|
+
/** Returns this color expressed as a rgb color code
|
|
716
719
|
* @param {Boolean} [useAlpha] - if alpha should be included in result
|
|
717
720
|
* @return {String} */
|
|
718
|
-
toString(useAlpha = true)
|
|
719
|
-
{
|
|
720
|
-
|
|
721
|
-
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
721
|
+
toString(useAlpha = true)
|
|
722
|
+
{
|
|
723
|
+
return `rgb(${this.r*255},${this.g*255},${this.b*255},${useAlpha ? this.a : 0})`;
|
|
722
724
|
}
|
|
723
725
|
|
|
724
726
|
/** Set this color from a hex code
|
|
@@ -776,11 +778,11 @@ class Timer
|
|
|
776
778
|
|
|
777
779
|
/** Returns true if set and has not elapsed
|
|
778
780
|
* @return {Boolean} */
|
|
779
|
-
active() { return time
|
|
781
|
+
active() { return time < this.time; }
|
|
780
782
|
|
|
781
783
|
/** Returns true if set and elapsed
|
|
782
784
|
* @return {Boolean} */
|
|
783
|
-
elapsed() { return time
|
|
785
|
+
elapsed() { return time >= this.time; }
|
|
784
786
|
|
|
785
787
|
/** Get how long since elapsed, returns 0 if not set (returns negative if currently active)
|
|
786
788
|
* @return {Number} */
|
package/src/engineWebGL.js
CHANGED
|
@@ -30,6 +30,8 @@ let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData,
|
|
|
30
30
|
// Initalize WebGL, called automatically by the engine
|
|
31
31
|
function glInit()
|
|
32
32
|
{
|
|
33
|
+
if (!glEnable || headlessMode) return;
|
|
34
|
+
|
|
33
35
|
// create the canvas and textures
|
|
34
36
|
glCanvas = document.createElement('canvas');
|
|
35
37
|
glContext = glCanvas.getContext('webgl2');
|
|
@@ -42,11 +44,11 @@ function glInit()
|
|
|
42
44
|
'#version 300 es\n' + // specify GLSL ES version
|
|
43
45
|
'precision highp float;'+ // use highp for better accuracy
|
|
44
46
|
'uniform mat4 m;'+ // transform matrix
|
|
45
|
-
'in vec2 g;'+ // geometry
|
|
46
|
-
'in vec4 p,u,c,a;'+ // position/size, uvs, color, additiveColor
|
|
47
|
-
'in float r;'+ // rotation
|
|
48
|
-
'out vec2 v;'+ //
|
|
49
|
-
'out vec4 d,e;'+ //
|
|
47
|
+
'in vec2 g;'+ // in: geometry
|
|
48
|
+
'in vec4 p,u,c,a;'+ // in: position/size, uvs, color, additiveColor
|
|
49
|
+
'in float r;'+ // in: rotation
|
|
50
|
+
'out vec2 v;'+ // out: uv
|
|
51
|
+
'out vec4 d,e;'+ // out: color, additiveColor
|
|
50
52
|
'void main(){'+ // shader entry point
|
|
51
53
|
'vec2 s=(g-.5)*p.zw;'+ // get size offset
|
|
52
54
|
'gl_Position=m*vec4(p.xy+s*cos(r)-vec2(-s.y,s)*sin(r),1,1);'+ // transform position
|
|
@@ -56,10 +58,10 @@ function glInit()
|
|
|
56
58
|
,
|
|
57
59
|
'#version 300 es\n' + // specify GLSL ES version
|
|
58
60
|
'precision highp float;'+ // use highp for better accuracy
|
|
59
|
-
'in vec2 v;'+ // uv
|
|
60
|
-
'in vec4 d,e;'+ // color, additiveColor
|
|
61
61
|
'uniform sampler2D s;'+ // texture
|
|
62
|
-
'
|
|
62
|
+
'in vec2 v;'+ // in: uv
|
|
63
|
+
'in vec4 d,e;'+ // in: color, additiveColor
|
|
64
|
+
'out vec4 c;'+ // out: color
|
|
63
65
|
'void main(){'+ // shader entry point
|
|
64
66
|
'c=texture(s,v)*d+e;'+ // modulate texture by color plus additive
|
|
65
67
|
'}' // end of shader
|
|
@@ -81,9 +83,11 @@ function glInit()
|
|
|
81
83
|
// Setup render each frame, called automatically by engine
|
|
82
84
|
function glPreRender()
|
|
83
85
|
{
|
|
86
|
+
if (!glEnable || headlessMode) return;
|
|
87
|
+
|
|
84
88
|
// clear and set to same size as main canvas
|
|
85
89
|
glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
|
|
86
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
90
|
+
//glContext.clear(gl_COLOR_BUFFER_BIT); // auto cleared when size is set
|
|
87
91
|
|
|
88
92
|
// set up the shader
|
|
89
93
|
glContext.useProgram(glShader);
|
|
@@ -117,12 +121,12 @@ function glPreRender()
|
|
|
117
121
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
118
122
|
const p = vec2(-1).subtract(cameraPos.multiply(s));
|
|
119
123
|
glContext.uniformMatrix4fv(glContext.getUniformLocation(glShader, 'm'), false,
|
|
120
|
-
|
|
124
|
+
[
|
|
121
125
|
s.x, 0, 0, 0,
|
|
122
126
|
0, s.y, 0, 0,
|
|
123
127
|
1, 1, 1, 1,
|
|
124
128
|
p.x, p.y, 0, 0
|
|
125
|
-
]
|
|
129
|
+
]
|
|
126
130
|
);
|
|
127
131
|
}
|
|
128
132
|
|
|
@@ -133,7 +137,7 @@ function glPreRender()
|
|
|
133
137
|
function glSetTexture(texture)
|
|
134
138
|
{
|
|
135
139
|
// must flush cache with the old texture to set a new one
|
|
136
|
-
if (texture == glActiveTexture)
|
|
140
|
+
if (headlessMode || texture == glActiveTexture)
|
|
137
141
|
return;
|
|
138
142
|
|
|
139
143
|
glFlush();
|
|
@@ -193,7 +197,6 @@ function glCreateTexture(image)
|
|
|
193
197
|
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
194
198
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, filter);
|
|
195
199
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
196
|
-
|
|
197
200
|
return texture;
|
|
198
201
|
}
|
|
199
202
|
|
|
@@ -217,12 +220,12 @@ function glFlush()
|
|
|
217
220
|
}
|
|
218
221
|
|
|
219
222
|
/** Draw any sprites still in the buffer, copy to main canvas and clear
|
|
220
|
-
* @param {CanvasRenderingContext2D} context
|
|
223
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
221
224
|
* @param {Boolean} [forceDraw]
|
|
222
225
|
* @memberof WebGL */
|
|
223
226
|
function glCopyToContext(context, forceDraw=false)
|
|
224
227
|
{
|
|
225
|
-
if (!glInstanceCount && !forceDraw) return;
|
|
228
|
+
if (!glEnable || !glInstanceCount && !forceDraw) return;
|
|
226
229
|
|
|
227
230
|
glFlush();
|
|
228
231
|
|
|
@@ -279,7 +282,7 @@ let glPostShader, glPostTexture, glPostIncludeOverlay;
|
|
|
279
282
|
function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
280
283
|
{
|
|
281
284
|
ASSERT(!glPostShader, 'can only have 1 post effects shader');
|
|
282
|
-
|
|
285
|
+
if (headlessMode) return;
|
|
283
286
|
if (!shaderCode) // default shader pass through
|
|
284
287
|
shaderCode = 'void mainImage(out vec4 c,vec2 p){c=texture(iChannel0,p/iResolution.xy);}';
|
|
285
288
|
|
|
@@ -318,8 +321,7 @@ function glInitPostProcess(shaderCode, includeOverlay=false)
|
|
|
318
321
|
// Render the post processing shader, called automatically by the engine
|
|
319
322
|
function glRenderPostProcess()
|
|
320
323
|
{
|
|
321
|
-
if (!glPostShader)
|
|
322
|
-
return;
|
|
324
|
+
if (!glPostShader || headlessMode) return;
|
|
323
325
|
|
|
324
326
|
// prepare to render post process shader
|
|
325
327
|
if (glEnable)
|
package/LittleJS.zip
DELETED
|
Binary file
|