littlejsengine 1.9.6 → 1.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/littlejs.d.ts +21 -15
- package/dist/littlejs.esm.js +90 -47
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +89 -47
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +89 -47
- package/package.json +1 -1
- package/src/engine.js +2 -1
- package/src/engineAudio.js +55 -38
- package/src/engineExport.js +1 -0
- package/src/engineObject.js +17 -2
- package/src/engineParticles.js +3 -0
- package/src/engineSettings.js +6 -1
- package/src/engineUtilities.js +6 -5
package/dist/littlejs.release.js
CHANGED
|
@@ -750,14 +750,15 @@ class Color
|
|
|
750
750
|
).clamp();
|
|
751
751
|
}
|
|
752
752
|
|
|
753
|
-
/** Returns this color expressed as a
|
|
753
|
+
/** Returns this color expressed as a hex color code
|
|
754
754
|
* @param {Boolean} [useAlpha] - if alpha should be included in result
|
|
755
755
|
* @return {String} */
|
|
756
|
-
toString(useAlpha = true)
|
|
757
|
-
{
|
|
758
|
-
|
|
756
|
+
toString(useAlpha = true)
|
|
757
|
+
{
|
|
758
|
+
const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
|
|
759
|
+
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
759
760
|
}
|
|
760
|
-
|
|
761
|
+
|
|
761
762
|
/** Set this color from a hex code
|
|
762
763
|
* @param {String} hex - html hex code
|
|
763
764
|
* @return {Color} */
|
|
@@ -1254,7 +1255,12 @@ function setSoundEnable(enable) { soundEnable = enable; }
|
|
|
1254
1255
|
/** Set volume scale to apply to all sound, music and speech
|
|
1255
1256
|
* @param {Number} volume
|
|
1256
1257
|
* @memberof Settings */
|
|
1257
|
-
function setSoundVolume(volume)
|
|
1258
|
+
function setSoundVolume(volume)
|
|
1259
|
+
{
|
|
1260
|
+
soundVolume = volume;
|
|
1261
|
+
if (soundEnable && !headlessMode && audioGainNode)
|
|
1262
|
+
audioGainNode.gain.value = volume; // update gain immediatly
|
|
1263
|
+
}
|
|
1258
1264
|
|
|
1259
1265
|
/** Set default range where sound no longer plays
|
|
1260
1266
|
* @param {Number} range
|
|
@@ -1387,6 +1393,8 @@ class EngineObject
|
|
|
1387
1393
|
this.spawnTime = time;
|
|
1388
1394
|
/** @property {Array} - List of children of this object */
|
|
1389
1395
|
this.children = [];
|
|
1396
|
+
/** @property {Boolean} - Limit object speed using linear or circular math */
|
|
1397
|
+
this.clampSpeedLinear = true;
|
|
1390
1398
|
|
|
1391
1399
|
// parent child system
|
|
1392
1400
|
/** @property {EngineObject} - Parent of object if in local space */
|
|
@@ -1435,8 +1443,21 @@ class EngineObject
|
|
|
1435
1443
|
return;
|
|
1436
1444
|
|
|
1437
1445
|
// limit max speed to prevent missing collisions
|
|
1438
|
-
|
|
1439
|
-
|
|
1446
|
+
if (this.clampSpeedLinear)
|
|
1447
|
+
{
|
|
1448
|
+
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
1449
|
+
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
1450
|
+
}
|
|
1451
|
+
else
|
|
1452
|
+
{
|
|
1453
|
+
const length2 = this.velocity.lengthSquared();
|
|
1454
|
+
if (length2 > objectMaxSpeed*objectMaxSpeed)
|
|
1455
|
+
{
|
|
1456
|
+
const s = objectMaxSpeed / length2**.5;
|
|
1457
|
+
this.velocity.x *= s;
|
|
1458
|
+
this.velocity.y *= s;
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1440
1461
|
|
|
1441
1462
|
// apply physics
|
|
1442
1463
|
const oldPos = this.pos.copy();
|
|
@@ -2719,6 +2740,32 @@ function touchGamepadRender()
|
|
|
2719
2740
|
|
|
2720
2741
|
|
|
2721
2742
|
|
|
2743
|
+
/** Audio context used by the engine
|
|
2744
|
+
* @type {AudioContext}
|
|
2745
|
+
* @memberof Audio */
|
|
2746
|
+
let audioContext;
|
|
2747
|
+
|
|
2748
|
+
/** Master gain node for all audio to pass through
|
|
2749
|
+
* @type {GainNode}
|
|
2750
|
+
* @memberof Audio */
|
|
2751
|
+
let audioGainNode;
|
|
2752
|
+
|
|
2753
|
+
function audioInit()
|
|
2754
|
+
{
|
|
2755
|
+
if (!soundEnable || headlessMode) return;
|
|
2756
|
+
|
|
2757
|
+
// create audio context
|
|
2758
|
+
audioContext = new AudioContext;
|
|
2759
|
+
|
|
2760
|
+
// create and connect gain node
|
|
2761
|
+
// (createGain is more widely spported then GainNode construtor)
|
|
2762
|
+
audioGainNode = audioContext.createGain();
|
|
2763
|
+
audioGainNode.connect(audioContext.destination);
|
|
2764
|
+
setSoundVolume(soundVolume); // update gain volume
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2768
|
+
|
|
2722
2769
|
/**
|
|
2723
2770
|
* Sound Object - Stores a sound for later use and can be played positionally
|
|
2724
2771
|
*
|
|
@@ -2770,7 +2817,8 @@ class Sound
|
|
|
2770
2817
|
*/
|
|
2771
2818
|
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false)
|
|
2772
2819
|
{
|
|
2773
|
-
if (!soundEnable ||
|
|
2820
|
+
if (!soundEnable || headlessMode) return;
|
|
2821
|
+
if (!this.sampleChannels) return;
|
|
2774
2822
|
|
|
2775
2823
|
let pan;
|
|
2776
2824
|
if (pos)
|
|
@@ -2847,16 +2895,14 @@ class SoundWave extends Sound
|
|
|
2847
2895
|
* @param {Number} [randomness] - How much to randomize frequency each time sound plays
|
|
2848
2896
|
* @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
|
|
2849
2897
|
* @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
|
|
2898
|
+
* @param {Function} [onloadCallback] - callback function to call when sound is loaded
|
|
2850
2899
|
*/
|
|
2851
|
-
constructor(filename, randomness=0, range, taper)
|
|
2900
|
+
constructor(filename, randomness=0, range, taper, onloadCallback)
|
|
2852
2901
|
{
|
|
2853
2902
|
super(undefined, range, taper);
|
|
2854
|
-
this.randomness = randomness;
|
|
2855
|
-
|
|
2856
2903
|
if (!soundEnable || headlessMode) return;
|
|
2857
|
-
if (!audioContext)
|
|
2858
|
-
audioContext = new AudioContext; // create audio context
|
|
2859
2904
|
|
|
2905
|
+
this.randomness = randomness;
|
|
2860
2906
|
fetch(filename)
|
|
2861
2907
|
.then(response => response.arrayBuffer())
|
|
2862
2908
|
.then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
|
|
@@ -2866,10 +2912,23 @@ class SoundWave extends Sound
|
|
|
2866
2912
|
for (let i = audioBuffer.numberOfChannels; i--;)
|
|
2867
2913
|
this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
|
|
2868
2914
|
this.sampleRate = audioBuffer.sampleRate;
|
|
2869
|
-
});
|
|
2915
|
+
}).then(() => onloadCallback && onloadCallback(this));
|
|
2870
2916
|
}
|
|
2871
2917
|
}
|
|
2872
2918
|
|
|
2919
|
+
/** Play an mp3, ogg, or wav audio from a local file or url
|
|
2920
|
+
* @param {String} filename - Location of sound file to play
|
|
2921
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
2922
|
+
* @param {Boolean} [loop] - True if the music should loop
|
|
2923
|
+
* @return {SoundWave} - The sound object for this file
|
|
2924
|
+
* @memberof Audio */
|
|
2925
|
+
function playAudioFile(filename, volume=1, loop=false)
|
|
2926
|
+
{
|
|
2927
|
+
if (!soundEnable || headlessMode) return;
|
|
2928
|
+
|
|
2929
|
+
return new SoundWave(filename,0,0,0, s=>s.play(undefined, volume, 1, 1, loop));
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2873
2932
|
/**
|
|
2874
2933
|
* Music Object - Stores a zzfx music track for later use
|
|
2875
2934
|
*
|
|
@@ -2924,23 +2983,6 @@ class Music extends Sound
|
|
|
2924
2983
|
{ return super.play(undefined, volume, 1, 1, loop); }
|
|
2925
2984
|
}
|
|
2926
2985
|
|
|
2927
|
-
/** Play an mp3, ogg, or wav audio from a local file or url
|
|
2928
|
-
* @param {String} filename - Location of sound file to play
|
|
2929
|
-
* @param {Number} [volume] - How much to scale volume by
|
|
2930
|
-
* @param {Boolean} [loop] - True if the music should loop
|
|
2931
|
-
* @return {HTMLAudioElement} - The audio element for this sound
|
|
2932
|
-
* @memberof Audio */
|
|
2933
|
-
function playAudioFile(filename, volume=1, loop=false)
|
|
2934
|
-
{
|
|
2935
|
-
if (!soundEnable || headlessMode) return;
|
|
2936
|
-
|
|
2937
|
-
const audio = new Audio(filename);
|
|
2938
|
-
audio.volume = soundVolume * volume;
|
|
2939
|
-
audio.loop = loop;
|
|
2940
|
-
audio.play();
|
|
2941
|
-
return audio;
|
|
2942
|
-
}
|
|
2943
|
-
|
|
2944
2986
|
/** Speak text with passed in settings
|
|
2945
2987
|
* @param {String} text - The text to speak
|
|
2946
2988
|
* @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
@@ -2951,7 +2993,8 @@ function playAudioFile(filename, volume=1, loop=false)
|
|
|
2951
2993
|
* @memberof Audio */
|
|
2952
2994
|
function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
2953
2995
|
{
|
|
2954
|
-
if (!soundEnable ||
|
|
2996
|
+
if (!soundEnable || headlessMode) return;
|
|
2997
|
+
if (!speechSynthesis) return;
|
|
2955
2998
|
|
|
2956
2999
|
// common languages (not supported by all browsers)
|
|
2957
3000
|
// en - english, it - italian, fr - french, de - german, es - spanish
|
|
@@ -2981,14 +3024,8 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
2981
3024
|
|
|
2982
3025
|
///////////////////////////////////////////////////////////////////////////////
|
|
2983
3026
|
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
* @memberof Audio */
|
|
2987
|
-
let audioContext;
|
|
2988
|
-
|
|
2989
|
-
/** Keep track if audio was suspended when last sound was played
|
|
2990
|
-
* @type {Boolean}
|
|
2991
|
-
* @memberof Audio */
|
|
3027
|
+
// internal tracking if audio was suspended when last sound was played
|
|
3028
|
+
// allows first suspended sound to play when audio is resumed
|
|
2992
3029
|
let audioSuspended = false;
|
|
2993
3030
|
|
|
2994
3031
|
/** Play cached audio samples with given settings
|
|
@@ -3003,8 +3040,6 @@ let audioSuspended = false;
|
|
|
3003
3040
|
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
|
|
3004
3041
|
{
|
|
3005
3042
|
if (!soundEnable || headlessMode) return;
|
|
3006
|
-
if (!audioContext)
|
|
3007
|
-
audioContext = new AudioContext; // create audio context
|
|
3008
3043
|
|
|
3009
3044
|
// prevent sounds from building up if they can't be played
|
|
3010
3045
|
const audioWasSuspended = audioSuspended;
|
|
@@ -3028,10 +3063,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
3028
3063
|
source.playbackRate.value = rate;
|
|
3029
3064
|
source.loop = loop;
|
|
3030
3065
|
|
|
3031
|
-
//
|
|
3066
|
+
// set master gain volume
|
|
3067
|
+
setSoundVolume(soundVolume);
|
|
3068
|
+
|
|
3069
|
+
// create and connect gain node
|
|
3032
3070
|
const gainNode = audioContext.createGain();
|
|
3033
|
-
gainNode.gain.value =
|
|
3034
|
-
gainNode.connect(
|
|
3071
|
+
gainNode.gain.value = volume;
|
|
3072
|
+
gainNode.connect(audioGainNode);
|
|
3035
3073
|
|
|
3036
3074
|
// connect source to stereo panner and gain
|
|
3037
3075
|
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)})).connect(gainNode);
|
|
@@ -3938,6 +3976,9 @@ class Particle extends EngineObject
|
|
|
3938
3976
|
this.localSpaceEmitter = localSpaceEmitter;
|
|
3939
3977
|
/** @property {Function} - Called when particle dies */
|
|
3940
3978
|
this.destroyCallback = destroyCallback;
|
|
3979
|
+
|
|
3980
|
+
// particles use circular clamped speed
|
|
3981
|
+
this.clampSpeedLinear = false;
|
|
3941
3982
|
}
|
|
3942
3983
|
|
|
3943
3984
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
|
@@ -4717,7 +4758,7 @@ const engineName = 'LittleJS';
|
|
|
4717
4758
|
* @type {String}
|
|
4718
4759
|
* @default
|
|
4719
4760
|
* @memberof Engine */
|
|
4720
|
-
const engineVersion = '1.9.
|
|
4761
|
+
const engineVersion = '1.9.7';
|
|
4721
4762
|
|
|
4722
4763
|
/** Frames per second to update
|
|
4723
4764
|
* @type {Number}
|
|
@@ -4950,6 +4991,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4950
4991
|
|
|
4951
4992
|
// init stuff and start engine
|
|
4952
4993
|
inputInit();
|
|
4994
|
+
audioInit();
|
|
4953
4995
|
debugInit();
|
|
4954
4996
|
glInit();
|
|
4955
4997
|
|
package/package.json
CHANGED
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.9.
|
|
33
|
+
const engineVersion = '1.9.7';
|
|
34
34
|
|
|
35
35
|
/** Frames per second to update
|
|
36
36
|
* @type {Number}
|
|
@@ -263,6 +263,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
263
263
|
|
|
264
264
|
// init stuff and start engine
|
|
265
265
|
inputInit();
|
|
266
|
+
audioInit();
|
|
266
267
|
debugInit();
|
|
267
268
|
glInit();
|
|
268
269
|
|
package/src/engineAudio.js
CHANGED
|
@@ -11,6 +11,32 @@
|
|
|
11
11
|
|
|
12
12
|
'use strict';
|
|
13
13
|
|
|
14
|
+
/** Audio context used by the engine
|
|
15
|
+
* @type {AudioContext}
|
|
16
|
+
* @memberof Audio */
|
|
17
|
+
let audioContext;
|
|
18
|
+
|
|
19
|
+
/** Master gain node for all audio to pass through
|
|
20
|
+
* @type {GainNode}
|
|
21
|
+
* @memberof Audio */
|
|
22
|
+
let audioGainNode;
|
|
23
|
+
|
|
24
|
+
function audioInit()
|
|
25
|
+
{
|
|
26
|
+
if (!soundEnable || headlessMode) return;
|
|
27
|
+
|
|
28
|
+
// create audio context
|
|
29
|
+
audioContext = new AudioContext;
|
|
30
|
+
|
|
31
|
+
// create and connect gain node
|
|
32
|
+
// (createGain is more widely spported then GainNode construtor)
|
|
33
|
+
audioGainNode = audioContext.createGain();
|
|
34
|
+
audioGainNode.connect(audioContext.destination);
|
|
35
|
+
setSoundVolume(soundVolume); // update gain volume
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
39
|
+
|
|
14
40
|
/**
|
|
15
41
|
* Sound Object - Stores a sound for later use and can be played positionally
|
|
16
42
|
*
|
|
@@ -62,7 +88,8 @@ class Sound
|
|
|
62
88
|
*/
|
|
63
89
|
play(pos, volume=1, pitch=1, randomnessScale=1, loop=false)
|
|
64
90
|
{
|
|
65
|
-
if (!soundEnable ||
|
|
91
|
+
if (!soundEnable || headlessMode) return;
|
|
92
|
+
if (!this.sampleChannels) return;
|
|
66
93
|
|
|
67
94
|
let pan;
|
|
68
95
|
if (pos)
|
|
@@ -139,16 +166,14 @@ class SoundWave extends Sound
|
|
|
139
166
|
* @param {Number} [randomness] - How much to randomize frequency each time sound plays
|
|
140
167
|
* @param {Number} [range=soundDefaultRange] - World space max range of sound, will not play if camera is farther away
|
|
141
168
|
* @param {Number} [taper=soundDefaultTaper] - At what percentage of range should it start tapering off
|
|
169
|
+
* @param {Function} [onloadCallback] - callback function to call when sound is loaded
|
|
142
170
|
*/
|
|
143
|
-
constructor(filename, randomness=0, range, taper)
|
|
171
|
+
constructor(filename, randomness=0, range, taper, onloadCallback)
|
|
144
172
|
{
|
|
145
173
|
super(undefined, range, taper);
|
|
146
|
-
this.randomness = randomness;
|
|
147
|
-
|
|
148
174
|
if (!soundEnable || headlessMode) return;
|
|
149
|
-
if (!audioContext)
|
|
150
|
-
audioContext = new AudioContext; // create audio context
|
|
151
175
|
|
|
176
|
+
this.randomness = randomness;
|
|
152
177
|
fetch(filename)
|
|
153
178
|
.then(response => response.arrayBuffer())
|
|
154
179
|
.then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
|
|
@@ -158,10 +183,23 @@ class SoundWave extends Sound
|
|
|
158
183
|
for (let i = audioBuffer.numberOfChannels; i--;)
|
|
159
184
|
this.sampleChannels[i] = Array.from(audioBuffer.getChannelData(i));
|
|
160
185
|
this.sampleRate = audioBuffer.sampleRate;
|
|
161
|
-
});
|
|
186
|
+
}).then(() => onloadCallback && onloadCallback(this));
|
|
162
187
|
}
|
|
163
188
|
}
|
|
164
189
|
|
|
190
|
+
/** Play an mp3, ogg, or wav audio from a local file or url
|
|
191
|
+
* @param {String} filename - Location of sound file to play
|
|
192
|
+
* @param {Number} [volume] - How much to scale volume by
|
|
193
|
+
* @param {Boolean} [loop] - True if the music should loop
|
|
194
|
+
* @return {SoundWave} - The sound object for this file
|
|
195
|
+
* @memberof Audio */
|
|
196
|
+
function playAudioFile(filename, volume=1, loop=false)
|
|
197
|
+
{
|
|
198
|
+
if (!soundEnable || headlessMode) return;
|
|
199
|
+
|
|
200
|
+
return new SoundWave(filename,0,0,0, s=>s.play(undefined, volume, 1, 1, loop));
|
|
201
|
+
}
|
|
202
|
+
|
|
165
203
|
/**
|
|
166
204
|
* Music Object - Stores a zzfx music track for later use
|
|
167
205
|
*
|
|
@@ -216,23 +254,6 @@ class Music extends Sound
|
|
|
216
254
|
{ return super.play(undefined, volume, 1, 1, loop); }
|
|
217
255
|
}
|
|
218
256
|
|
|
219
|
-
/** Play an mp3, ogg, or wav audio from a local file or url
|
|
220
|
-
* @param {String} filename - Location of sound file to play
|
|
221
|
-
* @param {Number} [volume] - How much to scale volume by
|
|
222
|
-
* @param {Boolean} [loop] - True if the music should loop
|
|
223
|
-
* @return {HTMLAudioElement} - The audio element for this sound
|
|
224
|
-
* @memberof Audio */
|
|
225
|
-
function playAudioFile(filename, volume=1, loop=false)
|
|
226
|
-
{
|
|
227
|
-
if (!soundEnable || headlessMode) return;
|
|
228
|
-
|
|
229
|
-
const audio = new Audio(filename);
|
|
230
|
-
audio.volume = soundVolume * volume;
|
|
231
|
-
audio.loop = loop;
|
|
232
|
-
audio.play();
|
|
233
|
-
return audio;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
257
|
/** Speak text with passed in settings
|
|
237
258
|
* @param {String} text - The text to speak
|
|
238
259
|
* @param {String} [language] - The language/accent to use (examples: en, it, ru, ja, zh)
|
|
@@ -243,7 +264,8 @@ function playAudioFile(filename, volume=1, loop=false)
|
|
|
243
264
|
* @memberof Audio */
|
|
244
265
|
function speak(text, language='', volume=1, rate=1, pitch=1)
|
|
245
266
|
{
|
|
246
|
-
if (!soundEnable ||
|
|
267
|
+
if (!soundEnable || headlessMode) return;
|
|
268
|
+
if (!speechSynthesis) return;
|
|
247
269
|
|
|
248
270
|
// common languages (not supported by all browsers)
|
|
249
271
|
// en - english, it - italian, fr - french, de - german, es - spanish
|
|
@@ -273,14 +295,8 @@ function getNoteFrequency(semitoneOffset, rootFrequency=220)
|
|
|
273
295
|
|
|
274
296
|
///////////////////////////////////////////////////////////////////////////////
|
|
275
297
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
* @memberof Audio */
|
|
279
|
-
let audioContext;
|
|
280
|
-
|
|
281
|
-
/** Keep track if audio was suspended when last sound was played
|
|
282
|
-
* @type {Boolean}
|
|
283
|
-
* @memberof Audio */
|
|
298
|
+
// internal tracking if audio was suspended when last sound was played
|
|
299
|
+
// allows first suspended sound to play when audio is resumed
|
|
284
300
|
let audioSuspended = false;
|
|
285
301
|
|
|
286
302
|
/** Play cached audio samples with given settings
|
|
@@ -295,8 +311,6 @@ let audioSuspended = false;
|
|
|
295
311
|
function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sampleRate=zzfxR)
|
|
296
312
|
{
|
|
297
313
|
if (!soundEnable || headlessMode) return;
|
|
298
|
-
if (!audioContext)
|
|
299
|
-
audioContext = new AudioContext; // create audio context
|
|
300
314
|
|
|
301
315
|
// prevent sounds from building up if they can't be played
|
|
302
316
|
const audioWasSuspended = audioSuspended;
|
|
@@ -320,10 +334,13 @@ function playSamples(sampleChannels, volume=1, rate=1, pan=0, loop=false, sample
|
|
|
320
334
|
source.playbackRate.value = rate;
|
|
321
335
|
source.loop = loop;
|
|
322
336
|
|
|
323
|
-
//
|
|
337
|
+
// set master gain volume
|
|
338
|
+
setSoundVolume(soundVolume);
|
|
339
|
+
|
|
340
|
+
// create and connect gain node
|
|
324
341
|
const gainNode = audioContext.createGain();
|
|
325
|
-
gainNode.gain.value =
|
|
326
|
-
gainNode.connect(
|
|
342
|
+
gainNode.gain.value = volume;
|
|
343
|
+
gainNode.connect(audioGainNode);
|
|
327
344
|
|
|
328
345
|
// connect source to stereo panner and gain
|
|
329
346
|
source.connect(new StereoPannerNode(audioContext, {'pan':clamp(pan, -1, 1)})).connect(gainNode);
|
package/src/engineExport.js
CHANGED
package/src/engineObject.js
CHANGED
|
@@ -85,6 +85,8 @@ class EngineObject
|
|
|
85
85
|
this.spawnTime = time;
|
|
86
86
|
/** @property {Array} - List of children of this object */
|
|
87
87
|
this.children = [];
|
|
88
|
+
/** @property {Boolean} - Limit object speed using linear or circular math */
|
|
89
|
+
this.clampSpeedLinear = true;
|
|
88
90
|
|
|
89
91
|
// parent child system
|
|
90
92
|
/** @property {EngineObject} - Parent of object if in local space */
|
|
@@ -133,8 +135,21 @@ class EngineObject
|
|
|
133
135
|
return;
|
|
134
136
|
|
|
135
137
|
// limit max speed to prevent missing collisions
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
if (this.clampSpeedLinear)
|
|
139
|
+
{
|
|
140
|
+
this.velocity.x = clamp(this.velocity.x, -objectMaxSpeed, objectMaxSpeed);
|
|
141
|
+
this.velocity.y = clamp(this.velocity.y, -objectMaxSpeed, objectMaxSpeed);
|
|
142
|
+
}
|
|
143
|
+
else
|
|
144
|
+
{
|
|
145
|
+
const length2 = this.velocity.lengthSquared();
|
|
146
|
+
if (length2 > objectMaxSpeed*objectMaxSpeed)
|
|
147
|
+
{
|
|
148
|
+
const s = objectMaxSpeed / length2**.5;
|
|
149
|
+
this.velocity.x *= s;
|
|
150
|
+
this.velocity.y *= s;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
138
153
|
|
|
139
154
|
// apply physics
|
|
140
155
|
const oldPos = this.pos.copy();
|
package/src/engineParticles.js
CHANGED
|
@@ -274,6 +274,9 @@ class Particle extends EngineObject
|
|
|
274
274
|
this.localSpaceEmitter = localSpaceEmitter;
|
|
275
275
|
/** @property {Function} - Called when particle dies */
|
|
276
276
|
this.destroyCallback = destroyCallback;
|
|
277
|
+
|
|
278
|
+
// particles use circular clamped speed
|
|
279
|
+
this.clampSpeedLinear = false;
|
|
277
280
|
}
|
|
278
281
|
|
|
279
282
|
/** Render the particle, automatically called each frame, sorted by renderOrder */
|
package/src/engineSettings.js
CHANGED
|
@@ -417,7 +417,12 @@ function setSoundEnable(enable) { soundEnable = enable; }
|
|
|
417
417
|
/** Set volume scale to apply to all sound, music and speech
|
|
418
418
|
* @param {Number} volume
|
|
419
419
|
* @memberof Settings */
|
|
420
|
-
function setSoundVolume(volume)
|
|
420
|
+
function setSoundVolume(volume)
|
|
421
|
+
{
|
|
422
|
+
soundVolume = volume;
|
|
423
|
+
if (soundEnable && !headlessMode && audioGainNode)
|
|
424
|
+
audioGainNode.gain.value = volume; // update gain immediatly
|
|
425
|
+
}
|
|
421
426
|
|
|
422
427
|
/** Set default range where sound no longer plays
|
|
423
428
|
* @param {Number} range
|
package/src/engineUtilities.js
CHANGED
|
@@ -715,14 +715,15 @@ class Color
|
|
|
715
715
|
).clamp();
|
|
716
716
|
}
|
|
717
717
|
|
|
718
|
-
/** Returns this color expressed as a
|
|
718
|
+
/** Returns this color expressed as a hex color code
|
|
719
719
|
* @param {Boolean} [useAlpha] - if alpha should be included in result
|
|
720
720
|
* @return {String} */
|
|
721
|
-
toString(useAlpha = true)
|
|
722
|
-
{
|
|
723
|
-
|
|
721
|
+
toString(useAlpha = true)
|
|
722
|
+
{
|
|
723
|
+
const toHex = (c)=> ((c=c*255|0)<16 ? '0' : '') + c.toString(16);
|
|
724
|
+
return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (useAlpha ? toHex(this.a) : '');
|
|
724
725
|
}
|
|
725
|
-
|
|
726
|
+
|
|
726
727
|
/** Set this color from a hex code
|
|
727
728
|
* @param {String} hex - html hex code
|
|
728
729
|
* @return {Color} */
|