This is part 2 of a 4 part tutorial to create an audio manager for unity. This tutorial focuses on creating the main methods that will play sounds, as will as tracking and managing active sounds.
Part 1 |
Part 2 |
Part 3 |
Part 4
AudioManager - ClipInfo Object
We will first create a ClipInfo object as a new data type to hold all the relevant data about each sound that
is handled by the audio manager. Right now it contains only variables for the sound clip to play and its initial volume, but it
could be expanded to hold any information you want to keep track of,
such as the time the clip started playing, or how many times it has
looped. We will add a member variable List of this ClipInfo to our AudioManager to track all audio playing, and we will update our Awake method to initialize this List.
public class AudioManager : Singleton<AudioManager> {
class ClipInfo
{
//ClipInfo used to maintain default audio source info
public AudioSource source { get; set; }
public float defaultVolume { get; set; }
}
private List<ClipInfo> m_activeAudio;
void Awake(){
m_activeAudio = new List<ClipInfo>();
[see previous tutorial]
}
}
First note that the ClipInfo class is inside the audio manager. This allows
it to be accessed only by the audio manager, hiding the data. You can
read the rules on nesting classes
here.
Second, notice that ClipInfo holds the AudioSource directly, ignoring the GameObject that the source must be attached to.
Lastly, we create a 'defaultVolume' property so that we can change the volume of the audio source dynamically, but remember what volume it was created at. More on this in part 4.
The Play Method
For the core functionality of this AudioManager, I’ve referenced
Daniel Rodriguez for the basic idea (I also recommend his
post on setting up managers for your unity projects.) The Play method of the AudioManager will:
- Create a new GameObject.
- Set its AudioSource to be our given sound clip.
- Play the AudioSource.
- Destroy the object.
The beauty of this is that we can play a given audio clip at any time by writing AudioManager.Instance.Play(parameters). We don’t have to worry about
creating the AudioManager since it is a singleton, we don’t have to worry about creating and destroying the accompanying GameObjects because everything is handled within the method.
public AudioSource Play(AudioClip clip, Vector3 soundOrigin, float volume) {
//Create an empty game object
GameObject soundLoc = new GameObject("Audio: " + clip.name);
soundLoc.transform.position = soundOrigin;
//Create the source
AudioSource source = soundLoc.AddComponent<AudioSource>();
setSource(ref source, clip, volume);
source.Play();
Destroy(soundLoc, clip.length);
//Set the source as active
m_activeAudio.Add(new ClipInfo{source = source, defaultVolume = volume});
return source;
}
For sound data, the method takes an audio
clip and a volume value as parameters, but could be extended to take other parameters to
modify the sound, such as pitch.
The other parameter here is the Vector3 that indicates the position of the
sound. This is where the sound is playing from, and uses Unity’s built
in sound management to determine the panning and volume of the sound
based on the position of the audio listener component in your scene and
the rolloff for the audio source (more in rolloff in a little bit.)
This isn’t a long method, and it is the most critical one, so I will step through the code.
//Create an empty game object
GameObject soundLoc = new GameObject("Audio: " + clip.name);
soundLoc.transform.position = soundOrigin;
This creates an empty GameObject in the world. When running the game, this
will show up in the inspector as “Audio: [whatever your clip name is as
listed in the Unity assets]”. It then moves this newly created object to the Vector3 position passed in.
Next we attach an audio source to the fresh GameObject we just created:
//Create the source
AudioSource source = soundLoc.AddComponent<AudioSource>();
setSource(ref source, clip, volume);
source.Play();
Destroy(soundLoc, clip.length);
After creating the AudioSource, we pass a reference of it to ‘setSource’,
which I will outline shortly. Basically this method takes
the values passed in and applies them to the AudioSource of our newly
created GameObject. We start the AudioSource playing, then call the
Destroy method on the new GameObject, passing clip.length as the
parameter of how long to wait before destroying the object. Since
clip.length is the time it takes to play the clip, this will destroy our
AudioSource GameObject immediately after finishing the sound. This is
essentially like playing a one shot of an AudioClip, but since it is an
AudioSource, we can do more things with the clip, such as changing it’s
volume while it is playing, or pausing it, which we will take advantage
of later.
Lastly, we register this audio source with the AudioManager as a ClipInfo
object which we created earlier. This way the AudioManager tracks the
sound and can handle it as it is playing.
//Set the source as active
m_activeAudio.Add(new ClipInfo{source = source, defaultVolume = volume});
return source;
Since ‘source’ and ‘defaultVolume’ are public properties of our ClipInfo
object, we can assign them using object initializers (using the curly
braces), which allows us to avoid the need for an explicit constructor.
We add the new ClipInfo object to our AudioManager member variable List
m_activeAudio, which maintains references to all the sounds that are
playing.
setSource Method
For the final part of this section of the tutorial, we will create the “setSource” method referenced
above in the “Play” method. This method sets the properties of our new
audio source.
private void setSource(ref AudioSource source, AudioClip clip, float volume) {
source.rolloffMode = AudioRolloffMode.Logarithmic;
source.dopplerLevel = 0.2f;
source.minDistance = 150;
source.maxDistance = 1500;
source.clip = clip;
source.volume = volume;
}
The AudioSource is sent in as a ‘ref’, so that all changes made to the AudioSource
parameter passed in will be kept after this method is finished and our
‘Play’ method resumes.
Rolloff mode is the way that Unity makes the sound decrease in volume as its
distance from the AudioListener in the scene increases. In the inspector
you can see a graph that models this.
The Linear mode means that the sound decreases at an equal rate as you get further from the listener.
The Logarithmic mode means that the sound decreases sharply as you begin
to move away from the listener, but the farther you get away from the
listener, the less the sound decreases. I opted for Logarithmic sound
because it more closely matches the way you hear sounds in reality- a
noise made at 10ft away will sound much louder than the same noise at
100ft away, but a noise at 500ft away will sound only slightly louder
than the same noise at 800ft away.
The dopplerLevel is related to how the sound is affected by movement (like sirens on a passing ambulence.)
I set this to .2 because I found that the default value of 1 can create strange sound anomalies
when rotating the camera quickly.
minDistance and maxDistance are important, particularly for Logarithmic rolloff.
minDistance is how far the sound must be from the listener before the volume starts
to decrease. This is especially important because if this is set to its
default 0, the sound will start to drop off sharply at any distance
from the listener. This means that a sound made at 1ft away will be
significantly louder than a sound made at 2 ft away, which just does not
feel right in-game. For Tumbleweed, a distance of 150 seems to be
working for now- all sounds less than 150 game ‘units’ away will play at
max volume, and after that the sound dropoff will begin.
maxDistance is important because it determines both the maximum distance at which a
sound will play, as well as the curve of sound dropoff. With
logarithmic rolloff, you can make this number really huge, and sounds
that play in the far distance and middle distance will still sound
natural.
Finally, we set the sound clip to our clip, and the default volume to our volume.
We covered a lot here. If set up
correctly, you should now be able to play any AudioClip by calling
“AudioManager.Instance.Play(AudioClip, Vector3, float)” with your
desired clip, position, and volume.
Here is a my project setup and in-game hierarchy in Unity so far:
Next time...
In
part 3 of the AudioManager tutorial, I will cover placing moving sounds on moving objects, pausing sounds, and changing volumes.