littlejsengine 1.9.9 → 1.10.2
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 +6 -0
- package/dist/littlejs.d.ts +107 -14
- package/dist/littlejs.esm.js +250 -102
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +226 -98
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +208 -95
- package/examples/box2d/game.js +29 -10
- package/examples/box2d/gameObjects.js +485 -0
- package/examples/box2d/index.html +7 -5
- package/examples/box2d/scenes.js +28 -247
- package/examples/box2d/tiles.png +0 -0
- package/examples/breakout/game.js +1 -1
- package/examples/breakout/index.html +5 -4
- package/examples/breakoutTutorial/index.html +4 -3
- package/examples/electron/game.js +1 -1
- package/examples/electron/index.html +4 -3
- package/examples/empty/game.js +1 -1
- package/examples/empty/index.html +2 -1
- package/examples/htmlMenu/game.js +67 -0
- package/examples/htmlMenu/index.html +56 -0
- package/examples/htmlMenu/tiles.png +0 -0
- package/examples/index.html +33 -0
- package/examples/js13k/game.js +1 -1
- package/examples/js13k/index.html +17 -14
- package/examples/module/game.js +1 -1
- package/examples/module/index.html +3 -2
- package/examples/particles/index.html +4 -3
- package/examples/platformer/game.js +1 -1
- package/examples/platformer/gameCharacter.js +1 -0
- package/examples/platformer/gameEffects.js +0 -2
- package/examples/platformer/gameLevel.js +2 -2
- package/examples/platformer/gameObjects.js +1 -2
- package/examples/platformer/index.html +10 -9
- package/examples/puzzle/game.js +1 -1
- package/examples/puzzle/index.html +4 -3
- package/examples/starter/game.js +5 -5
- package/examples/starter/index.html +15 -14
- package/examples/stress/index.html +3 -2
- package/examples/typescript/game.js +1 -1
- package/examples/typescript/index.html +3 -2
- package/examples/uiSystem/game.js +134 -0
- package/examples/uiSystem/index.html +25 -0
- package/examples/uiSystem/tiles.png +0 -0
- package/jsconfig.json +11 -0
- package/package.json +1 -1
- package/plugins/box2d.js +116 -42
- package/plugins/postProcess.js +6 -7
- package/plugins/uiSystem.js +243 -0
- package/src/engine.js +48 -20
- package/src/engineAudio.js +22 -25
- package/src/engineDebug.js +18 -3
- package/src/engineDraw.js +101 -27
- package/src/engineExport.js +23 -4
- package/src/engineInput.js +9 -5
- package/src/engineObject.js +15 -12
- package/src/engineSettings.js +1 -1
- package/src/engineUtilities.js +1 -1
- package/src/engineWebGL.js +11 -4
package/dist/littlejs.release.js
CHANGED
|
@@ -773,7 +773,7 @@ class Color
|
|
|
773
773
|
* @return {String} */
|
|
774
774
|
toString(useAlpha = true)
|
|
775
775
|
{
|
|
776
|
-
const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
|
|
776
|
+
const toHex = (c)=> ((c=clamp(c)*255|0)<16 ? '0' : '') + c.toString(16);
|
|
777
777
|
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
778
778
|
}
|
|
779
779
|
|
|
@@ -1003,7 +1003,7 @@ let tileSizeDefault = vec2(16);
|
|
|
1003
1003
|
* @type {Number}
|
|
1004
1004
|
* @default
|
|
1005
1005
|
* @memberof Settings */
|
|
1006
|
-
let tileFixBleedScale =
|
|
1006
|
+
let tileFixBleedScale = 0;
|
|
1007
1007
|
|
|
1008
1008
|
///////////////////////////////////////////////////////////////////////////////
|
|
1009
1009
|
// Object settings
|
|
@@ -1549,9 +1549,9 @@ class EngineObject
|
|
|
1549
1549
|
|
|
1550
1550
|
// apply physics
|
|
1551
1551
|
const oldPos = this.pos.copy();
|
|
1552
|
-
this.velocity.y += gravity * this.gravityScale;
|
|
1553
1552
|
this.pos.x += this.velocity.x *= this.damping;
|
|
1554
|
-
this.pos.y += this.velocity.y
|
|
1553
|
+
this.pos.y += this.velocity.y = this.damping * this.velocity.y
|
|
1554
|
+
+ gravity * this.gravityScale;
|
|
1555
1555
|
this.angle += this.angleVelocity *= this.angleDamping;
|
|
1556
1556
|
|
|
1557
1557
|
// physics sanity checks
|
|
@@ -1680,19 +1680,22 @@ class EngineObject
|
|
|
1680
1680
|
const isBlockedX = tileCollisionTest(vec2(this.pos.x, oldPos.y), this.size, this);
|
|
1681
1681
|
if (isBlockedY || !isBlockedX)
|
|
1682
1682
|
{
|
|
1683
|
-
// set if landed on ground
|
|
1684
|
-
this.groundObject = wasMovingDown;
|
|
1685
|
-
|
|
1686
1683
|
// bounce velocity
|
|
1687
1684
|
this.velocity.y *= -this.elasticity;
|
|
1688
1685
|
|
|
1689
|
-
//
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1686
|
+
// set if landed on ground
|
|
1687
|
+
if (this.groundObject = wasMovingDown)
|
|
1688
|
+
{
|
|
1689
|
+
// adjust position to slightly above nearest tile boundary
|
|
1690
|
+
// this prevents gap between object and ground
|
|
1691
|
+
const epsilon = .0001;
|
|
1692
|
+
this.pos.y = (oldPos.y-this.size.y/2|0)+this.size.y/2+epsilon;
|
|
1693
|
+
}
|
|
1694
|
+
else
|
|
1695
|
+
{
|
|
1696
|
+
// move to previous position
|
|
1697
|
+
this.pos.y = oldPos.y;
|
|
1698
|
+
}
|
|
1696
1699
|
}
|
|
1697
1700
|
if (isBlockedX)
|
|
1698
1701
|
{
|
|
@@ -1902,12 +1905,13 @@ let drawCount;
|
|
|
1902
1905
|
///////////////////////////////////////////////////////////////////////////////
|
|
1903
1906
|
|
|
1904
1907
|
/**
|
|
1905
|
-
* Create a tile info object
|
|
1908
|
+
* Create a tile info object using a grid based system
|
|
1906
1909
|
* - This can take vecs or floats for easier use and conversion
|
|
1907
1910
|
* - If an index is passed in, the tile size and index will determine the position
|
|
1908
|
-
* @param {(Number|Vector2)} [pos=
|
|
1911
|
+
* @param {(Number|Vector2)} [pos=0] - Index of tile in sheet
|
|
1909
1912
|
* @param {(Number|Vector2)} [size=tileSizeDefault] - Size of tile in pixels
|
|
1910
1913
|
* @param {Number} [textureIndex] - Texture index to use
|
|
1914
|
+
* @param {Number} [padding] - How many pixels padding around tiles
|
|
1911
1915
|
* @return {TileInfo}
|
|
1912
1916
|
* @example
|
|
1913
1917
|
* tile(2) // a tile at index 2 using the default tile size of 16
|
|
@@ -1916,7 +1920,7 @@ let drawCount;
|
|
|
1916
1920
|
* tile(vec2(4,8), vec2(30,10)) // a tile at pixel location (4,8) with a size of (30,10)
|
|
1917
1921
|
* @memberof Draw
|
|
1918
1922
|
*/
|
|
1919
|
-
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
1923
|
+
function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
|
|
1920
1924
|
{
|
|
1921
1925
|
if (headlessMode)
|
|
1922
1926
|
return new TileInfo;
|
|
@@ -1928,17 +1932,17 @@ function tile(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
|
1928
1932
|
size = vec2(size);
|
|
1929
1933
|
}
|
|
1930
1934
|
|
|
1931
|
-
//
|
|
1935
|
+
// use pos as a tile index
|
|
1936
|
+
const textureInfo = textureInfos[textureIndex];
|
|
1937
|
+
ASSERT(textureInfo, 'Texture not loaded');
|
|
1938
|
+
const sizePadded = size.add(vec2(padding*2));
|
|
1939
|
+
const cols = textureInfo.size.x / sizePadded.x |0;
|
|
1932
1940
|
if (typeof pos === 'number')
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
ASSERT(textureInfo, 'Texture not loaded');
|
|
1936
|
-
const cols = textureInfo.size.x / size.x |0;
|
|
1937
|
-
pos = vec2((pos%cols)*size.x, (pos/cols|0)*size.y);
|
|
1938
|
-
}
|
|
1941
|
+
pos = vec2(pos%cols, pos/cols|0);
|
|
1942
|
+
pos = vec2(pos.x*sizePadded.x+padding, pos.y*sizePadded.y+padding);
|
|
1939
1943
|
|
|
1940
1944
|
// return a tile info object
|
|
1941
|
-
return new TileInfo(pos, size, textureIndex);
|
|
1945
|
+
return new TileInfo(pos, size, textureIndex, padding);
|
|
1942
1946
|
}
|
|
1943
1947
|
|
|
1944
1948
|
/**
|
|
@@ -1950,8 +1954,9 @@ class TileInfo
|
|
|
1950
1954
|
* @param {Vector2} [pos=(0,0)] - Top left corner of tile in pixels
|
|
1951
1955
|
* @param {Vector2} [size=tileSizeDefault] - Size of tile in pixels
|
|
1952
1956
|
* @param {Number} [textureIndex] - Texture index to use
|
|
1957
|
+
* @param {Number} [padding] - How many pixels padding around tiles
|
|
1953
1958
|
*/
|
|
1954
|
-
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0)
|
|
1959
|
+
constructor(pos=vec2(), size=tileSizeDefault, textureIndex=0, padding=0)
|
|
1955
1960
|
{
|
|
1956
1961
|
/** @property {Vector2} - Top left corner of tile in pixels */
|
|
1957
1962
|
this.pos = pos.copy();
|
|
@@ -1959,6 +1964,8 @@ class TileInfo
|
|
|
1959
1964
|
this.size = size.copy();
|
|
1960
1965
|
/** @property {Number} - Texture index to use */
|
|
1961
1966
|
this.textureIndex = textureIndex;
|
|
1967
|
+
/** @property {Number} - How many pixels padding around tiles */
|
|
1968
|
+
this.padding = padding;
|
|
1962
1969
|
}
|
|
1963
1970
|
|
|
1964
1971
|
/** Returns a copy of this tile offset by a vector
|
|
@@ -1975,7 +1982,7 @@ class TileInfo
|
|
|
1975
1982
|
frame(frame)
|
|
1976
1983
|
{
|
|
1977
1984
|
ASSERT(typeof frame == 'number');
|
|
1978
|
-
return this.offset(vec2(frame*this.size.x, 0));
|
|
1985
|
+
return this.offset(vec2(frame*(this.size.x+this.padding*2), 0));
|
|
1979
1986
|
}
|
|
1980
1987
|
|
|
1981
1988
|
/** Returns the texture info for this tile
|
|
@@ -2000,8 +2007,6 @@ class TextureInfo
|
|
|
2000
2007
|
this.size = vec2(image.width, image.height);
|
|
2001
2008
|
/** @property {WebGLTexture} - webgl texture */
|
|
2002
2009
|
this.glTexture = glEnable && glCreateTexture(image);
|
|
2003
|
-
/** @property {Vector2} - size to adjust tile to fix bleeding */
|
|
2004
|
-
this.fixBleedSize = vec2(tileFixBleedScale).divide(this.size);
|
|
2005
2010
|
}
|
|
2006
2011
|
}
|
|
2007
2012
|
|
|
@@ -2070,11 +2075,12 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
2070
2075
|
if (textureInfo)
|
|
2071
2076
|
{
|
|
2072
2077
|
// calculate uvs and render
|
|
2073
|
-
const
|
|
2074
|
-
const
|
|
2075
|
-
const
|
|
2076
|
-
const
|
|
2077
|
-
const
|
|
2078
|
+
const sizeInverse = vec2(1).divide(textureInfo.size);
|
|
2079
|
+
const x = tileInfo.pos.x * sizeInverse.x;
|
|
2080
|
+
const y = tileInfo.pos.y * sizeInverse.y;
|
|
2081
|
+
const w = tileInfo.size.x * sizeInverse.x;
|
|
2082
|
+
const h = tileInfo.size.y * sizeInverse.y;
|
|
2083
|
+
const tileImageFixBleed = sizeInverse.scale(tileFixBleedScale);
|
|
2078
2084
|
glSetTexture(textureInfo.glTexture);
|
|
2079
2085
|
glDraw(pos.x, pos.y, mirror ? -size.x : size.x, size.y, angle,
|
|
2080
2086
|
x + tileImageFixBleed.x, y + tileImageFixBleed.y,
|
|
@@ -2091,6 +2097,7 @@ function drawTile(pos, size=vec2(1), tileInfo, color=new Color,
|
|
|
2091
2097
|
{
|
|
2092
2098
|
// normal canvas 2D rendering method (slower)
|
|
2093
2099
|
showWatermark && ++drawCount;
|
|
2100
|
+
size = vec2(size.x, -size.y); // fix upside down sprites
|
|
2094
2101
|
drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
2095
2102
|
{
|
|
2096
2103
|
if (textureInfo)
|
|
@@ -2128,6 +2135,74 @@ function drawRect(pos, size, color, angle, useWebGL, screenSpace, context)
|
|
|
2128
2135
|
drawTile(pos, size, undefined, color, angle, false, undefined, useWebGL, screenSpace, context);
|
|
2129
2136
|
}
|
|
2130
2137
|
|
|
2138
|
+
/** Draw colored polygon using passed in points
|
|
2139
|
+
* @param {Array} points - Array of Vector2 points
|
|
2140
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
2141
|
+
* @param {Number} [lineWidth=0]
|
|
2142
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2143
|
+
* @param {Boolean} [screenSpace=false]
|
|
2144
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2145
|
+
* @memberof Draw */
|
|
2146
|
+
function drawPoly(points, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
2147
|
+
{
|
|
2148
|
+
context.fillStyle = color.toString();
|
|
2149
|
+
context.beginPath();
|
|
2150
|
+
for (const point of screenSpace ? points : points.map(worldToScreen))
|
|
2151
|
+
context.lineTo(point.x, point.y);
|
|
2152
|
+
context.closePath();
|
|
2153
|
+
context.fill();
|
|
2154
|
+
if (lineWidth)
|
|
2155
|
+
{
|
|
2156
|
+
context.strokeStyle = lineColor.toString();
|
|
2157
|
+
context.lineWidth = screenSpace ? lineWidth : lineWidth*cameraScale;
|
|
2158
|
+
context.stroke();
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
/** Draw colored ellipse using passed in point
|
|
2163
|
+
* @param {Vector2} pos
|
|
2164
|
+
* @param {Number} [width=1]
|
|
2165
|
+
* @param {Number} [height=1]
|
|
2166
|
+
* @param {Number} [angle=0]
|
|
2167
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
2168
|
+
* @param {Number} [lineWidth=0]
|
|
2169
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2170
|
+
* @param {Boolean} [screenSpace=false]
|
|
2171
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2172
|
+
* @memberof Draw */
|
|
2173
|
+
function drawEllipse(pos, width=1, height=1, angle=0, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
2174
|
+
{
|
|
2175
|
+
if (!screenSpace)
|
|
2176
|
+
{
|
|
2177
|
+
pos = worldToScreen(pos);
|
|
2178
|
+
width *= cameraScale;
|
|
2179
|
+
height *= cameraScale;
|
|
2180
|
+
lineWidth *= cameraScale;
|
|
2181
|
+
}
|
|
2182
|
+
context.fillStyle = color.toString();
|
|
2183
|
+
context.beginPath();
|
|
2184
|
+
context.ellipse(pos.x, pos.y, width, height, angle, 0, 9);
|
|
2185
|
+
context.fill();
|
|
2186
|
+
if (lineWidth)
|
|
2187
|
+
{
|
|
2188
|
+
context.strokeStyle = lineColor.toString();
|
|
2189
|
+
context.lineWidth = lineWidth;
|
|
2190
|
+
context.stroke();
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
/** Draw colored circle using passed in point
|
|
2195
|
+
* @param {Vector2} pos
|
|
2196
|
+
* @param {Number} [radius=1]
|
|
2197
|
+
* @param {Color} [color=(1,1,1,1)]
|
|
2198
|
+
* @param {Number} [lineWidth=0]
|
|
2199
|
+
* @param {Color} [lineColor=(0,0,0,1)]
|
|
2200
|
+
* @param {Boolean} [screenSpace=false]
|
|
2201
|
+
* @param {CanvasRenderingContext2D} [context=mainContext]
|
|
2202
|
+
* @memberof Draw */
|
|
2203
|
+
function drawCircle(pos, radius=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), screenSpace, context=mainContext)
|
|
2204
|
+
{ drawEllipse(pos, radius, radius, 0, color, lineWidth, lineColor, screenSpace, context); }
|
|
2205
|
+
|
|
2131
2206
|
/** Draw colored line between two points
|
|
2132
2207
|
* @param {Vector2} posA
|
|
2133
2208
|
* @param {Vector2} posB
|
|
@@ -2198,10 +2273,11 @@ function setBlendMode(additive, useWebGL=glEnable, context)
|
|
|
2198
2273
|
* @param {CanvasTextAlign} [textAlign='center']
|
|
2199
2274
|
* @param {String} [font=fontDefault]
|
|
2200
2275
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
2276
|
+
* @param {Number} [maxWidth]
|
|
2201
2277
|
* @memberof Draw */
|
|
2202
|
-
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context)
|
|
2278
|
+
function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, font, context, maxWidth)
|
|
2203
2279
|
{
|
|
2204
|
-
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context);
|
|
2280
|
+
drawTextScreen(text, worldToScreen(pos), size*cameraScale, color, lineWidth*cameraScale, lineColor, textAlign, font, context, maxWidth);
|
|
2205
2281
|
}
|
|
2206
2282
|
|
|
2207
2283
|
/** Draw text on overlay canvas in screen space
|
|
@@ -2215,8 +2291,9 @@ function drawText(text, pos, size=1, color, lineWidth=0, lineColor, textAlign, f
|
|
|
2215
2291
|
* @param {CanvasTextAlign} [textAlign]
|
|
2216
2292
|
* @param {String} [font=fontDefault]
|
|
2217
2293
|
* @param {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} [context=overlayContext]
|
|
2294
|
+
* @param {Number} [maxWidth]
|
|
2218
2295
|
* @memberof Draw */
|
|
2219
|
-
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext)
|
|
2296
|
+
function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineColor=new Color(0,0,0), textAlign='center', font=fontDefault, context=overlayContext, maxWidth=undefined)
|
|
2220
2297
|
{
|
|
2221
2298
|
context.fillStyle = color.toString();
|
|
2222
2299
|
context.lineWidth = lineWidth;
|
|
@@ -2229,8 +2306,8 @@ function drawTextScreen(text, pos, size=1, color=new Color, lineWidth=0, lineCol
|
|
|
2229
2306
|
pos = pos.copy();
|
|
2230
2307
|
(text+'').split('\n').forEach(line=>
|
|
2231
2308
|
{
|
|
2232
|
-
lineWidth && context.strokeText(line, pos.x, pos.y);
|
|
2233
|
-
context.fillText(line, pos.x, pos.y);
|
|
2309
|
+
lineWidth && context.strokeText(line, pos.x, pos.y, maxWidth);
|
|
2310
|
+
context.fillText(line, pos.x, pos.y, maxWidth);
|
|
2234
2311
|
pos.y += size;
|
|
2235
2312
|
});
|
|
2236
2313
|
}
|
|
@@ -2338,8 +2415,8 @@ function toggleFullscreen()
|
|
|
2338
2415
|
if (document.exitFullscreen)
|
|
2339
2416
|
document.exitFullscreen();
|
|
2340
2417
|
}
|
|
2341
|
-
else if (
|
|
2342
|
-
|
|
2418
|
+
else if (engineRoot.requestFullscreen)
|
|
2419
|
+
engineRoot.requestFullscreen();
|
|
2343
2420
|
}
|
|
2344
2421
|
/**
|
|
2345
2422
|
* LittleJS Input System
|
|
@@ -2510,7 +2587,7 @@ function inputInit()
|
|
|
2510
2587
|
|
|
2511
2588
|
onkeydown = (e)=>
|
|
2512
2589
|
{
|
|
2513
|
-
if (debug && e.target !=
|
|
2590
|
+
if (debug && e.target != engineRoot) return;
|
|
2514
2591
|
if (!e.repeat)
|
|
2515
2592
|
{
|
|
2516
2593
|
isUsingGamepad = false;
|
|
@@ -2523,7 +2600,7 @@ function inputInit()
|
|
|
2523
2600
|
|
|
2524
2601
|
onkeyup = (e)=>
|
|
2525
2602
|
{
|
|
2526
|
-
if (debug && e.target !=
|
|
2603
|
+
if (debug && e.target != engineRoot) return;
|
|
2527
2604
|
inputData[0][e.code] = 4;
|
|
2528
2605
|
if (inputWASDEmulateDirection)
|
|
2529
2606
|
inputData[0][remapKey(e.code)] = 4;
|
|
@@ -2542,6 +2619,10 @@ function inputInit()
|
|
|
2542
2619
|
// mouse event handlers
|
|
2543
2620
|
onmousedown = (e)=>
|
|
2544
2621
|
{
|
|
2622
|
+
// fix stalled audio requiring user interaction
|
|
2623
|
+
if (soundEnable && !headlessMode && audioContext && audioContext.state != 'running')
|
|
2624
|
+
audioContext.resume();
|
|
2625
|
+
|
|
2545
2626
|
isUsingGamepad = false;
|
|
2546
2627
|
inputData[0][e.button] = 3;
|
|
2547
2628
|
mousePosScreen = mouseToScreen(e);
|
|
@@ -2553,7 +2634,7 @@ function inputInit()
|
|
|
2553
2634
|
oncontextmenu = (e)=> false; // prevent right click menu
|
|
2554
2635
|
|
|
2555
2636
|
// init touch input
|
|
2556
|
-
if (isTouchDevice && touchInputEnable
|
|
2637
|
+
if (isTouchDevice && touchInputEnable)
|
|
2557
2638
|
touchInputInit();
|
|
2558
2639
|
}
|
|
2559
2640
|
|
|
@@ -2710,8 +2791,8 @@ function touchInputInit()
|
|
|
2710
2791
|
function handleTouchDefault(e)
|
|
2711
2792
|
{
|
|
2712
2793
|
// fix stalled audio requiring user interaction
|
|
2713
|
-
if (soundEnable && audioContext && audioContext.state != 'running')
|
|
2714
|
-
|
|
2794
|
+
if (soundEnable && !headlessMode && audioContext && audioContext.state != 'running')
|
|
2795
|
+
audioContext.resume();
|
|
2715
2796
|
|
|
2716
2797
|
// check if touching and pass to mouse events
|
|
2717
2798
|
const touching = e.touches.length;
|
|
@@ -2924,6 +3005,7 @@ class Sound
|
|
|
2924
3005
|
// generate zzfx sound now for fast playback
|
|
2925
3006
|
const defaultRandomness = .05;
|
|
2926
3007
|
this.randomness = zzfxSound[1] || defaultRandomness;
|
|
3008
|
+
zzfxSound[1] = 0; // generate without randomness
|
|
2927
3009
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
2928
3010
|
this.sampleRate = zzfxR;
|
|
2929
3011
|
}
|
|
@@ -3156,10 +3238,6 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
3156
3238
|
|
|
3157
3239
|
///////////////////////////////////////////////////////////////////////////////
|
|
3158
3240
|
|
|
3159
|
-
// internal tracking if audio was suspended when last sound was played
|
|
3160
|
-
// allows first suspended sound to play when audio is resumed
|
|
3161
|
-
let audioSuspended = false;
|
|
3162
|
-
|
|
3163
3241
|
/** Play cached audio samples with given settings
|
|
3164
3242
|
* @param {Array} sampleChannels - Array of arrays of samples to play (for stereo playback)
|
|
3165
3243
|
* @param {Number} [volume] - How much to scale volume by
|
|
@@ -3174,21 +3252,11 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3174
3252
|
{
|
|
3175
3253
|
if (!soundEnable || headlessMode) return;
|
|
3176
3254
|
|
|
3177
|
-
// prevent sounds from building up if they can't be played
|
|
3178
|
-
const audioWasSuspended = audioSuspended;
|
|
3179
|
-
if (audioSuspended = audioContext.state != 'running')
|
|
3180
|
-
{
|
|
3181
|
-
// fix stalled audio
|
|
3182
|
-
audioContext.resume();
|
|
3183
|
-
|
|
3184
|
-
// prevent suspended sounds from building up
|
|
3185
|
-
if (audioWasSuspended)
|
|
3186
|
-
return;
|
|
3187
|
-
}
|
|
3188
|
-
|
|
3189
3255
|
// create buffer and source
|
|
3190
|
-
const
|
|
3191
|
-
|
|
3256
|
+
const channelCount = sampleChannels.length;
|
|
3257
|
+
const sampleLength = sampleChannels[0].length;
|
|
3258
|
+
const buffer = audioContext.createBuffer(channelCount, sampleLength, sampleRate);
|
|
3259
|
+
const source = audioContext.createBufferSource();
|
|
3192
3260
|
|
|
3193
3261
|
// copy samples to buffer and setup source
|
|
3194
3262
|
sampleChannels.forEach((c,i)=> buffer.getChannelData(i).set(c));
|
|
@@ -3202,10 +3270,19 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3202
3270
|
gainNode.connect(audioGainNode);
|
|
3203
3271
|
|
|
3204
3272
|
// connect source to stereo panner and gain
|
|
3205
|
-
|
|
3273
|
+
const pannerNode = new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)});
|
|
3274
|
+
source.connect(pannerNode).connect(gainNode);
|
|
3206
3275
|
|
|
3207
|
-
// play
|
|
3208
|
-
|
|
3276
|
+
// play the sound
|
|
3277
|
+
if (audioContext.state != 'running')
|
|
3278
|
+
{
|
|
3279
|
+
// fix stalled audio and play
|
|
3280
|
+
audioContext.resume().then(()=>source.start());
|
|
3281
|
+
}
|
|
3282
|
+
else
|
|
3283
|
+
source.start();
|
|
3284
|
+
|
|
3285
|
+
// return sound
|
|
3209
3286
|
return source;
|
|
3210
3287
|
}
|
|
3211
3288
|
|
|
@@ -3218,7 +3295,7 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3218
3295
|
* @param {Array} zzfxSound - Array of ZzFX parameters, ex. [.5,.5]
|
|
3219
3296
|
* @return {AudioBufferSourceNode} - The audio node of the sound played
|
|
3220
3297
|
* @memberof Audio */
|
|
3221
|
-
function zzfx(...zzfxSound) { return
|
|
3298
|
+
function zzfx(...zzfxSound) { return playSamples([zzfxG(...zzfxSound)]); }
|
|
3222
3299
|
|
|
3223
3300
|
/** Sample rate used for all ZzFX sounds
|
|
3224
3301
|
* @default 44100
|
|
@@ -3227,7 +3304,7 @@ const zzfxR = 44100;
|
|
|
3227
3304
|
|
|
3228
3305
|
/** Generate samples for a ZzFX sound
|
|
3229
3306
|
* @param {Number} [volume] - Volume scale (percent)
|
|
3230
|
-
* @param {Number} [randomness] -
|
|
3307
|
+
* @param {Number} [randomness] - How much to randomize frequency (percent Hz)
|
|
3231
3308
|
* @param {Number} [frequency] - Frequency of sound (Hz)
|
|
3232
3309
|
* @param {Number} [attack] - Attack time, how fast sound starts (seconds)
|
|
3233
3310
|
* @param {Number} [sustain] - Sustain time, how long sound holds (seconds)
|
|
@@ -3253,7 +3330,7 @@ const zzfxR = 44100;
|
|
|
3253
3330
|
function zzfxG
|
|
3254
3331
|
(
|
|
3255
3332
|
// parameters
|
|
3256
|
-
volume = 1, randomness =
|
|
3333
|
+
volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
|
|
3257
3334
|
release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
|
|
3258
3335
|
pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
|
|
3259
3336
|
bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0, filter = 0
|
|
@@ -3264,7 +3341,8 @@ function zzfxG
|
|
|
3264
3341
|
// init parameters
|
|
3265
3342
|
let PI2 = PI*2, sampleRate = zzfxR,
|
|
3266
3343
|
startSlide = slide *= 500 * PI2 / sampleRate / sampleRate,
|
|
3267
|
-
startFrequency = frequency *=
|
|
3344
|
+
startFrequency = frequency *=
|
|
3345
|
+
rand(1 + randomness, 1-randomness) * PI2 / sampleRate,
|
|
3268
3346
|
b = [], t = 0, tm = 0, i = 0, j = 1, r = 0, c = 0, s = 0, f, length,
|
|
3269
3347
|
|
|
3270
3348
|
// biquad LP/HP filter
|
|
@@ -4364,10 +4442,10 @@ function glInit()
|
|
|
4364
4442
|
|
|
4365
4443
|
// create the canvas and textures
|
|
4366
4444
|
glCanvas = document.createElement('canvas');
|
|
4367
|
-
glContext = glCanvas.getContext('webgl2');
|
|
4445
|
+
glContext = glCanvas.getContext('webgl2', {antialias:!canvasPixelated});
|
|
4368
4446
|
|
|
4369
4447
|
// some browsers are much faster without copying the gl buffer so we just overlay it instead
|
|
4370
|
-
glOverlay &&
|
|
4448
|
+
glOverlay && engineRoot.appendChild(glCanvas);
|
|
4371
4449
|
|
|
4372
4450
|
// setup vertex and fragment shaders
|
|
4373
4451
|
glShader = glCreateProgram(
|
|
@@ -4422,7 +4500,8 @@ function glPreRender()
|
|
|
4422
4500
|
// set up the shader
|
|
4423
4501
|
glContext.useProgram(glShader);
|
|
4424
4502
|
glContext.activeTexture(gl_TEXTURE0);
|
|
4425
|
-
|
|
4503
|
+
if (textureInfos[0])
|
|
4504
|
+
glContext.bindTexture(gl_TEXTURE_2D, glActiveTexture = textureInfos[0].glTexture);
|
|
4426
4505
|
|
|
4427
4506
|
// set vertex attributes
|
|
4428
4507
|
let offset = glAdditive = glBatchAdditive = 0;
|
|
@@ -4520,8 +4599,14 @@ function glCreateTexture(image)
|
|
|
4520
4599
|
// build the texture
|
|
4521
4600
|
const texture = glContext.createTexture();
|
|
4522
4601
|
glContext.bindTexture(gl_TEXTURE_2D, texture);
|
|
4523
|
-
if (image)
|
|
4602
|
+
if (image && image.width)
|
|
4524
4603
|
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
4604
|
+
else
|
|
4605
|
+
{
|
|
4606
|
+
// create a white texture
|
|
4607
|
+
const whitePixel = new Uint8Array([255, 255, 255, 255]);
|
|
4608
|
+
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, 1, 1, 0, gl_RGBA, gl_UNSIGNED_BYTE, whitePixel);
|
|
4609
|
+
}
|
|
4525
4610
|
|
|
4526
4611
|
// use point filtering for pixelated rendering
|
|
4527
4612
|
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
@@ -4664,7 +4749,7 @@ const engineName = 'LittleJS';
|
|
|
4664
4749
|
* @type {String}
|
|
4665
4750
|
* @default
|
|
4666
4751
|
* @memberof Engine */
|
|
4667
|
-
const engineVersion = '1.
|
|
4752
|
+
const engineVersion = '1.10.2';
|
|
4668
4753
|
|
|
4669
4754
|
/** Frames per second to update
|
|
4670
4755
|
* @type {Number}
|
|
@@ -4709,6 +4794,12 @@ let timeReal = 0;
|
|
|
4709
4794
|
* @memberof Engine */
|
|
4710
4795
|
let paused = false;
|
|
4711
4796
|
|
|
4797
|
+
/** The root element that engine is attached to
|
|
4798
|
+
* @type {HTMLElement}
|
|
4799
|
+
* @default document.body
|
|
4800
|
+
* @memberof Engine */
|
|
4801
|
+
let engineRoot;
|
|
4802
|
+
|
|
4712
4803
|
/** Set if game is paused
|
|
4713
4804
|
* @param {Boolean} isPaused
|
|
4714
4805
|
* @memberof Engine */
|
|
@@ -4742,8 +4833,9 @@ function engineAddPlugin(updateFunction, renderFunction)
|
|
|
4742
4833
|
* @param {Function} gameRender - Called before objects are rendered, draw any background effects that appear behind objects
|
|
4743
4834
|
* @param {Function} gameRenderPost - Called after objects are rendered, draw effects or hud that appear above all objects
|
|
4744
4835
|
* @param {Array} [imageSources=['tiles.png']] - Image to load
|
|
4836
|
+
* @param {HTMLElement} [rootElement] - Root element to attach to, the document body by default
|
|
4745
4837
|
* @memberof Engine */
|
|
4746
|
-
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[
|
|
4838
|
+
function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
4747
4839
|
{
|
|
4748
4840
|
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
4749
4841
|
|
|
@@ -4785,6 +4877,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4785
4877
|
for (const o of engineObjects)
|
|
4786
4878
|
o.parent || o.updateTransforms();
|
|
4787
4879
|
inputUpdate();
|
|
4880
|
+
pluginUpdateList.forEach(f=>f());
|
|
4788
4881
|
debugUpdate();
|
|
4789
4882
|
gameUpdatePost();
|
|
4790
4883
|
inputUpdatePost();
|
|
@@ -4900,16 +4993,22 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4900
4993
|
}
|
|
4901
4994
|
|
|
4902
4995
|
// setup html
|
|
4903
|
-
const
|
|
4996
|
+
const styleRoot =
|
|
4904
4997
|
'margin:0;overflow:hidden;' + // fill the window
|
|
4998
|
+
'width:100vw;height:100vh;' + // fill the window
|
|
4999
|
+
'display:flex;' + // use flexbox
|
|
5000
|
+
'align-items:center;' + // horizontal center
|
|
5001
|
+
(canvasPixelated ? 'image-rendering:pixelated;' : '') + // pixel art
|
|
5002
|
+
'justify-content:center;' + // vertical center
|
|
4905
5003
|
'background:#000;' + // set background color
|
|
4906
5004
|
'user-select:none;' + // prevent hold to select
|
|
4907
5005
|
'-webkit-user-select:none;' + // compatibility for ios
|
|
4908
5006
|
(!touchInputEnable ? '' : // no touch css setttings
|
|
4909
5007
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
4910
5008
|
'-webkit-touch-callout:none');// compatibility for ios
|
|
4911
|
-
|
|
4912
|
-
|
|
5009
|
+
engineRoot = rootElement;
|
|
5010
|
+
engineRoot.style.cssText = styleRoot;
|
|
5011
|
+
engineRoot.appendChild(mainCanvas = document.createElement('canvas'));
|
|
4913
5012
|
mainContext = mainCanvas.getContext('2d');
|
|
4914
5013
|
|
|
4915
5014
|
// init stuff and start engine
|
|
@@ -4919,13 +5018,14 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4919
5018
|
glInit();
|
|
4920
5019
|
|
|
4921
5020
|
// create overlay canvas for hud to appear above gl canvas
|
|
4922
|
-
|
|
5021
|
+
engineRoot.appendChild(overlayCanvas = document.createElement('canvas'));
|
|
4923
5022
|
overlayContext = overlayCanvas.getContext('2d');
|
|
4924
5023
|
|
|
4925
5024
|
// set canvas style
|
|
4926
|
-
const styleCanvas = 'position:absolute;
|
|
4927
|
-
|
|
4928
|
-
(glCanvas
|
|
5025
|
+
const styleCanvas = 'position:absolute'; // allow canvases to overlap
|
|
5026
|
+
mainCanvas.style.cssText = overlayCanvas.style.cssText = styleCanvas;
|
|
5027
|
+
if (glCanvas)
|
|
5028
|
+
glCanvas.style.cssText = styleCanvas;
|
|
4929
5029
|
updateCanvas();
|
|
4930
5030
|
|
|
4931
5031
|
// create promises for loading images
|
|
@@ -4942,19 +5042,32 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4942
5042
|
})
|
|
4943
5043
|
);
|
|
4944
5044
|
|
|
4945
|
-
|
|
4946
|
-
showSplashScreen && promises.push(new Promise(resolve =>
|
|
5045
|
+
if (!imageSources.length)
|
|
4947
5046
|
{
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
updateSplash();
|
|
4951
|
-
function updateSplash()
|
|
5047
|
+
// no images to load
|
|
5048
|
+
promises.push(new Promise(resolve =>
|
|
4952
5049
|
{
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
5050
|
+
textureInfos[0] = new TextureInfo(new Image);
|
|
5051
|
+
resolve();
|
|
5052
|
+
}));
|
|
5053
|
+
}
|
|
5054
|
+
|
|
5055
|
+
if (showSplashScreen)
|
|
5056
|
+
{
|
|
5057
|
+
// draw splash screen
|
|
5058
|
+
promises.push(new Promise(resolve =>
|
|
5059
|
+
{
|
|
5060
|
+
let t = 0;
|
|
5061
|
+
console.log(`${engineName} Engine v${engineVersion}`);
|
|
5062
|
+
updateSplash();
|
|
5063
|
+
function updateSplash()
|
|
5064
|
+
{
|
|
5065
|
+
clearInput();
|
|
5066
|
+
drawEngineSplashScreen(t+=.01);
|
|
5067
|
+
t>1 ? resolve() : setTimeout(updateSplash, 16);
|
|
5068
|
+
}
|
|
5069
|
+
}));
|
|
5070
|
+
}
|
|
4958
5071
|
|
|
4959
5072
|
// load all of the images
|
|
4960
5073
|
Promise.all(promises).then(startEngine);
|