I’m starting to implement sounds in my project. I’m starting by implementing footstep sounds to the player Character. For that, I’ve created a script that inherits AudioStreamPlayer3D, I called it SoundFX. It has an exported Dictionary<string, AudioStream>, and a function PlaySound(string name). It searches the dictionary for the name and if it exists, it changes the Stream of the AudioStreamPlayer to the AudioStream stored in the dictionary.
The issue I’m having is that when I change the Stream of the AudioStreamPlayer3D, it abruptly cuts off whatever sound it was playing. It’s not very noticeable with footsteps because they’re short, but with some landing sounds that last a bit longer, you can tell it’s being cut off when the next footstep plays. Even with Max Polyphony set to a high number, it still cuts off.
I guess it makes sense that changing the Stream will stop the Stream. But how do I play different sounds without cutting off the currently playing sound, then?
Here’s the SoundFX script:
public partial class SoundFX : AudioStreamPlayer3D
{
[Export]
public Dictionary<string, AudioStream> sounds { get; private set; } = new Dictionary<string, AudioStream>();
public void PlaySound(string name)
{
if (!sounds.ContainsKey(name))
{
GD.Print($"{this.Name} trying to play sound {name}, but the key isn't present in sounds Dictionary");
return;
}
Stream = sounds[name];
Play();
}
}
You could use an AudioStreamPolyphonic for the AudioStreamPlayer3D.
Instead of changing the stream, you would then call PlayStream() on its playback (with one of the AudioStreams of your dictionary as first argument).
There are other drawbacks to a centralized sound player that crop up over time, which is why I stopped using one and went back to using AudioStream nodes on things that have sounds. Problems typically crop up when:
You have to modify the volume of a single, or handful of sounds. (This results in adding in a volume argument.)
You end up with more specialized Audio Buses for things like reverb, and other audio effects for certain sounds, but not all sounds.
You start using AudioStream2D or AudioStream3D nodes which need to be attached to the object to have the correct location in the world, or need a bunch of extra code every frame to stay updated.
You reach the limits of polyphony (max channels is 32) and you have to start doing AudioStream pooling. (Which can result in memory leaks if not done properly.)
You do you, but the fact that AudioStreamPlayer is a Node was made for a lot of really good design decisions, and is optimized in C++. The more code you surround them with, the more it becomes clear why they were designed the way they were designed.
What do you mean by centralized player? In this case It’s one stream player for each character footsteps. It’s not like I’ll be using only one stream player for all sounds in the game. Not even for all sounds a character makes. The issue was I had to change streams based on surface type, and using the AudioStreamPolyphonic solved that. Or else I’d have to have a different stream player node for each surface type footstep, which I guess could work but I prefer using a single player for footsteps.
I don’t understand, I’m still using AudioStreamPlayer nodes. I’m just using an AudioStreamPolyphonic inside an AudioStreamPlayer3D node.
For anybody that might search for how to do this in the future, here’s how my SoundFX class, that inherits AudioStreamPlayer3D, looks like now:
public partial class SoundFX : AudioStreamPlayer3D
{
[Export]
public Dictionary<string, AudioStream> sounds { get; private set; } = new Dictionary<string, AudioStream>();
public override void _Ready()
{
//You have to call Play() at least once before calling GetStreamPlayback() or you get an error
Play();
}
public void PlaySound(string name)
{
if (!sounds.ContainsKey(name))
{
GD.Print($"{this.Name} trying to play sound {name}, but the key isn't present in sounds Dictionary");
return;
}
if (GetStreamPlayback() is AudioStreamPlaybackPolyphonic streamPlaybackPolyphonic)
{
streamPlaybackPolyphonic.PlayStream(sounds[name]);
}
else
{
GD.Print($"StreamPlayback is not {typeof(AudioStreamPlaybackPolyphonic)}");
}
}
}
@OleNic Don’t know if I understand what you’re asking. The node is positioned at the character’s feet position in the character scene. It’s not a single node for all characters footsteps, each character would have its own AudioStreamPlayer3D footsteps node.
My bad, totally misread you and misunderstood the setup. I read that with the polyphonic player you were playing different footsteps together, and I was wondering how would that work for positional audio to sound right in headphones or 4.1 or better speaker setup. When polyphony is a property of EACH 3d positional emitter, e.g.AudioStreamPlayer3D in Godot speak.
Sorry, carry on, these are not the droids, erh, sounds you’re looking for