littlejsengine 1.17.1 → 1.17.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/littlejs.d.ts +94 -83
- package/dist/littlejs.esm.js +302 -198
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +302 -197
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +298 -193
- package/examples/box2d/gameObjects.js +3 -3
- package/examples/electron/index.html +2 -2
- package/examples/index.html +8 -7
- package/examples/module/build.bat +7 -0
- package/examples/module/build.js +121 -0
- package/examples/platformer/game.js +1 -1
- package/examples/platformer/gameEffects.js +2 -7
- package/examples/platformer/gameLevel.js +3 -3
- package/examples/puzzle/game.js +2 -2
- package/examples/shorts/base.html +4 -1
- package/examples/shorts/box2dPool.js +2 -2
- package/examples/shorts/box2dTileLayer.js +48 -0
- package/examples/shorts/music.js +2 -2
- package/examples/shorts/musicPlayer.js +2 -2
- package/examples/starter/index.html +2 -2
- package/package.json +5 -5
- package/plugins/box2d.js +167 -63
- package/plugins/uiSystem.js +3 -3
- package/plugins/zzfxm.js +2 -2
- package/reference.md +3 -2
- package/src/engine.js +10 -9
- package/src/engineAudio.js +44 -58
- package/src/engineDebug.js +4 -4
- package/src/engineDraw.js +5 -2
- package/src/engineExport.js +0 -1
- package/src/engineInput.js +4 -4
- package/src/engineMath.js +9 -9
- package/src/engineObject.js +1 -1
- package/src/engineTileLayer.js +51 -40
- package/src/engineWebGL.js +2 -2
package/plugins/box2d.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - Contact begin and end callbacks
|
|
10
10
|
* - Wraps b2Vec2 type to/from Vector2
|
|
11
11
|
* - Raycasting and querying
|
|
12
|
+
* - Box2dTileLayer for grid based collision
|
|
12
13
|
* - Every type of joint
|
|
13
14
|
* - Debug physics drawing
|
|
14
15
|
* @namespace Box2D
|
|
@@ -60,21 +61,24 @@ class Box2dObject extends EngineObject
|
|
|
60
61
|
bodyDef.set_type(bodyType);
|
|
61
62
|
bodyDef.set_position(box2d.vec2dTo(pos));
|
|
62
63
|
bodyDef.set_angle(-angle);
|
|
64
|
+
|
|
65
|
+
/** @property {Object} - The Box2d body */
|
|
63
66
|
this.body = box2d.world.CreateBody(bodyDef);
|
|
64
|
-
|
|
67
|
+
/** @property {Color} - Line color used for default box2d drawing */
|
|
65
68
|
this.lineColor = BLACK;
|
|
66
|
-
box2d
|
|
67
|
-
|
|
68
|
-
// edge lists and loops for drawing
|
|
69
|
+
/** @property {Array<Object>} - List of all edges for default box2d drawing */
|
|
69
70
|
this.edgeLists = [];
|
|
71
|
+
/** @property {Array<Object>} - List of all edge loops for default box2d drawing */
|
|
70
72
|
this.edgeLoops = [];
|
|
73
|
+
|
|
74
|
+
this.body.object = this; // link body to this object
|
|
75
|
+
box2d.objects.push(this); // keep track of all box2d objects
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
/** Destroy this object and its physics body */
|
|
74
79
|
destroy()
|
|
75
80
|
{
|
|
76
|
-
if (this.destroyed)
|
|
77
|
-
return;
|
|
81
|
+
if (this.destroyed) return;
|
|
78
82
|
|
|
79
83
|
// destroy physics body, fixtures, and joints
|
|
80
84
|
ASSERT(this.body, 'Box2dObject has no body to destroy');
|
|
@@ -105,11 +109,12 @@ class Box2dObject extends EngineObject
|
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
/** Draws all this object's fixtures
|
|
108
|
-
* @param {Color}
|
|
109
|
-
* @param {Color}
|
|
110
|
-
* @param {number}
|
|
112
|
+
* @param {Color} [color]
|
|
113
|
+
* @param {Color} [lineColor]
|
|
114
|
+
* @param {number} [lineWidth]
|
|
115
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
111
116
|
* @param {CanvasRenderingContext2D} [context] */
|
|
112
|
-
drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, context)
|
|
117
|
+
drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
|
|
113
118
|
{
|
|
114
119
|
// draw non-edge fixtures
|
|
115
120
|
this.getFixtureList().forEach((fixture)=>
|
|
@@ -117,7 +122,7 @@ class Box2dObject extends EngineObject
|
|
|
117
122
|
const shape = box2d.castObjectType(fixture.GetShape());
|
|
118
123
|
if (shape.GetType() !== box2d.instance.b2Shape.e_edge)
|
|
119
124
|
{
|
|
120
|
-
box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, context);
|
|
125
|
+
box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, useWebGL, context);
|
|
121
126
|
}
|
|
122
127
|
});
|
|
123
128
|
|
|
@@ -343,6 +348,14 @@ class Box2dObject extends EngineObject
|
|
|
343
348
|
return fixtures;
|
|
344
349
|
}
|
|
345
350
|
|
|
351
|
+
/** Destroy a fixture from the body
|
|
352
|
+
* @param {Object} [fixture] */
|
|
353
|
+
destroyFixture(fixture) { this.body.DestroyFixture(fixture); }
|
|
354
|
+
|
|
355
|
+
/** Destroy all fixture from the body */
|
|
356
|
+
destroyAllFixtures()
|
|
357
|
+
{ this.getFixtureList().forEach(fixture=>this.destroyFixture(fixture)); }
|
|
358
|
+
|
|
346
359
|
///////////////////////////////////////////////////////////////////////////////
|
|
347
360
|
// physics get functions
|
|
348
361
|
|
|
@@ -575,6 +588,136 @@ class Box2dObject extends EngineObject
|
|
|
575
588
|
}
|
|
576
589
|
}
|
|
577
590
|
|
|
591
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
592
|
+
/**
|
|
593
|
+
* Box2D Static Object - Box2d with a static physics body
|
|
594
|
+
* @extends Box2dObject
|
|
595
|
+
* @memberof Box2D
|
|
596
|
+
*/
|
|
597
|
+
class Box2dStaticObject extends Box2dObject
|
|
598
|
+
{
|
|
599
|
+
/** Create a LittleJS object with Box2d physics
|
|
600
|
+
* @param {Vector2} [pos]
|
|
601
|
+
* @param {Vector2} [size]
|
|
602
|
+
* @param {TileInfo} [tileInfo]
|
|
603
|
+
* @param {number} [angle]
|
|
604
|
+
* @param {Color} [color]
|
|
605
|
+
* @param {number} [renderOrder] */
|
|
606
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
607
|
+
{
|
|
608
|
+
const bodyType = box2d.bodyTypeStatic;
|
|
609
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
614
|
+
/**
|
|
615
|
+
* Box2D Kiematic Object - Box2d with a kinematic physics body
|
|
616
|
+
* @extends Box2dObject
|
|
617
|
+
* @memberof Box2D
|
|
618
|
+
*/
|
|
619
|
+
class Box2dKiematicObject extends Box2dObject
|
|
620
|
+
{
|
|
621
|
+
/** Create a LittleJS object with Box2d physics
|
|
622
|
+
* @param {Vector2} [pos]
|
|
623
|
+
* @param {Vector2} [size]
|
|
624
|
+
* @param {TileInfo} [tileInfo]
|
|
625
|
+
* @param {number} [angle]
|
|
626
|
+
* @param {Color} [color]
|
|
627
|
+
* @param {number} [renderOrder] */
|
|
628
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
629
|
+
{
|
|
630
|
+
const bodyType = box2d.bodyTypeKinematic;
|
|
631
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
636
|
+
/**
|
|
637
|
+
* Box2d Tile Layer
|
|
638
|
+
* - adds Box2d support to tile layers
|
|
639
|
+
* - creates static box2d fixtures for solid tiles
|
|
640
|
+
* @extends Box2dObject
|
|
641
|
+
* @memberof Box2D
|
|
642
|
+
*/
|
|
643
|
+
class Box2dTileLayer extends Box2dStaticObject
|
|
644
|
+
{
|
|
645
|
+
/** Create a Box2d tile layer object
|
|
646
|
+
* @param {TileCollisionLayer} tileLayer - Tile layer for this object */
|
|
647
|
+
constructor(tileLayer)
|
|
648
|
+
{
|
|
649
|
+
ASSERT(tileLayer instanceof TileCollisionLayer, 'tileLayer must be a TileCollisionLayer');
|
|
650
|
+
super(tileLayer.pos, tileLayer.size);
|
|
651
|
+
|
|
652
|
+
/** @property {TileLayer} - The tile layer */
|
|
653
|
+
this.tileLayer = tileLayer;
|
|
654
|
+
this.addChild(tileLayer);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
render()
|
|
658
|
+
{
|
|
659
|
+
// do not render fixtures, tile layer handles rendering
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/** Create box2d collision fixtures for solid tiles
|
|
663
|
+
* @param {number} [friction]
|
|
664
|
+
* @param {number} [restitution] */
|
|
665
|
+
buildCollision(friction=.2, restitution=0)
|
|
666
|
+
{
|
|
667
|
+
// destroy all fixtures and create new ones
|
|
668
|
+
this.destroyAllFixtures();
|
|
669
|
+
|
|
670
|
+
// create box2d object for this layer
|
|
671
|
+
const size = this.tileLayer.size;
|
|
672
|
+
this.pos = this.tileLayer.pos.copy();
|
|
673
|
+
this.size = size.copy();
|
|
674
|
+
|
|
675
|
+
// track which tiles have been processed
|
|
676
|
+
const processed = [];
|
|
677
|
+
const getIndex = (x, y)=> x + y * size.x;
|
|
678
|
+
const isSolidUnprocessed = (x, y)=>
|
|
679
|
+
!processed[getIndex(x, y)] &&
|
|
680
|
+
this.tileLayer.getCollisionData(vec2(x, y)) > 0;
|
|
681
|
+
|
|
682
|
+
// combine tiles into larger boxes
|
|
683
|
+
for (let x = 0; x < size.x; ++x)
|
|
684
|
+
for (let y = 0; y < size.y; ++y)
|
|
685
|
+
{
|
|
686
|
+
if (!isSolidUnprocessed(x, y)) continue;
|
|
687
|
+
|
|
688
|
+
// find max width by scanning right
|
|
689
|
+
let width = 1, height = 1, canExpand = true;
|
|
690
|
+
while (isSolidUnprocessed(x + width, y))
|
|
691
|
+
++width;
|
|
692
|
+
|
|
693
|
+
// find max height by scanning up, ensuring all rows have the same width
|
|
694
|
+
while (canExpand)
|
|
695
|
+
{
|
|
696
|
+
for (let checkX = 0; checkX < width; ++checkX)
|
|
697
|
+
{
|
|
698
|
+
if (!isSolidUnprocessed(x + checkX, y + height))
|
|
699
|
+
{
|
|
700
|
+
canExpand = false;
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (canExpand)
|
|
705
|
+
++height;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// mark all tiles in this rectangle as processed
|
|
709
|
+
for (let rectX = width; rectX--;)
|
|
710
|
+
for (let rectY = height; rectY--;)
|
|
711
|
+
processed[getIndex(x + rectX, y + rectY)] = true;
|
|
712
|
+
|
|
713
|
+
// create a single fixture for the entire rectangle
|
|
714
|
+
const shapeSize = vec2(width, height);
|
|
715
|
+
const offset = vec2(x + width/2, y + height/2);
|
|
716
|
+
this.addBox(shapeSize, offset, 0, 0, friction, restitution);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
578
721
|
///////////////////////////////////////////////////////////////////////////////
|
|
579
722
|
/**
|
|
580
723
|
* Box2D Raycast Result
|
|
@@ -616,6 +759,7 @@ class Box2dJoint
|
|
|
616
759
|
* @param {Object} jointDef */
|
|
617
760
|
constructor(jointDef)
|
|
618
761
|
{
|
|
762
|
+
/** @property {Object} - The Box2d joint */
|
|
619
763
|
this.box2dJoint = box2d.castObjectType(box2d.world.CreateJoint(jointDef));
|
|
620
764
|
}
|
|
621
765
|
|
|
@@ -962,7 +1106,7 @@ class Box2dGearJoint extends Box2dJoint
|
|
|
962
1106
|
* @param {Box2dObject} objectB
|
|
963
1107
|
* @param {Box2dJoint} joint1
|
|
964
1108
|
* @param {Box2dJoint} joint2
|
|
965
|
-
* @param {
|
|
1109
|
+
* @param {number} [ratio] */
|
|
966
1110
|
constructor(objectA, objectB, joint1, joint2, ratio=1)
|
|
967
1111
|
{
|
|
968
1112
|
const jointDef = new box2d.instance.b2GearJointDef();
|
|
@@ -1104,50 +1248,6 @@ class Box2dPrismaticJoint extends Box2dJoint
|
|
|
1104
1248
|
getMotorForce(time) { return this.box2dJoint.GetMotorForce(1/time); }
|
|
1105
1249
|
}
|
|
1106
1250
|
|
|
1107
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
1108
|
-
/**
|
|
1109
|
-
* Box2D Static Object - Box2d with a static physics body
|
|
1110
|
-
* @extends Box2dObject
|
|
1111
|
-
* @memberof Box2D
|
|
1112
|
-
*/
|
|
1113
|
-
class Box2dStaticObject extends Box2dObject
|
|
1114
|
-
{
|
|
1115
|
-
/** Create a LittleJS object with Box2d physics
|
|
1116
|
-
* @param {Vector2} [pos]
|
|
1117
|
-
* @param {Vector2} [size]
|
|
1118
|
-
* @param {TileInfo} [tileInfo]
|
|
1119
|
-
* @param {number} [angle]
|
|
1120
|
-
* @param {Color} [color]
|
|
1121
|
-
* @param {number} [renderOrder] */
|
|
1122
|
-
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
1123
|
-
{
|
|
1124
|
-
const bodyType = box2d.bodyTypeStatic;
|
|
1125
|
-
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
1130
|
-
/**
|
|
1131
|
-
* Box2D Kiematic Object - Box2d with a kinematic physics body
|
|
1132
|
-
* @extends Box2dObject
|
|
1133
|
-
* @memberof Box2D
|
|
1134
|
-
*/
|
|
1135
|
-
class Box2dKiematicObject extends Box2dObject
|
|
1136
|
-
{
|
|
1137
|
-
/** Create a LittleJS object with Box2d physics
|
|
1138
|
-
* @param {Vector2} [pos]
|
|
1139
|
-
* @param {Vector2} [size]
|
|
1140
|
-
* @param {TileInfo} [tileInfo]
|
|
1141
|
-
* @param {number} [angle]
|
|
1142
|
-
* @param {Color} [color]
|
|
1143
|
-
* @param {number} [renderOrder] */
|
|
1144
|
-
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
1145
|
-
{
|
|
1146
|
-
const bodyType = box2d.bodyTypeKinematic;
|
|
1147
|
-
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
1251
|
///////////////////////////////////////////////////////////////////////////////
|
|
1152
1252
|
/**
|
|
1153
1253
|
* Box2D Wheel Joint
|
|
@@ -1508,10 +1608,13 @@ class Box2dPlugin
|
|
|
1508
1608
|
{
|
|
1509
1609
|
ASSERT(!box2d, 'Box2D already initialized');
|
|
1510
1610
|
box2d = this;
|
|
1611
|
+
|
|
1612
|
+
/** @property {Object} - The Box2d instance */
|
|
1511
1613
|
this.instance = instance;
|
|
1614
|
+
/** @property {Object} - The Box2d world */
|
|
1512
1615
|
this.world = new box2d.instance.b2World();
|
|
1616
|
+
/** @property {Array<Box2dObject>} - List of all Box2d objects */
|
|
1513
1617
|
this.objects = [];
|
|
1514
|
-
|
|
1515
1618
|
/** @property {number} - Velocity iterations per update*/
|
|
1516
1619
|
this.velocityIterations = 8;
|
|
1517
1620
|
/** @property {number} - Position iterations per update*/
|
|
@@ -1710,8 +1813,9 @@ class Box2dPlugin
|
|
|
1710
1813
|
* @param {Color} [color]
|
|
1711
1814
|
* @param {Color} [lineColor]
|
|
1712
1815
|
* @param {number} [lineWidth]
|
|
1816
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
1713
1817
|
* @param {CanvasRenderingContext2D} [context] */
|
|
1714
|
-
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, context)
|
|
1818
|
+
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebgl, context)
|
|
1715
1819
|
{
|
|
1716
1820
|
const shape = box2d.castObjectType(fixture.GetShape());
|
|
1717
1821
|
switch (shape.GetType())
|
|
@@ -1721,20 +1825,20 @@ class Box2dPlugin
|
|
|
1721
1825
|
let points = [];
|
|
1722
1826
|
for (let i=shape.GetVertexCount(); i--;)
|
|
1723
1827
|
points.push(box2d.vec2From(shape.GetVertex(i)));
|
|
1724
|
-
drawPoly(points, color, lineWidth, lineColor, pos, angle);
|
|
1828
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebgl, false, context);
|
|
1725
1829
|
break;
|
|
1726
1830
|
}
|
|
1727
1831
|
case box2d.instance.b2Shape.e_circle:
|
|
1728
1832
|
{
|
|
1729
1833
|
const radius = shape.get_m_radius();
|
|
1730
|
-
drawCircle(pos, radius*2, color, lineWidth, lineColor);
|
|
1834
|
+
drawCircle(pos, radius*2, color, lineWidth, lineColor, useWebgl, false, context);
|
|
1731
1835
|
break;
|
|
1732
1836
|
}
|
|
1733
1837
|
case box2d.instance.b2Shape.e_edge:
|
|
1734
1838
|
{
|
|
1735
1839
|
const v1 = box2d.vec2From(shape.get_m_vertex1());
|
|
1736
1840
|
const v2 = box2d.vec2From(shape.get_m_vertex2());
|
|
1737
|
-
drawLine(v1, v2, lineWidth, lineColor, pos, angle);
|
|
1841
|
+
drawLine(v1, v2, lineWidth, lineColor, pos, angle, useWebgl, false, context);
|
|
1738
1842
|
break;
|
|
1739
1843
|
}
|
|
1740
1844
|
}
|
|
@@ -1752,7 +1856,7 @@ class Box2dPlugin
|
|
|
1752
1856
|
}
|
|
1753
1857
|
|
|
1754
1858
|
/** converts a box2d vec2 pointer to a Vector2
|
|
1755
|
-
* @param {Object}
|
|
1859
|
+
* @param {Object} vp */
|
|
1756
1860
|
vec2FromPointer(vp)
|
|
1757
1861
|
{
|
|
1758
1862
|
const v = box2d.instance.wrapPointer(vp, box2d.instance.b2Vec2);
|
|
@@ -1864,7 +1968,7 @@ async function box2dInit()
|
|
|
1864
1968
|
const box2dColorPointer = (c)=>
|
|
1865
1969
|
box2dColor(box2d.instance.wrapPointer(c, box2d.instance.b2Color));
|
|
1866
1970
|
const getDebugColor = (color)=>box2dColorPointer(color).scale(1,.8);
|
|
1867
|
-
const getPointsList = (vertices, vertexCount)
|
|
1971
|
+
const getPointsList = (vertices, vertexCount)=>
|
|
1868
1972
|
{
|
|
1869
1973
|
const points = [];
|
|
1870
1974
|
for (let i=vertexCount; i--;)
|
package/plugins/uiSystem.js
CHANGED
|
@@ -30,7 +30,7 @@ let uiDebug = 0;
|
|
|
30
30
|
|
|
31
31
|
/** Enable UI system debug drawing
|
|
32
32
|
* 0=off, 1=normal, 2=show invisible
|
|
33
|
-
* @param {number|boolean}
|
|
33
|
+
* @param {number|boolean} debugMode
|
|
34
34
|
* @memberof UISystem */
|
|
35
35
|
function uiSetDebug(debugMode)
|
|
36
36
|
{ uiDebug = typeof debugMode === 'boolean' ? (debugMode ? 1 : 0) : debugMode; }
|
|
@@ -1231,8 +1231,8 @@ class UIScrollbar extends UIObject
|
|
|
1231
1231
|
class UIVideo extends UIObject
|
|
1232
1232
|
{
|
|
1233
1233
|
/** Create a video player UI object
|
|
1234
|
-
* @param {Vector2}
|
|
1235
|
-
* @param {Vector2}
|
|
1234
|
+
* @param {Vector2} pos
|
|
1235
|
+
* @param {Vector2} size
|
|
1236
1236
|
* @param {string} src - Video file path or URL
|
|
1237
1237
|
* @param {boolean} [autoplay=false] - Start playing immediately?
|
|
1238
1238
|
* @param {boolean} [loop=false] - Loop the video?
|
package/plugins/zzfxm.js
CHANGED
|
@@ -55,7 +55,7 @@ class ZzFXMusic extends Sound
|
|
|
55
55
|
/** Play the music that loops by default
|
|
56
56
|
* @param {number} [volume] - Volume to play the music at
|
|
57
57
|
* @param {boolean} [loop] - Should the music loop?
|
|
58
|
-
* @return {
|
|
58
|
+
* @return {SoundInstance} - The sound instance
|
|
59
59
|
*/
|
|
60
60
|
playMusic(volume=1, loop=true)
|
|
61
61
|
{ return super.play(undefined, volume, 1, 0, loop); }
|
|
@@ -102,7 +102,7 @@ function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
|
102
102
|
sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
|
|
103
103
|
|
|
104
104
|
// for each pattern in sequence
|
|
105
|
-
sequence.forEach((patternIndex, sequenceIndex)
|
|
105
|
+
sequence.forEach((patternIndex, sequenceIndex)=> {
|
|
106
106
|
// get pattern for current channel, use empty 1 note pattern if none found
|
|
107
107
|
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
108
108
|
|
package/reference.md
CHANGED
|
@@ -205,8 +205,8 @@ tileDefaultBleed = .3 // How much smaller to draw tiles to prevent bleeding
|
|
|
205
205
|
|
|
206
206
|
```javascript
|
|
207
207
|
// Sound Object
|
|
208
|
-
Sound(zzfxSound, range, taper)
|
|
209
|
-
|
|
208
|
+
Sound(zzfxSound, randomness, range, taper) // Create a zzfx sound
|
|
209
|
+
Sound(filename, randomness, range, taper) // Load a wave, mp3, or ogg
|
|
210
210
|
Sound.play(pos, volume=1, pitch=1, randomness=1, loop) // Play a sound, returns SoundInstance
|
|
211
211
|
Sound.playMusic(volume=1, loop=true) // Play as music with looping
|
|
212
212
|
Sound.playNote(semitoneOffset, pos, volume=1) // Play as note with a semitone offset
|
|
@@ -380,6 +380,7 @@ CanvasLayer.updateWebGL() // Creates or updates WebGL texture
|
|
|
380
380
|
// LittleJS Layer System
|
|
381
381
|
TileLayer(position, size, tileInfo, scale) // Create a tile layer object
|
|
382
382
|
TileLayer.setData(layerPos, data, redraw) // Set data at position
|
|
383
|
+
TileLayer.clearData(layerPos, redraw) // Clear data at position
|
|
383
384
|
TileLayer.getData(layerPos) // Get data at position
|
|
384
385
|
TileLayer.redraw() // Draw to an offscreen canvas
|
|
385
386
|
TileLayer.drawTileData(layerPos, clear=true) // Draw the tile
|
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.17.
|
|
33
|
+
const engineVersion = '1.17.5';
|
|
34
34
|
|
|
35
35
|
/** Frames per second to update
|
|
36
36
|
* @type {number}
|
|
@@ -85,8 +85,9 @@ function getPaused() { return paused; }
|
|
|
85
85
|
* @memberof Engine */
|
|
86
86
|
function setPaused(isPaused=true) { paused = isPaused; }
|
|
87
87
|
|
|
88
|
-
//
|
|
88
|
+
// Engine internal variables
|
|
89
89
|
let frameTimeLastMS = 0, frameTimeBufferMS = 0, averageFPS = 0;
|
|
90
|
+
let showEngineVersion = true;
|
|
90
91
|
|
|
91
92
|
///////////////////////////////////////////////////////////////////////////////
|
|
92
93
|
// plugin hooks
|
|
@@ -150,16 +151,17 @@ function engineAddPlugin(update, render, glContextLost, glContextRestored)
|
|
|
150
151
|
* @example
|
|
151
152
|
* // Basic engine startup
|
|
152
153
|
* engineInit(
|
|
153
|
-
* ()
|
|
154
|
-
* ()
|
|
155
|
-
* ()
|
|
156
|
-
* ()
|
|
157
|
-
* ()
|
|
154
|
+
* ()=> { LOG('Game initialized!'); }, // gameInit
|
|
155
|
+
* ()=> { updateGameLogic(); }, // gameUpdate
|
|
156
|
+
* ()=> { updateUI(); }, // gameUpdatePost
|
|
157
|
+
* ()=> { drawBackground(); }, // gameRender
|
|
158
|
+
* ()=> { drawHUD(); }, // gameRenderPost
|
|
158
159
|
* ['tiles.png', 'tilesLevel.png'] // images to load
|
|
159
160
|
* );
|
|
160
161
|
* @memberof Engine */
|
|
161
162
|
async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, imageSources=[], rootElement=document.body)
|
|
162
163
|
{
|
|
164
|
+
showEngineVersion && console.log(`${engineName} Engine v${engineVersion}`);
|
|
163
165
|
ASSERT(!mainContext, 'engine already initialized');
|
|
164
166
|
ASSERT(isArray(imageSources), 'pass in images as array');
|
|
165
167
|
|
|
@@ -428,7 +430,6 @@ async function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, game
|
|
|
428
430
|
promises.push(new Promise(resolve =>
|
|
429
431
|
{
|
|
430
432
|
let t = 0;
|
|
431
|
-
console.log(`${engineName} Engine v${engineVersion}`);
|
|
432
433
|
updateSplash();
|
|
433
434
|
function updateSplash()
|
|
434
435
|
{
|
|
@@ -605,7 +606,7 @@ function drawEngineLogo(t)
|
|
|
605
606
|
x.fillStyle = C;
|
|
606
607
|
C ? x.fill() : x.stroke();
|
|
607
608
|
};
|
|
608
|
-
const color = (c=0, l=0)
|
|
609
|
+
const color = (c=0, l=0)=>
|
|
609
610
|
hsl([.98,.3,.57,.14][c%4],.9,[0,.3,.5,.8,.9][l]).toString();
|
|
610
611
|
const alpha = wave(1,1,t);
|
|
611
612
|
const p = percent(alpha, .1, .5);
|
package/src/engineAudio.js
CHANGED
|
@@ -44,29 +44,46 @@ function audioInit()
|
|
|
44
44
|
///////////////////////////////////////////////////////////////////////////////
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Sound Object - Stores a sound for later
|
|
47
|
+
* Sound Object - Stores a sound for later
|
|
48
|
+
* - this can be used to load and play wave, mp3, and ogg files
|
|
49
|
+
* - it can also create sounds using the ZzFX sound generator
|
|
50
|
+
* - can attenuate and apply stereo panning to sounds
|
|
51
|
+
* - sound instance control with pause/resume capability
|
|
48
52
|
*
|
|
49
53
|
* <a href=https://killedbyapixel.github.io/ZzFX/>Create sounds using the ZzFX Sound Designer.</a>
|
|
50
54
|
* @memberof Audio
|
|
51
55
|
* @example
|
|
52
|
-
* //
|
|
56
|
+
* // load an audio asset file
|
|
57
|
+
* const sound_example = new Sound('sound.mp3');
|
|
58
|
+
*
|
|
59
|
+
* // create a zzfx sound
|
|
53
60
|
* const sound_example = new Sound([.5,.5]);
|
|
54
61
|
*
|
|
55
|
-
* // play
|
|
62
|
+
* // play a sound
|
|
56
63
|
* sound_example.play();
|
|
57
64
|
*/
|
|
58
65
|
class Sound
|
|
59
66
|
{
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
67
|
+
/**
|
|
68
|
+
* @callback SoundLoadCallback - Function called when sound is loaded
|
|
69
|
+
* @param {Sound} sound
|
|
70
|
+
* @memberof Audio
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/** Create a sound object and cache the audio for later use
|
|
74
|
+
* @param {string|Array} [asset] - Filename of audio file or zzfx array
|
|
75
|
+
* @param {number} [randomness] - How much to randomize frequency each time sound plays, for zzfx sounds the zzfx default is used if undefined
|
|
62
76
|
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
63
77
|
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
78
|
+
* @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
|
|
64
79
|
*/
|
|
65
|
-
constructor(
|
|
80
|
+
constructor(asset, randomness, range=soundDefaultRange, taper=soundDefaultTaper, onloadCallback)
|
|
66
81
|
{
|
|
67
82
|
if (!soundEnable || headlessMode) return;
|
|
68
83
|
|
|
69
|
-
ASSERT(!
|
|
84
|
+
ASSERT(!asset || isArray(asset) || isString(asset), 'asset must be a file name or zzfx array');
|
|
85
|
+
ASSERT(randomness === undefined || isNumber(randomness), 'randomness must be a number');
|
|
86
|
+
ASSERT(randomness === undefined || randomness >= 0 && randomness <=1, 'randomness must be between 0 and 1');
|
|
70
87
|
ASSERT(isNumber(range), 'range must be a number');
|
|
71
88
|
ASSERT(isNumber(taper), 'taper must be a number');
|
|
72
89
|
|
|
@@ -75,23 +92,35 @@ class Sound
|
|
|
75
92
|
/** @property {number} - At what percentage of range should it start tapering */
|
|
76
93
|
this.taper = taper;
|
|
77
94
|
/** @property {number} - How much to randomize frequency each time sound plays */
|
|
78
|
-
this.randomness = 0;
|
|
95
|
+
this.randomness = randomness ?? 0;
|
|
79
96
|
/** @property {number} - Sample rate for this sound */
|
|
80
97
|
this.sampleRate = audioDefaultSampleRate;
|
|
81
98
|
/** @property {number} - Percentage of this sound currently loaded */
|
|
82
99
|
this.loadedPercent = 0;
|
|
100
|
+
/** @property {SoundLoadCallback} - function to call when sound is loaded */
|
|
101
|
+
this.onloadCallback = onloadCallback;
|
|
83
102
|
|
|
84
|
-
|
|
85
|
-
if (zzfxSound)
|
|
103
|
+
if (Array.isArray(asset))
|
|
86
104
|
{
|
|
105
|
+
// generate zzfx sound
|
|
106
|
+
const zzfxSound = asset;
|
|
107
|
+
|
|
87
108
|
// remove randomness so it can be applied on playback
|
|
88
|
-
const
|
|
109
|
+
const defaultRandomness = randomness ?? .05;
|
|
110
|
+
const randomnessIndex = 1;
|
|
89
111
|
this.randomness = zzfxSound[randomnessIndex] ?? defaultRandomness;
|
|
90
112
|
zzfxSound[randomnessIndex] = 0;
|
|
91
113
|
|
|
92
114
|
// generate the zzfx samples
|
|
93
115
|
this.sampleChannels = [zzfxG(...zzfxSound)];
|
|
94
116
|
this.loadedPercent = 1;
|
|
117
|
+
onloadCallback?.(this);
|
|
118
|
+
}
|
|
119
|
+
else if (typeof asset === 'string')
|
|
120
|
+
{
|
|
121
|
+
// load the audio file
|
|
122
|
+
const filename = asset;
|
|
123
|
+
this.loadSound(filename);
|
|
95
124
|
}
|
|
96
125
|
}
|
|
97
126
|
|
|
@@ -143,7 +172,7 @@ class Sound
|
|
|
143
172
|
* @param {number} [volume] - Volume to play the music at
|
|
144
173
|
* @param {boolean} [loop] - Should the music loop?
|
|
145
174
|
* @param {boolean} [paused] - Should the music start paused
|
|
146
|
-
* @return {SoundInstance} - The
|
|
175
|
+
* @return {SoundInstance} - The sound instance
|
|
147
176
|
*/
|
|
148
177
|
playMusic(volume=1, loop=true, paused=false)
|
|
149
178
|
{ return this.play(undefined, volume, 1, 0, loop, paused); }
|
|
@@ -153,7 +182,7 @@ class Sound
|
|
|
153
182
|
* @param {number} [semitoneOffset=0] - How many semitones to offset pitch
|
|
154
183
|
* @param {Vector2} [pos] - World space position to play the sound if any
|
|
155
184
|
* @param {number} [volume=1] - How much to scale volume by
|
|
156
|
-
* @return {SoundInstance} - The
|
|
185
|
+
* @return {SoundInstance} - The sound instance
|
|
157
186
|
*/
|
|
158
187
|
playNote(semitoneOffset=0, pos, volume)
|
|
159
188
|
{
|
|
@@ -166,57 +195,14 @@ class Sound
|
|
|
166
195
|
* @return {number} - How long the sound is in seconds (undefined if loading)
|
|
167
196
|
*/
|
|
168
197
|
getDuration()
|
|
169
|
-
{ return this.sampleChannels?.[0]
|
|
198
|
+
{ return this.sampleChannels?.[0]?.length / this.sampleRate || 0; }
|
|
170
199
|
|
|
171
200
|
/** Check if sound is loaded, for sounds fetched from a url
|
|
172
201
|
* @return {boolean} - True if sound is loaded and ready to play
|
|
173
202
|
*/
|
|
174
203
|
isLoaded() { return this.loadedPercent === 1; }
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Sound Wave Object - Loads and stores an audio file for later use
|
|
181
|
-
* - this can be used to load and play wave, mp3, and ogg files
|
|
182
|
-
* @extends Sound
|
|
183
|
-
* @memberof Audio
|
|
184
|
-
* @example
|
|
185
|
-
* // load an audio asset file
|
|
186
|
-
* const sound_example = new SoundWave('sound.mp3');
|
|
187
|
-
*
|
|
188
|
-
* // play the sound
|
|
189
|
-
* sound_example.play();
|
|
190
|
-
*/
|
|
191
|
-
class SoundWave extends Sound
|
|
192
|
-
{
|
|
193
|
-
/**
|
|
194
|
-
* @callback SoundLoadCallback - Function called when sound is loaded
|
|
195
|
-
* @param {SoundWave} sound
|
|
196
|
-
* @memberof Audio
|
|
197
|
-
*/
|
|
198
204
|
|
|
199
|
-
/**
|
|
200
|
-
* @param {string} filename - Filename of audio file to load
|
|
201
|
-
* @param {number} [randomness] - How much to randomize frequency each time sound plays
|
|
202
|
-
* @param {number} [range=soundDefaultRange] - World space max range of sound
|
|
203
|
-
* @param {number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering
|
|
204
|
-
* @param {SoundLoadCallback} [onloadCallback] - callback function to call when sound is loaded
|
|
205
|
-
*/
|
|
206
|
-
constructor(filename, randomness=0, range, taper, onloadCallback)
|
|
207
|
-
{
|
|
208
|
-
super(undefined, range, taper);
|
|
209
|
-
if (!soundEnable || headlessMode) return;
|
|
210
|
-
ASSERT(!filename || isString(filename), 'filename must be a string');
|
|
211
|
-
ASSERT(isNumber(randomness), 'randomness must be a number');
|
|
212
|
-
|
|
213
|
-
/** @property {SoundLoadCallback} - callback function to call when sound is loaded */
|
|
214
|
-
this.onloadCallback = onloadCallback;
|
|
215
|
-
this.randomness = randomness;
|
|
216
|
-
filename && this.loadSound(filename);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/** Loads a sound from a URL and decodes it into sample data. Must be used with await!
|
|
205
|
+
/** Loads a sound from a URL and decodes it into sample data.
|
|
220
206
|
* @param {string} filename
|
|
221
207
|
* @return {Promise<void>} */
|
|
222
208
|
async loadSound(filename)
|
package/src/engineDebug.js
CHANGED
|
@@ -49,7 +49,7 @@ let debugPrimitives = [], debugPhysics = false, debugRaycast = false, debugParti
|
|
|
49
49
|
/** Asserts if the expression is false, does nothing in release builds
|
|
50
50
|
* Halts execution if the assert fails and throws an error
|
|
51
51
|
* @param {boolean} assert
|
|
52
|
-
* @param {...Object}
|
|
52
|
+
* @param {...Object} output - error message output
|
|
53
53
|
* @memberof Debug */
|
|
54
54
|
function ASSERT(assert, ...output)
|
|
55
55
|
{
|
|
@@ -59,7 +59,7 @@ function ASSERT(assert, ...output)
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/** Log to console if debug is enabled, does nothing in release builds
|
|
62
|
-
* @param {...Object}
|
|
62
|
+
* @param {...Object} output - message output
|
|
63
63
|
* @memberof Debug */
|
|
64
64
|
function LOG(...output) { console.log(...output); }
|
|
65
65
|
|
|
@@ -706,8 +706,8 @@ function debugProtectConstant(obj)
|
|
|
706
706
|
props.forEach(prop =>
|
|
707
707
|
{
|
|
708
708
|
Object.defineProperty(obj, prop, {
|
|
709
|
-
get: ()
|
|
710
|
-
set: (value)
|
|
709
|
+
get: ()=> values[prop],
|
|
710
|
+
set: (value)=>
|
|
711
711
|
{
|
|
712
712
|
ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
|
|
713
713
|
},
|