littlejsengine 1.11.2 → 1.11.4
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/.vscode/launch.json +14 -0
- package/README.md +14 -20
- package/dist/littlejs.d.ts +41 -30
- package/dist/littlejs.esm.js +145 -141
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +141 -140
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +138 -133
- package/examples/box2d/gameObjects.js +4 -4
- package/examples/box2d/scenes.js +2 -2
- package/examples/breakout/gameObjects.js +2 -2
- package/examples/breakoutTutorial/game.js +1 -1
- package/examples/index.html +1 -7
- package/examples/logo.png +0 -0
- package/examples/module/game.js +1 -1
- package/examples/platformer/game.js +2 -2
- package/examples/platformer/gameCharacter.js +5 -5
- package/examples/platformer/gameEffects.js +4 -4
- package/examples/platformer/gameObjects.js +2 -2
- package/examples/puzzle/game.js +2 -2
- package/examples/starter/game.js +10 -44
- package/examples/starter/tiles.png +0 -0
- package/jsconfig.json +1 -0
- package/package.json +1 -1
- package/plugins/box2d.js +10 -10
- package/plugins/newgrounds.js +1 -1
- package/src/engine.js +5 -3
- package/src/engineAudio.js +4 -4
- package/src/engineDebug.js +3 -7
- package/src/engineDraw.js +50 -25
- package/src/engineExport.js +4 -1
- package/src/engineInput.js +1 -1
- package/src/engineObject.js +8 -8
- package/src/engineParticles.js +6 -6
- package/src/engineSettings.js +5 -5
- package/src/engineTileLayer.js +2 -2
- package/src/engineUtilities.js +5 -5
- package/src/engineWebGL.js +52 -74
- package/examples/electron/build.js +0 -107
- package/examples/electron/electron.js +0 -43
- package/examples/electron/game.js +0 -131
- package/examples/electron/index.html +0 -13
- package/examples/electron/package.json +0 -22
- package/examples/electron/tiles.png +0 -0
- package/examples/js13k/build.bat +0 -2
- package/examples/js13k/build.js +0 -131
- package/examples/js13k/game.js +0 -118
- package/examples/js13k/index.html +0 -21
- package/examples/js13k/tiles.png +0 -0
- package/examples/typescript/build.bat +0 -5
- package/examples/typescript/build.js +0 -33
- package/examples/typescript/game.js +0 -102
- package/examples/typescript/game.ts +0 -134
- package/examples/typescript/index.html +0 -10
- package/examples/typescript/tiles.png +0 -0
- package/examples/typescript/tsconfig.json +0 -8
package/dist/littlejs.release.js
CHANGED
|
@@ -54,7 +54,7 @@ function debugSaveDataURL(){}
|
|
|
54
54
|
* @memberof Utilities */
|
|
55
55
|
const PI = Math.PI;
|
|
56
56
|
|
|
57
|
-
/** Returns
|
|
57
|
+
/** Returns absolute value of value passed in
|
|
58
58
|
* @param {Number} value
|
|
59
59
|
* @return {Number}
|
|
60
60
|
* @memberof Utilities */
|
|
@@ -87,7 +87,7 @@ function sign(value) { return Math.sign(value); }
|
|
|
87
87
|
* @memberof Utilities */
|
|
88
88
|
function mod(dividend, divisor=1) { return ((dividend % divisor) + divisor) % divisor; }
|
|
89
89
|
|
|
90
|
-
/** Clamps the value
|
|
90
|
+
/** Clamps the value between max and min
|
|
91
91
|
* @param {Number} value
|
|
92
92
|
* @param {Number} [min]
|
|
93
93
|
* @param {Number} [max]
|
|
@@ -523,7 +523,7 @@ class Vector2
|
|
|
523
523
|
return new Vector2(this.x*c - this.y*s, this.x*s + this.y*c);
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
-
/** Set the integer direction of this vector,
|
|
526
|
+
/** Set the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
|
|
527
527
|
* @param {Number} [direction]
|
|
528
528
|
* @param {Number} [length] */
|
|
529
529
|
setDirection(direction, length=1)
|
|
@@ -534,7 +534,7 @@ class Vector2
|
|
|
534
534
|
direction%2 ? 0 : direction ? -length : length);
|
|
535
535
|
}
|
|
536
536
|
|
|
537
|
-
/** Returns the integer direction of this vector,
|
|
537
|
+
/** Returns the integer direction of this vector, corresponding to multiples of 90 degree rotation (0-3)
|
|
538
538
|
* @return {Number} */
|
|
539
539
|
direction()
|
|
540
540
|
{ return abs(this.x) > abs(this.y) ? this.x < 0 ? 3 : 1 : this.y < 0 ? 2 : 0; }
|
|
@@ -919,7 +919,7 @@ const MAGENTA = rgb(1,0,1);
|
|
|
919
919
|
* a.set(3); // sets the timer to 3 seconds
|
|
920
920
|
*
|
|
921
921
|
* let b = new Timer(1); // creates a timer with 1 second left
|
|
922
|
-
* b.unset(); //
|
|
922
|
+
* b.unset(); // unset the timer
|
|
923
923
|
*/
|
|
924
924
|
class Timer
|
|
925
925
|
{
|
|
@@ -1070,7 +1070,7 @@ let tileFixBleedScale = 0;
|
|
|
1070
1070
|
* @memberof Settings */
|
|
1071
1071
|
let enablePhysicsSolver = true;
|
|
1072
1072
|
|
|
1073
|
-
/** Default object mass for collision
|
|
1073
|
+
/** Default object mass for collision calculations (how heavy objects are)
|
|
1074
1074
|
* @type {Number}
|
|
1075
1075
|
* @default
|
|
1076
1076
|
* @memberof Settings */
|
|
@@ -1281,7 +1281,7 @@ function setFontDefault(font) { fontDefault = font; }
|
|
|
1281
1281
|
* @memberof Settings */
|
|
1282
1282
|
function setShowSplashScreen(show) { showSplashScreen = show; }
|
|
1283
1283
|
|
|
1284
|
-
/** Set to
|
|
1284
|
+
/** Set to disable rendering, audio, and input for servers
|
|
1285
1285
|
* @param {Boolean} headless
|
|
1286
1286
|
* @memberof Settings */
|
|
1287
1287
|
function setHeadlessMode(headless) { headlessMode = headless; }
|
|
@@ -1311,7 +1311,7 @@ function setTileFixBleedScale(scale) { tileFixBleedScale = scale; }
|
|
|
1311
1311
|
* @memberof Settings */
|
|
1312
1312
|
function setEnablePhysicsSolver(enable) { enablePhysicsSolver = enable; }
|
|
1313
1313
|
|
|
1314
|
-
/** Set default object mass for
|
|
1314
|
+
/** Set default object mass for collision calculations
|
|
1315
1315
|
* @param {Number} mass
|
|
1316
1316
|
* @memberof Settings */
|
|
1317
1317
|
function setObjectDefaultMass(mass) { objectDefaultMass = mass; }
|
|
@@ -1381,7 +1381,7 @@ function setTouchGamepadEnable(enable) { touchGamepadEnable = enable; }
|
|
|
1381
1381
|
* @memberof Settings */
|
|
1382
1382
|
function setTouchGamepadAnalog(analog) { touchGamepadAnalog = analog; }
|
|
1383
1383
|
|
|
1384
|
-
/** Set size of
|
|
1384
|
+
/** Set size of virtual gamepad for touch devices in pixels
|
|
1385
1385
|
* @param {Number} size
|
|
1386
1386
|
* @memberof Settings */
|
|
1387
1387
|
function setTouchGamepadSize(size) { touchGamepadSize = size; }
|
|
@@ -1408,7 +1408,7 @@ function setSoundVolume(volume)
|
|
|
1408
1408
|
{
|
|
1409
1409
|
soundVolume = volume;
|
|
1410
1410
|
if (soundEnable && !headlessMode && audioGainNode)
|
|
1411
|
-
audioGainNode.gain.value = volume; // update gain
|
|
1411
|
+
audioGainNode.gain.value = volume; // update gain immediately
|
|
1412
1412
|
}
|
|
1413
1413
|
|
|
1414
1414
|
/** Set default range where sound no longer plays
|
|
@@ -1612,7 +1612,7 @@ class EngineObject
|
|
|
1612
1612
|
const oldPos = this.pos.copy();
|
|
1613
1613
|
this.velocity.x *= this.damping;
|
|
1614
1614
|
this.velocity.y *= this.damping;
|
|
1615
|
-
if (this.mass) //
|
|
1615
|
+
if (this.mass) // don't apply gravity to static objects
|
|
1616
1616
|
this.velocity.y += gravity * this.gravityScale;
|
|
1617
1617
|
this.pos.x += this.velocity.x;
|
|
1618
1618
|
this.pos.y += this.velocity.y;
|
|
@@ -1621,7 +1621,7 @@ class EngineObject
|
|
|
1621
1621
|
// physics sanity checks
|
|
1622
1622
|
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1);
|
|
1623
1623
|
ASSERT(this.damping >= 0 && this.damping <= 1);
|
|
1624
|
-
if (!enablePhysicsSolver || !this.mass) //
|
|
1624
|
+
if (!enablePhysicsSolver || !this.mass) // don't do collision for static objects
|
|
1625
1625
|
return;
|
|
1626
1626
|
|
|
1627
1627
|
const wasMovingDown = this.velocity.y < 0;
|
|
@@ -1640,7 +1640,7 @@ class EngineObject
|
|
|
1640
1640
|
const epsilon = .001; // necessary to push slightly outside of the collision
|
|
1641
1641
|
for (const o of engineObjectsCollide)
|
|
1642
1642
|
{
|
|
1643
|
-
// non solid objects don't collide with
|
|
1643
|
+
// non solid objects don't collide with each other
|
|
1644
1644
|
if (!this.isSolid && !o.isSolid || o.destroyed || o.parent || o == this)
|
|
1645
1645
|
continue;
|
|
1646
1646
|
|
|
@@ -1700,7 +1700,7 @@ class EngineObject
|
|
|
1700
1700
|
const elastic1 = o.velocity.y * (o.mass - this.mass) / (this.mass + o.mass)
|
|
1701
1701
|
+ this.velocity.y * 2 * this.mass / (this.mass + o.mass);
|
|
1702
1702
|
|
|
1703
|
-
// lerp
|
|
1703
|
+
// lerp between elastic or inelastic based on elasticity
|
|
1704
1704
|
this.velocity.y = lerp(elasticity, inelastic, elastic0);
|
|
1705
1705
|
o.velocity.y = lerp(elasticity, inelastic, elastic1);
|
|
1706
1706
|
}
|
|
@@ -1720,7 +1720,7 @@ class EngineObject
|
|
|
1720
1720
|
const elastic1 = o.velocity.x * (o.mass - this.mass) / (this.mass + o.mass)
|
|
1721
1721
|
+ this.velocity.x * 2 * this.mass / (this.mass + o.mass);
|
|
1722
1722
|
|
|
1723
|
-
// lerp
|
|
1723
|
+
// lerp between elastic or inelastic based on elasticity
|
|
1724
1724
|
this.velocity.x = lerp(elasticity, inelastic, elastic0);
|
|
1725
1725
|
o.velocity.x = lerp(elasticity, inelastic, elastic1);
|
|
1726
1726
|
}
|
|
@@ -1786,7 +1786,7 @@ class EngineObject
|
|
|
1786
1786
|
if (this.destroyed)
|
|
1787
1787
|
return;
|
|
1788
1788
|
|
|
1789
|
-
// disconnect from parent and destroy
|
|
1789
|
+
// disconnect from parent and destroy children
|
|
1790
1790
|
this.destroyed = 1;
|
|
1791
1791
|
this.parent && this.parent.removeChild(this);
|
|
1792
1792
|
for (const child of this.children)
|
|
@@ -1811,7 +1811,7 @@ class EngineObject
|
|
|
1811
1811
|
|
|
1812
1812
|
/** Called to check if a tile collision should be resolved
|
|
1813
1813
|
* @param {Number} tileData - the value of the tile at the position
|
|
1814
|
-
* @param {Vector2} pos - tile where the collision
|
|
1814
|
+
* @param {Vector2} pos - tile where the collision occurred
|
|
1815
1815
|
* @return {Boolean} - true if the collision should be resolved */
|
|
1816
1816
|
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
1817
1817
|
|
|
@@ -1874,7 +1874,7 @@ class EngineObject
|
|
|
1874
1874
|
this.collideRaycast = collideRaycast;
|
|
1875
1875
|
}
|
|
1876
1876
|
|
|
1877
|
-
/** Returns string
|
|
1877
|
+
/** Returns string containing info about this object for debugging
|
|
1878
1878
|
* @return {String} */
|
|
1879
1879
|
toString()
|
|
1880
1880
|
{
|
|
@@ -1960,7 +1960,7 @@ let overlayContext;
|
|
|
1960
1960
|
let mainCanvasSize = vec2();
|
|
1961
1961
|
|
|
1962
1962
|
/** Array containing texture info for batch rendering system
|
|
1963
|
-
* @type {Array}
|
|
1963
|
+
* @type {Array<TextureInfo>}
|
|
1964
1964
|
* @memberof Draw */
|
|
1965
1965
|
let textureInfos = [];
|
|
1966
1966
|
|
|
@@ -1999,11 +1999,13 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
|
|
|
1999
1999
|
|
|
2000
2000
|
// use pos as a tile index
|
|
2001
2001
|
const textureInfo = textureInfos[textureIndex];
|
|
2002
|
-
ASSERT(textureInfo, 'Texture not loaded');
|
|
2002
|
+
ASSERT(!!textureInfo, 'Texture not loaded');
|
|
2003
2003
|
const sizePadded = size.add(vec2(padding*2));
|
|
2004
|
-
const cols = textureInfo.size.x / sizePadded.x |0;
|
|
2005
2004
|
if (typeof pos === 'number')
|
|
2006
|
-
|
|
2005
|
+
{
|
|
2006
|
+
const cols = textureInfo.size.x / sizePadded.x |0;
|
|
2007
|
+
pos = cols>0 ? vec2(pos%cols, pos/cols|0) : vec2();
|
|
2008
|
+
}
|
|
2007
2009
|
pos = vec2(pos.x*sizePadded.x+padding, pos.y*sizePadded.y+padding);
|
|
2008
2010
|
|
|
2009
2011
|
// return a tile info object
|
|
@@ -2309,24 +2311,6 @@ function drawCanvas2D(pos, size, angle, mirror, drawFunction, screenSpace, conte
|
|
|
2309
2311
|
context.restore();
|
|
2310
2312
|
}
|
|
2311
2313
|
|
|
2312
|
-
/** Enable normal or additive blend mode
|
|
2313
|
-
* @param {Boolean} [additive]
|
|
2314
|
-
* @param {Boolean} [useWebGL=glEnable]
|
|
2315
|
-
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
|
|
2316
|
-
* @memberof Draw */
|
|
2317
|
-
function setBlendMode(additive, useWebGL=glEnable, context)
|
|
2318
|
-
{
|
|
2319
|
-
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
2320
|
-
if (useWebGL)
|
|
2321
|
-
glAdditive = additive;
|
|
2322
|
-
else
|
|
2323
|
-
{
|
|
2324
|
-
if (!context)
|
|
2325
|
-
context = mainContext;
|
|
2326
|
-
context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
2327
|
-
}
|
|
2328
|
-
}
|
|
2329
|
-
|
|
2330
2314
|
/** Draw text on main canvas in world space
|
|
2331
2315
|
* Automatically splits new lines into rows
|
|
2332
2316
|
* @param {String} text
|
|
@@ -2394,6 +2378,38 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
|
|
|
2394
2378
|
});
|
|
2395
2379
|
}
|
|
2396
2380
|
|
|
2381
|
+
/** Enable normal or additive blend mode
|
|
2382
|
+
* @param {Boolean} [additive]
|
|
2383
|
+
* @param {Boolean} [useWebGL=glEnable]
|
|
2384
|
+
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=mainContext]
|
|
2385
|
+
* @memberof Draw */
|
|
2386
|
+
function setBlendMode(additive, useWebGL=glEnable, context)
|
|
2387
|
+
{
|
|
2388
|
+
ASSERT(!context || !useWebGL, 'context only supported in canvas 2D mode');
|
|
2389
|
+
if (useWebGL)
|
|
2390
|
+
glAdditive = additive;
|
|
2391
|
+
else
|
|
2392
|
+
{
|
|
2393
|
+
if (!context)
|
|
2394
|
+
context = mainContext;
|
|
2395
|
+
context.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
/** Combines all LittleJS canvases onto the main canvas and clears them
|
|
2400
|
+
* This is necessary for things like saving a screenshot
|
|
2401
|
+
* @memberof Draw */
|
|
2402
|
+
function combineCanvases()
|
|
2403
|
+
{
|
|
2404
|
+
// combine canvases
|
|
2405
|
+
glCopyToContext(mainContext, true);
|
|
2406
|
+
mainContext.drawImage(overlayCanvas, 0, 0);
|
|
2407
|
+
|
|
2408
|
+
// clear canvases
|
|
2409
|
+
glClearCanvas();
|
|
2410
|
+
overlayCanvas.width |= 0;
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2397
2413
|
///////////////////////////////////////////////////////////////////////////////
|
|
2398
2414
|
|
|
2399
2415
|
let engineFontImage;
|
|
@@ -2405,7 +2421,7 @@ let engineFontImage;
|
|
|
2405
2421
|
* - You can also use fonts from the main tile sheet
|
|
2406
2422
|
* @example
|
|
2407
2423
|
* // use built in font
|
|
2408
|
-
* const font = new
|
|
2424
|
+
* const font = new FontImage;
|
|
2409
2425
|
*
|
|
2410
2426
|
* // draw text
|
|
2411
2427
|
* font.drawTextScreen("LittleJS\nHello World!", vec2(200, 50));
|
|
@@ -2480,14 +2496,14 @@ class FontImage
|
|
|
2480
2496
|
}
|
|
2481
2497
|
|
|
2482
2498
|
///////////////////////////////////////////////////////////////////////////////
|
|
2483
|
-
//
|
|
2499
|
+
// Display functions
|
|
2484
2500
|
|
|
2485
2501
|
/** Returns true if fullscreen mode is active
|
|
2486
2502
|
* @return {Boolean}
|
|
2487
2503
|
* @memberof Draw */
|
|
2488
2504
|
function isFullscreen() { return !!document.fullscreenElement; }
|
|
2489
2505
|
|
|
2490
|
-
/** Toggle
|
|
2506
|
+
/** Toggle fullscreen mode
|
|
2491
2507
|
* @memberof Draw */
|
|
2492
2508
|
function toggleFullscreen()
|
|
2493
2509
|
{
|
|
@@ -2499,6 +2515,15 @@ function toggleFullscreen()
|
|
|
2499
2515
|
}
|
|
2500
2516
|
else if (rootElement.requestFullscreen)
|
|
2501
2517
|
rootElement.requestFullscreen();
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
/** Set the cursor style
|
|
2521
|
+
* @param {String} cursorStyle - CSS cursor style (auto, none, crosshair, etc)
|
|
2522
|
+
* @memberof Draw */
|
|
2523
|
+
function setCursor(cursorStyle = 'auto')
|
|
2524
|
+
{
|
|
2525
|
+
const rootElement = mainCanvas.parentElement;
|
|
2526
|
+
rootElement.style.cursor = cursorStyle;
|
|
2502
2527
|
}
|
|
2503
2528
|
/**
|
|
2504
2529
|
* LittleJS Input System
|
|
@@ -2843,7 +2868,7 @@ function vibrateStop() { vibrate(0); }
|
|
|
2843
2868
|
|
|
2844
2869
|
/** True if a touch device has been detected
|
|
2845
2870
|
* @memberof Input */
|
|
2846
|
-
const isTouchDevice = window.ontouchstart !== undefined;
|
|
2871
|
+
const isTouchDevice = !headlessMode && window.ontouchstart !== undefined;
|
|
2847
2872
|
|
|
2848
2873
|
// touch gamepad internal variables
|
|
2849
2874
|
let touchGamepadTimer = new Timer, touchGamepadButtons, touchGamepadStick;
|
|
@@ -3041,7 +3066,7 @@ function audioInit()
|
|
|
3041
3066
|
{
|
|
3042
3067
|
if (!soundEnable || headlessMode) return;
|
|
3043
3068
|
|
|
3044
|
-
// (createGain is more widely
|
|
3069
|
+
// (createGain is more widely supported then GainNode constructor)
|
|
3045
3070
|
audioGainNode = audioContext.createGain();
|
|
3046
3071
|
audioGainNode.connect(audioContext.destination);
|
|
3047
3072
|
audioGainNode.gain.value = soundVolume; // set starting value
|
|
@@ -3391,7 +3416,7 @@ const zzfxR = 44100;
|
|
|
3391
3416
|
* @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
|
|
3392
3417
|
* @param {Number} [release] - Release time, how fast sound fades out (seconds)
|
|
3393
3418
|
* @param {Number} [shape] - Shape of the sound wave
|
|
3394
|
-
* @param {Number} [shapeCurve] -
|
|
3419
|
+
* @param {Number} [shapeCurve] - Squareness of wave (0=square, 1=normal, 2=pointy)
|
|
3395
3420
|
* @param {Number} [slide] - How much to slide frequency (kHz/s)
|
|
3396
3421
|
* @param {Number} [deltaSlide] - How much to change slide (kHz/s/s)
|
|
3397
3422
|
* @param {Number} [pitchJump] - Frequency of pitch jump (Hz)
|
|
@@ -3403,7 +3428,7 @@ const zzfxR = 44100;
|
|
|
3403
3428
|
* @param {Number} [delay] - Overlap sound with itself for reverb and flanger effects (seconds)
|
|
3404
3429
|
* @param {Number} [sustainVolume] - Volume level for sustain (percent)
|
|
3405
3430
|
* @param {Number} [decay] - Decay time, how long to reach sustain after attack (seconds)
|
|
3406
|
-
* @param {Number} [tremolo] - Trembling effect, rate controlled by repeat time (
|
|
3431
|
+
* @param {Number} [tremolo] - Trembling effect, rate controlled by repeat time (percent)
|
|
3407
3432
|
* @param {Number} [filter] - Filter cutoff frequency, positive for HPF, negative for LPF (Hz)
|
|
3408
3433
|
* @return {Array} - Array of audio samples
|
|
3409
3434
|
* @memberof Audio
|
|
@@ -3507,7 +3532,7 @@ function zzfxG
|
|
|
3507
3532
|
// ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
|
|
3508
3533
|
|
|
3509
3534
|
/** Generate samples for a ZzFM song with given parameters
|
|
3510
|
-
* @param {Array} instruments - Array of ZzFX sound
|
|
3535
|
+
* @param {Array} instruments - Array of ZzFX sound parameters
|
|
3511
3536
|
* @param {Array} patterns - Array of pattern data
|
|
3512
3537
|
* @param {Array} sequence - Array of pattern indexes
|
|
3513
3538
|
* @param {Number} [BPM] - Playback speed of the song in BPM
|
|
@@ -3910,7 +3935,7 @@ class TileLayer extends EngineObject
|
|
|
3910
3935
|
|
|
3911
3936
|
/** Draw the tile at a given position in the tile grid
|
|
3912
3937
|
* This can be used to clear out tiles when they are destroyed
|
|
3913
|
-
* Tiles can also be redrawn if
|
|
3938
|
+
* Tiles can also be redrawn if inside a redrawStart/End block
|
|
3914
3939
|
* @param {Vector2} layerPos
|
|
3915
3940
|
* @param {Boolean} [clear] - should the old tile be cleared out
|
|
3916
3941
|
*/
|
|
@@ -3935,7 +3960,7 @@ class TileLayer extends EngineObject
|
|
|
3935
3960
|
}
|
|
3936
3961
|
}
|
|
3937
3962
|
|
|
3938
|
-
/** Draw directly to the 2D canvas in world space (
|
|
3963
|
+
/** Draw directly to the 2D canvas in world space (bypass webgl)
|
|
3939
3964
|
* @param {Vector2} pos
|
|
3940
3965
|
* @param {Vector2} size
|
|
3941
3966
|
* @param {Number} angle
|
|
@@ -4005,7 +4030,7 @@ class TileLayer extends EngineObject
|
|
|
4005
4030
|
* let pos = vec2(2,3);
|
|
4006
4031
|
* let particleEmitter = new ParticleEmitter
|
|
4007
4032
|
* (
|
|
4008
|
-
* pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate,
|
|
4033
|
+
* pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emitCone
|
|
4009
4034
|
* tile(0, 16), // tileInfo
|
|
4010
4035
|
* rgb(1,1,1), rgb(0,0,0), // colorStartA, colorStartB
|
|
4011
4036
|
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
@@ -4040,7 +4065,7 @@ class ParticleEmitter extends EngineObject
|
|
|
4040
4065
|
* @param {Number} [fadeRate] - How quick to fade particles at start/end in percent of life
|
|
4041
4066
|
* @param {Number} [randomness] - Apply extra randomness percent
|
|
4042
4067
|
* @param {Boolean} [collideTiles] - Do particles collide against tiles
|
|
4043
|
-
* @param {Boolean} [additive] - Should particles use
|
|
4068
|
+
* @param {Boolean} [additive] - Should particles use additive blend
|
|
4044
4069
|
* @param {Boolean} [randomColorLinear] - Should color be randomized linearly or across each component
|
|
4045
4070
|
* @param {Number} [renderOrder] - Render order for particles (additive is above other stuff by default)
|
|
4046
4071
|
* @param {Boolean} [localSpace] - Should it be in local space of emitter (world space is default)
|
|
@@ -4125,11 +4150,11 @@ class ParticleEmitter extends EngineObject
|
|
|
4125
4150
|
this.randomness = randomness;
|
|
4126
4151
|
/** @property {Boolean} - Do particles collide against tiles */
|
|
4127
4152
|
this.collideTiles = collideTiles;
|
|
4128
|
-
/** @property {Boolean} - Should particles use
|
|
4153
|
+
/** @property {Boolean} - Should particles use additive blend */
|
|
4129
4154
|
this.additive = additive;
|
|
4130
4155
|
/** @property {Boolean} - Should it be in local space of emitter */
|
|
4131
4156
|
this.localSpace = localSpace;
|
|
4132
|
-
/** @property {Number} - If non zero the
|
|
4157
|
+
/** @property {Number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
|
|
4133
4158
|
this.trailScale = 0;
|
|
4134
4159
|
/** @property {Function} - Callback when particle is destroyed */
|
|
4135
4160
|
this.particleDestroyCallback = undefined;
|
|
@@ -4178,7 +4203,7 @@ class ParticleEmitter extends EngineObject
|
|
|
4178
4203
|
angle += this.angle;
|
|
4179
4204
|
}
|
|
4180
4205
|
|
|
4181
|
-
// randomness scales each
|
|
4206
|
+
// randomness scales each parameter by a percentage
|
|
4182
4207
|
const randomness = this.randomness;
|
|
4183
4208
|
const randomizeScale = (v)=> v + v*rand(randomness, -randomness);
|
|
4184
4209
|
|
|
@@ -4207,7 +4232,7 @@ class ParticleEmitter extends EngineObject
|
|
|
4207
4232
|
particle.renderOrder = this.renderOrder;
|
|
4208
4233
|
particle.mirror = !!randInt(2);
|
|
4209
4234
|
|
|
4210
|
-
// call particle create
|
|
4235
|
+
// call particle create callback
|
|
4211
4236
|
this.particleCreateCallback && this.particleCreateCallback(particle);
|
|
4212
4237
|
|
|
4213
4238
|
// return the newly created particle
|
|
@@ -4527,7 +4552,7 @@ let glCanvas;
|
|
|
4527
4552
|
* @memberof WebGL */
|
|
4528
4553
|
let glContext;
|
|
4529
4554
|
|
|
4530
|
-
/**
|
|
4555
|
+
/** Should webgl be setup with anti-aliasing? must be set before calling engineInit
|
|
4531
4556
|
* @type {Boolean}
|
|
4532
4557
|
* @memberof WebGL */
|
|
4533
4558
|
let glAntialias = true;
|
|
@@ -4535,9 +4560,15 @@ let glAntialias = true;
|
|
|
4535
4560
|
// WebGL internal variables not exposed to documentation
|
|
4536
4561
|
let glShader, glActiveTexture, glArrayBuffer, glGeometryBuffer, glPositionData, glColorData, glInstanceCount, glAdditive, glBatchAdditive;
|
|
4537
4562
|
|
|
4563
|
+
// WebGL internal constants
|
|
4564
|
+
const gl_MAX_INSTANCES = 1e4;
|
|
4565
|
+
const gl_INDICES_PER_INSTANCE = 11;
|
|
4566
|
+
const gl_INSTANCE_BYTE_STRIDE = gl_INDICES_PER_INSTANCE * 4;
|
|
4567
|
+
const gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
|
|
4568
|
+
|
|
4538
4569
|
///////////////////////////////////////////////////////////////////////////////
|
|
4539
4570
|
|
|
4540
|
-
//
|
|
4571
|
+
// Initialize WebGL, called automatically by the engine
|
|
4541
4572
|
function glInit()
|
|
4542
4573
|
{
|
|
4543
4574
|
if (!glEnable || headlessMode) return;
|
|
@@ -4587,8 +4618,8 @@ function glInit()
|
|
|
4587
4618
|
|
|
4588
4619
|
// create the geometry buffer, triangle strip square
|
|
4589
4620
|
const geometry = new Float32Array([glInstanceCount=0,0,1,0,0,1,1,1]);
|
|
4590
|
-
glContext.bindBuffer(
|
|
4591
|
-
glContext.bufferData(
|
|
4621
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
4622
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, geometry, glContext.STATIC_DRAW);
|
|
4592
4623
|
}
|
|
4593
4624
|
|
|
4594
4625
|
// Setup render each frame, called automatically by engine
|
|
@@ -4596,15 +4627,12 @@ function glPreRender()
|
|
|
4596
4627
|
{
|
|
4597
4628
|
if (!glEnable || headlessMode) return;
|
|
4598
4629
|
|
|
4599
|
-
//
|
|
4600
|
-
|
|
4601
|
-
glContext.clear(gl_COLOR_BUFFER_BIT);
|
|
4602
|
-
|
|
4603
|
-
// set up the shader
|
|
4630
|
+
// set up the shader and canvas
|
|
4631
|
+
glClearCanvas();
|
|
4604
4632
|
glContext.useProgram(glShader);
|
|
4605
|
-
glContext.activeTexture(
|
|
4633
|
+
glContext.activeTexture(glContext.TEXTURE0);
|
|
4606
4634
|
if (textureInfos[0])
|
|
4607
|
-
glContext.bindTexture(
|
|
4635
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
|
|
4608
4636
|
|
|
4609
4637
|
// set vertex attributes
|
|
4610
4638
|
let offset = glAdditive = glBatchAdditive = 0;
|
|
@@ -4619,15 +4647,15 @@ function glPreRender()
|
|
|
4619
4647
|
glContext.vertexAttribDivisor(location, divisor);
|
|
4620
4648
|
offset += size*typeSize;
|
|
4621
4649
|
}
|
|
4622
|
-
glContext.bindBuffer(
|
|
4623
|
-
initVertexAttribArray('g',
|
|
4624
|
-
glContext.bindBuffer(
|
|
4625
|
-
glContext.bufferData(
|
|
4626
|
-
initVertexAttribArray('p',
|
|
4627
|
-
initVertexAttribArray('u',
|
|
4628
|
-
initVertexAttribArray('c',
|
|
4629
|
-
initVertexAttribArray('a',
|
|
4630
|
-
initVertexAttribArray('r',
|
|
4650
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glGeometryBuffer);
|
|
4651
|
+
initVertexAttribArray('g', glContext.FLOAT, 0, 2); // geometry
|
|
4652
|
+
glContext.bindBuffer(glContext.ARRAY_BUFFER, glArrayBuffer);
|
|
4653
|
+
glContext.bufferData(glContext.ARRAY_BUFFER, gl_INSTANCE_BUFFER_SIZE, glContext.DYNAMIC_DRAW);
|
|
4654
|
+
initVertexAttribArray('p', glContext.FLOAT, 4, 4); // position & size
|
|
4655
|
+
initVertexAttribArray('u', glContext.FLOAT, 4, 4); // texture coords
|
|
4656
|
+
initVertexAttribArray('c', glContext.UNSIGNED_BYTE, 1, 4); // color
|
|
4657
|
+
initVertexAttribArray('a', glContext.UNSIGNED_BYTE, 1, 4); // additiveColor
|
|
4658
|
+
initVertexAttribArray('r', glContext.FLOAT, 4, 1); // rotation
|
|
4631
4659
|
|
|
4632
4660
|
// build the transform matrix
|
|
4633
4661
|
const s = vec2(2*cameraScale).divide(mainCanvasSize);
|
|
@@ -4642,6 +4670,15 @@ function glPreRender()
|
|
|
4642
4670
|
);
|
|
4643
4671
|
}
|
|
4644
4672
|
|
|
4673
|
+
/** Clear the canvas and setup the viewport
|
|
4674
|
+
* @memberof WebGL */
|
|
4675
|
+
function glClearCanvas()
|
|
4676
|
+
{
|
|
4677
|
+
// clear and set to same size as main canvas
|
|
4678
|
+
glContext.viewport(0, 0, glCanvas.width=mainCanvas.width, glCanvas.height=mainCanvas.height);
|
|
4679
|
+
glContext.clear(glContext.COLOR_BUFFER_BIT);
|
|
4680
|
+
}
|
|
4681
|
+
|
|
4645
4682
|
/** Set the WebGl texture, called automatically if using multiple textures
|
|
4646
4683
|
* - This may also flush the gl buffer resulting in more draw calls and worse performance
|
|
4647
4684
|
* @param {WebGLTexture} texture
|
|
@@ -4653,7 +4690,7 @@ function glSetTexture(texture)
|
|
|
4653
4690
|
return;
|
|
4654
4691
|
|
|
4655
4692
|
glFlush();
|
|
4656
|
-
glContext.bindTexture(
|
|
4693
|
+
glContext.bindTexture(glContext.TEXTURE_2D, glActiveTexture = texture);
|
|
4657
4694
|
}
|
|
4658
4695
|
|
|
4659
4696
|
/** Compile WebGL shader of the given type, will throw errors if in debug mode
|
|
@@ -4669,7 +4706,7 @@ function glCompileShader(source, type)
|
|
|
4669
4706
|
glContext.compileShader(shader);
|
|
4670
4707
|
|
|
4671
4708
|
// check for errors
|
|
4672
|
-
if (debug && !glContext.getShaderParameter(shader,
|
|
4709
|
+
if (debug && !glContext.getShaderParameter(shader, glContext.COMPILE_STATUS))
|
|
4673
4710
|
throw glContext.getShaderInfoLog(shader);
|
|
4674
4711
|
return shader;
|
|
4675
4712
|
}
|
|
@@ -4683,12 +4720,12 @@ function glCreateProgram(vsSource, fsSource)
|
|
|
4683
4720
|
{
|
|
4684
4721
|
// build the program
|
|
4685
4722
|
const program = glContext.createProgram();
|
|
4686
|
-
glContext.attachShader(program, glCompileShader(vsSource,
|
|
4687
|
-
glContext.attachShader(program, glCompileShader(fsSource,
|
|
4723
|
+
glContext.attachShader(program, glCompileShader(vsSource, glContext.VERTEX_SHADER));
|
|
4724
|
+
glContext.attachShader(program, glCompileShader(fsSource, glContext.FRAGMENT_SHADER));
|
|
4688
4725
|
glContext.linkProgram(program);
|
|
4689
4726
|
|
|
4690
4727
|
// check for errors
|
|
4691
|
-
if (debug && !glContext.getProgramParameter(program,
|
|
4728
|
+
if (debug && !glContext.getProgramParameter(program, glContext.LINK_STATUS))
|
|
4692
4729
|
throw glContext.getProgramInfoLog(program);
|
|
4693
4730
|
return program;
|
|
4694
4731
|
}
|
|
@@ -4701,20 +4738,20 @@ function glCreateTexture(image)
|
|
|
4701
4738
|
{
|
|
4702
4739
|
// build the texture
|
|
4703
4740
|
const texture = glContext.createTexture();
|
|
4704
|
-
glContext.bindTexture(
|
|
4741
|
+
glContext.bindTexture(glContext.TEXTURE_2D, texture);
|
|
4705
4742
|
if (image && image.width)
|
|
4706
|
-
glContext.texImage2D(
|
|
4743
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, glContext.RGBA, glContext.UNSIGNED_BYTE, image);
|
|
4707
4744
|
else
|
|
4708
4745
|
{
|
|
4709
4746
|
// create a white texture
|
|
4710
4747
|
const whitePixel = new Uint8Array([255, 255, 255, 255]);
|
|
4711
|
-
glContext.texImage2D(
|
|
4748
|
+
glContext.texImage2D(glContext.TEXTURE_2D, 0, glContext.RGBA, 1, 1, 0, glContext.RGBA, glContext.UNSIGNED_BYTE, whitePixel);
|
|
4712
4749
|
}
|
|
4713
4750
|
|
|
4714
4751
|
// use point filtering for pixelated rendering
|
|
4715
|
-
const filter = tilesPixelated ?
|
|
4716
|
-
glContext.texParameteri(
|
|
4717
|
-
glContext.texParameteri(
|
|
4752
|
+
const filter = tilesPixelated ? glContext.NEAREST : glContext.LINEAR;
|
|
4753
|
+
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MIN_FILTER, filter);
|
|
4754
|
+
glContext.texParameteri(glContext.TEXTURE_2D, glContext.TEXTURE_MAG_FILTER, filter);
|
|
4718
4755
|
return texture;
|
|
4719
4756
|
}
|
|
4720
4757
|
|
|
@@ -4724,20 +4761,20 @@ function glFlush()
|
|
|
4724
4761
|
{
|
|
4725
4762
|
if (!glInstanceCount) return;
|
|
4726
4763
|
|
|
4727
|
-
const destBlend = glBatchAdditive ?
|
|
4728
|
-
glContext.blendFuncSeparate(
|
|
4729
|
-
glContext.enable(
|
|
4764
|
+
const destBlend = glBatchAdditive ? glContext.ONE : glContext.ONE_MINUS_SRC_ALPHA;
|
|
4765
|
+
glContext.blendFuncSeparate(glContext.SRC_ALPHA, destBlend, glContext.ONE, destBlend);
|
|
4766
|
+
glContext.enable(glContext.BLEND);
|
|
4730
4767
|
|
|
4731
4768
|
// draw all the sprites in the batch and reset the buffer
|
|
4732
|
-
glContext.bufferSubData(
|
|
4733
|
-
glContext.drawArraysInstanced(
|
|
4769
|
+
glContext.bufferSubData(glContext.ARRAY_BUFFER, 0, glPositionData);
|
|
4770
|
+
glContext.drawArraysInstanced(glContext.TRIANGLE_STRIP, 0, 4, glInstanceCount);
|
|
4734
4771
|
if (showWatermark)
|
|
4735
4772
|
drawCount += glInstanceCount;
|
|
4736
4773
|
glInstanceCount = 0;
|
|
4737
4774
|
glBatchAdditive = glAdditive;
|
|
4738
4775
|
}
|
|
4739
4776
|
|
|
4740
|
-
/** Draw any sprites still in the buffer
|
|
4777
|
+
/** Draw any sprites still in the buffer and copy to main canvas
|
|
4741
4778
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} context
|
|
4742
4779
|
* @param {Boolean} [forceDraw]
|
|
4743
4780
|
* @memberof WebGL */
|
|
@@ -4752,7 +4789,7 @@ function glCopyToContext(context, forceDraw=false)
|
|
|
4752
4789
|
context.drawImage(glCanvas, 0, 0);
|
|
4753
4790
|
}
|
|
4754
4791
|
|
|
4755
|
-
/** Set
|
|
4792
|
+
/** Set anti-aliasing for webgl canvas
|
|
4756
4793
|
* @param {Boolean} [antialias]
|
|
4757
4794
|
* @memberof WebGL */
|
|
4758
4795
|
function glSetAntialias(antialias=true)
|
|
@@ -4782,7 +4819,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
|
|
|
4782
4819
|
if (glInstanceCount >= gl_MAX_INSTANCES || glBatchAdditive != glAdditive)
|
|
4783
4820
|
glFlush();
|
|
4784
4821
|
|
|
4785
|
-
let offset = glInstanceCount *
|
|
4822
|
+
let offset = glInstanceCount++ * gl_INDICES_PER_INSTANCE;
|
|
4786
4823
|
glPositionData[offset++] = x;
|
|
4787
4824
|
glPositionData[offset++] = y;
|
|
4788
4825
|
glPositionData[offset++] = sizeX;
|
|
@@ -4794,41 +4831,7 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
|
|
|
4794
4831
|
glColorData[offset++] = rgba;
|
|
4795
4832
|
glColorData[offset++] = rgbaAdditive;
|
|
4796
4833
|
glPositionData[offset++] = angle;
|
|
4797
|
-
|
|
4798
|
-
}
|
|
4799
|
-
|
|
4800
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
4801
|
-
// store gl constants as integers so their name doesn't use space in minifed
|
|
4802
|
-
const
|
|
4803
|
-
gl_ONE = 1,
|
|
4804
|
-
gl_TRIANGLE_STRIP = 5,
|
|
4805
|
-
gl_SRC_ALPHA = 770,
|
|
4806
|
-
gl_ONE_MINUS_SRC_ALPHA = 771,
|
|
4807
|
-
gl_BLEND = 3042,
|
|
4808
|
-
gl_TEXTURE_2D = 3553,
|
|
4809
|
-
gl_UNSIGNED_BYTE = 5121,
|
|
4810
|
-
gl_FLOAT = 5126,
|
|
4811
|
-
gl_RGBA = 6408,
|
|
4812
|
-
gl_NEAREST = 9728,
|
|
4813
|
-
gl_LINEAR = 9729,
|
|
4814
|
-
gl_TEXTURE_MAG_FILTER = 10240,
|
|
4815
|
-
gl_TEXTURE_MIN_FILTER = 10241,
|
|
4816
|
-
gl_COLOR_BUFFER_BIT = 16384,
|
|
4817
|
-
gl_TEXTURE0 = 33984,
|
|
4818
|
-
gl_ARRAY_BUFFER = 34962,
|
|
4819
|
-
gl_STATIC_DRAW = 35044,
|
|
4820
|
-
gl_DYNAMIC_DRAW = 35048,
|
|
4821
|
-
gl_FRAGMENT_SHADER = 35632,
|
|
4822
|
-
gl_VERTEX_SHADER = 35633,
|
|
4823
|
-
gl_COMPILE_STATUS = 35713,
|
|
4824
|
-
gl_LINK_STATUS = 35714,
|
|
4825
|
-
gl_UNPACK_FLIP_Y_WEBGL = 37440,
|
|
4826
|
-
|
|
4827
|
-
// constants for batch rendering
|
|
4828
|
-
gl_INDICIES_PER_INSTANCE = 11,
|
|
4829
|
-
gl_MAX_INSTANCES = 1e4,
|
|
4830
|
-
gl_INSTANCE_BYTE_STRIDE = gl_INDICIES_PER_INSTANCE * 4, // 11 * 4
|
|
4831
|
-
gl_INSTANCE_BUFFER_SIZE = gl_MAX_INSTANCES * gl_INSTANCE_BYTE_STRIDE;
|
|
4834
|
+
}
|
|
4832
4835
|
/**
|
|
4833
4836
|
* LittleJS - The Tiny Fast JavaScript Game Engine
|
|
4834
4837
|
* MIT License - Copyright 2021 Frank Force
|
|
@@ -4861,7 +4864,7 @@ const engineName = 'LittleJS';
|
|
|
4861
4864
|
* @type {String}
|
|
4862
4865
|
* @default
|
|
4863
4866
|
* @memberof Engine */
|
|
4864
|
-
const engineVersion = '1.11.
|
|
4867
|
+
const engineVersion = '1.11.4';
|
|
4865
4868
|
|
|
4866
4869
|
/** Frames per second to update
|
|
4867
4870
|
* @type {Number}
|
|
@@ -4944,6 +4947,7 @@ function engineAddPlugin(updateFunction, renderFunction)
|
|
|
4944
4947
|
* @memberof Engine */
|
|
4945
4948
|
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
4946
4949
|
{
|
|
4950
|
+
ASSERT(!mainContext, 'engine already initialized');
|
|
4947
4951
|
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
4948
4952
|
|
|
4949
4953
|
// Called automatically by engine to setup render system
|
|
@@ -5110,7 +5114,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5110
5114
|
(canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
|
|
5111
5115
|
'user-select:none;' + // prevent hold to select
|
|
5112
5116
|
'-webkit-user-select:none;' + // compatibility for ios
|
|
5113
|
-
(!touchInputEnable ? '' : // no touch css
|
|
5117
|
+
(!touchInputEnable ? '' : // no touch css settings
|
|
5114
5118
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
5115
5119
|
'-webkit-touch-callout:none');// compatibility for ios
|
|
5116
5120
|
rootElement.style.cssText = styleRoot;
|
|
@@ -5139,6 +5143,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5139
5143
|
new Promise(resolve =>
|
|
5140
5144
|
{
|
|
5141
5145
|
const image = new Image;
|
|
5146
|
+
image.crossOrigin = 'anonymous';
|
|
5142
5147
|
image.onerror = image.onload = ()=>
|
|
5143
5148
|
{
|
|
5144
5149
|
textureInfos[textureIndex] = new TextureInfo(image);
|
|
@@ -5183,7 +5188,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5183
5188
|
* @memberof Engine */
|
|
5184
5189
|
function engineObjectsUpdate()
|
|
5185
5190
|
{
|
|
5186
|
-
// get list of solid objects for physics
|
|
5191
|
+
// get list of solid objects for physics optimization
|
|
5187
5192
|
engineObjectsCollide = engineObjects.filter(o=>o.collideSolidObjects);
|
|
5188
5193
|
|
|
5189
5194
|
// recursive object update
|