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.
- package/README.md +15 -93
- package/dist/littlejs.d.ts +79 -17
- package/dist/littlejs.esm.js +179 -90
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +165 -90
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +165 -90
- package/examples/box2d/game.js +15 -1
- package/examples/box2d/gameObjects.js +10 -9
- package/examples/box2d/index.html +5 -5
- package/examples/box2d/scenes.js +4 -4
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/index.html +3 -3
- package/examples/breakoutTutorial/README.md +9 -7
- package/examples/breakoutTutorial/game.js +6 -6
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/electron/game.js +3 -0
- package/examples/electron/index.html +2 -2
- package/examples/htmlMenu/index.html +3 -3
- package/examples/js13k/game.js +3 -0
- package/examples/js13k/index.html +13 -13
- package/examples/module/game.js +3 -0
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +4 -1
- package/examples/platformer/game.js +21 -15
- package/examples/platformer/gameCharacter.js +15 -14
- package/examples/platformer/gameEffects.js +3 -4
- package/examples/platformer/gameLevel.js +18 -33
- package/examples/platformer/gameObjects.js +9 -14
- package/examples/platformer/index.html +8 -8
- package/examples/platformer/tiles.png +0 -0
- package/examples/puzzle/index.html +2 -2
- package/examples/starter/game.js +3 -0
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +1 -1
- package/examples/typescript/game.js +2 -0
- package/examples/typescript/game.ts +4 -1
- package/examples/typescript/index.html +1 -1
- package/examples/uiSystem/game.js +23 -16
- package/examples/uiSystem/index.html +3 -14
- package/package.json +1 -1
- package/plugins/postProcess.js +1 -1
- package/plugins/uiSystem.js +84 -24
- package/src/engine.js +14 -22
- package/src/engineAudio.js +7 -13
- package/src/engineDraw.js +20 -19
- package/src/engineExport.js +14 -0
- package/src/engineInput.js +8 -6
- package/src/engineMedals.js +19 -5
- package/src/engineObject.js +9 -5
- package/src/engineTileLayer.js +10 -8
- package/src/engineUtilities.js +60 -10
- 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.
|
|
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
|
|
112
|
-
* @param {Function} gameUpdate
|
|
113
|
-
* @param {Function} gameUpdatePost - Called after physics and objects are updated,
|
|
114
|
-
* @param {Function} gameRender
|
|
115
|
-
* @param {Function} gameRenderPost - Called after objects are rendered,
|
|
116
|
-
* @param {Array} [imageSources=[
|
|
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
|
-
|
|
291
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/engineAudio.js
CHANGED
|
@@ -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
|
-
|
|
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]
|
|
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
|
|
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 (
|
|
572
|
-
|
|
572
|
+
else if (rootElement.requestFullscreen)
|
|
573
|
+
rootElement.requestFullscreen();
|
|
573
574
|
}
|
package/src/engineExport.js
CHANGED
|
@@ -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,
|
package/src/engineInput.js
CHANGED
|
@@ -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 =
|
|
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;
|
package/src/engineMedals.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
96
|
-
|
|
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 =
|
|
126
|
+
localStorage[this.storageKey()] = this.unlocked = true;
|
|
113
127
|
medalsDisplayQueue.push(this);
|
|
114
128
|
}
|
|
115
129
|
|
package/src/engineObject.js
CHANGED
|
@@ -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.
|
|
157
|
-
this.
|
|
158
|
-
|
|
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
|
|
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
|
}
|
package/src/engineTileLayer.js
CHANGED
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
|
|
13
13
|
'use strict';
|
|
14
14
|
|
|
15
|
-
/** The tile collision layer
|
|
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
|
|
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 (
|
|
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
|
}
|
package/src/engineUtilities.js
CHANGED
|
@@ -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)
|
|
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
|
-
{
|
|
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
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
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
|
///////////////////////////////////////////////////////////////////////////////
|
package/src/engineWebGL.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|