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/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
- this.body.object = this;
67
+ /** @property {Color} - Line color used for default box2d drawing */
65
68
  this.lineColor = BLACK;
66
- box2d.objects.push(this);
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} [color]
109
- * @param {Color} [lineColor]
110
- * @param {number} [lineWidth]
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 {ratio} [ratio] */
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} v */
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--;)
@@ -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} enable
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} [pos]
1235
- * @param {Vector2} [size]
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 {AudioBufferSourceNode} - The audio source node
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) // Create a zzfx sound
209
- SoundWave(filename, randomness=0, range, taper) // Load a wave, mp3, or ogg
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.1';
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
- // Frame time tracking
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
- * () => { LOG('Game initialized!'); }, // gameInit
154
- * () => { updateGameLogic(); }, // gameUpdate
155
- * () => { updateUI(); }, // gameUpdatePost
156
- * () => { drawBackground(); }, // gameRender
157
- * () => { drawHUD(); }, // gameRenderPost
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);
@@ -44,29 +44,46 @@ function audioInit()
44
44
  ///////////////////////////////////////////////////////////////////////////////
45
45
 
46
46
  /**
47
- * Sound Object - Stores a sound for later use and can be played positionally
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
- * // create a sound
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 the sound
62
+ * // play a sound
56
63
  * sound_example.play();
57
64
  */
58
65
  class Sound
59
66
  {
60
- /** Create a sound object and cache the zzfx samples for later use
61
- * @param {Array} zzfxSound - Array of zzfx parameters, ex. [.5,.5]
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(zzfxSound, range=soundDefaultRange, taper=soundDefaultTaper)
80
+ constructor(asset, randomness, range=soundDefaultRange, taper=soundDefaultTaper, onloadCallback)
66
81
  {
67
82
  if (!soundEnable || headlessMode) return;
68
83
 
69
- ASSERT(!zzfxSound || isArray(zzfxSound), 'zzfxSound is invalid');
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
- // generate zzfx sound now for fast playback
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 randomnessIndex = 1, defaultRandomness = .05;
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 audio source node
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 audio source node
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].length / this.sampleRate || 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
- /** Create a sound object and cache the wave file for later use
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)
@@ -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} [output] - error message output
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} [output] - message output
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: () => values[prop],
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
  },