Understanding and Implementing 3D Audio in GameMaker: Studio
Audio is a crucial element of the overall gaming experience, and GameMaker: Studio provides a relatively simple way for you to implement 3D audio in your projects.
To better understand the concept of 3D audio, just imagine adding a pair of digital ears to a player-controlled game object. As the player approaches a source of sound within the game, the volume of that source becomes louder; as the player moves away, the volume decreases until finally becoming silent. The position of the player in relation to the audio source will also result in directional stereo audio, and sound velocity applied to a moving object can be applied to create a Doppler effect.
This set of immersive audio features can be accomplished through a system of audio emitters and an audio listener. In this tutorial, we'll create two scenes that incorporate 3D audio in different ways, as seen in the demo video. You can download the source files and compiled demo EXE from GitHub.
Understanding 3D Audio
The concepts involved in 3D audio are similar across all game development environments, because they are based on familiar real-world rules: sound becomes louder as you approach the source, and sound originating on your right will be louder in your right ear. These basic principles can be achieved manually by calculating the gain and output of an audio source in relation to a specific game object, but GameMaker: Studio provides a set of powerful and easy-to-use functions that give you direct control of the audio API.
The audio emitter acts as the source of a 3D sound asset in GameMaker: Studio. The emitter is defined by a user-created variable and a position
(x, y, z) that can be either static or dynamic. The volume of the emitter is controlled by gain and falloff values, and real-time effects can be applied through the pitch and velocity values. The way in which the emitter handles audio falloff is determined by the distance model applied. Multiple audio emitters can exist within the same scene.
The audio listener acts as the "ears" that receive the 3D sound sent by the emitter. The listener is located at a position
(x, y, z) that can also be static or dynamic, but the orientation of the listener is just as important.
Listener orientation determines the precise direction and angle that the listener is "looking at". The default listener orientation in GameMaker: Studio results in the left and right audio channels being reversed for 3D Audio, which we will correct later during implementation.
The Doppler Effect
Without going into a lengthy and thorough explanation of the Doppler effect, it can be described as the change in frequency of sound in relation to the movement and speed of the emitter or listener. In GameMaker: Studio, the Doppler effect is achieved by assigning vectors to the velocity of the audio emitter or listener. For an in-depth explanation, read the Doppler effect entry on Wikipedia.
Implementing 3D Audio
Implementing 3D audio in your GameMaker project is a three-step process. You must properly define the emitter within an existing game object, place this object in the room, and tell the emitter when and how to play a sound. You must then define the orientation and position of the listener within an existing game object that is present in the same room as the emitter.
The first step, however, involves importing and defining the audio assets.
Start by clicking on Resources in the main GameMaker tool bar and selecting Create Sound from the drop-down menu. Enter a name for the sound asset and select the desired sound attributes for streaming and file compression. Then, in the Target Options section of the Sound Properties window, click the first drop-down menu and select 3D from the list of options.
A Stationary Emitter
In our first example, we will create a kind of isometric Sim City-style instance of 3D audio with a stationary emitter. Our listener will be tied to a game object that follows the mouse cursor, and the emitter of the 3D sound will remain within an object that does not move. This object will be represented by a small cluster of buildings, and the emitter will play an ambient city-scape sound with bustling traffic and car horns.
This will allow us to test out the audio falloff properties of the 3D audio system, and we will be able to better understand the importance of listener orientation.
Creating the Listener
Our 3D audio listener will exist within an object that follows the mouse cursor. In this example, the object will be identified by a sprite that resembles a GPS marker. The
obj_listener object contains two important blocks of code: one in the
Create event, and one in the
Click the Add Event button and select Create to add a
Create event to
obj_listener. Drag the Execute Code icon from the Control tab of the
obj_listener properties window into the Actions section of the Create event panel. Double-click the Execute Code icon to open the GML code editor, and add the following line of code:
This code ensures that, as soon as the listener object is created, it will be positioned in 3D space to look towards the screen, right-side-up. Audio coming from an emitter located to the right of the listener will be more present in the right speaker, and vice versa. This is the standard setup that you will want to use in nearly every type of listener implementation in GameMaker: Studio.
If you wanted the left and right audio to be swapped, then you would use the following code for orientation:
Step event to
obj_listener and drag another Execute Code icon into the Actions panel. Open the GML code editor once again, and enter the following lines of code:
x = mouse_x; y = mouse_y; audio_listener_position(x, y, 0);
This code finds the current location of the mouse cursor and assigns these coordinates to the position of the
obj_listener object. The position of the audio listener is also defined by these coordinates. By placing this code in the
Step event, we ensure that the
obj_listener object, its accompanying sprite, and the listener itself are always at the same location, since the
Step event code is executed at every frame during runtime.
0 in this code is the z-position of the listener. Since our game is in 2D, our listener will always be at
0 on the z-axis.)
Open the room where you want the listener object to appear. Click the Objects tab and click within the panel to display the list of available objects. Select your listener object and click within the room to place it. The starting position of this object doesn't matter, because it will be updated to match the position of the mouse cursor at runtime.
That's it for the listener. Now we have to create something for the listener to hear.
Creating the Emitter
As with the listener, we will be focusing exclusively on the
Step events of the emitter object. In this case, we will place the emitter in the
Step events to
obj_city ,and add an Execute Code action to both events.
In the GML code of the
Create event, add the following:
s_emit = audio_emitter_create(); audio_falloff_set_model(audio_falloff_exponent_distance); audio_emitter_falloff(s_emit, 50, 200, 1); audio_play_sound_on(s_emit, snd_cityaudio, true, 1);
s_emit is the name of our new emitter.)
We chose the Exponential Distance falloff model for this example because it gives a steady gain falloff with a sharp increase once you meet the Reference Distance point. This allows the 3D audio volume to decrease as you get further away from the object, smoothly, without any harsh jumps, until you get extremely close to the source. For a full list of falloff models and their in-depth descriptions, read GameMaker's falloff models documentation.
audio_emitter_falloff(emitter, falloff_ref, falloff_max, falloff_factor) is where we set up the attributes of the falloff model:
falloff_refis the point at which the volume falloff will begin to start.
falloff_maxis the point at which the listener will no longer be able to hear the emitter.
falloff_factoris a number used in the
audio_falloff_set_modelcalculation to determine the results of the falloff. In our example, we use the default value of
1so that our
s_emitemitter will begin to fall off once the listener is 100 pixels away from the emitter, and the listener will be unable to hear the emitter at a distance of 300 pixels or greater.
audio_play_sound_on(emitter, sound, loop, priority) is how we get the emitter to begin playing our 3D sound:
snd_cityaudio3D audio asset that we imported earlier.
loopdetermines whether this sound should repeat or not.
priorityis a value from
100that determines the 'importance' of the sound being played—larger numbers represent higher priority sounds.
In our example, we only have one sound being played, so the priority does not matter. And since this code is executed in the
Create event and we have chosen to loop the sound, this 3D audio asset will play as soon as the room is loaded, and will continue playing until instructed to stop. And that brings us to a very important step:
This audio asset will play even when you load another room that doesn't contain the emitter. In order to effectively stop this asset from playing in any room other than the room that contains the emitter, we have to add a
Room End event to
In the Actions panel of the
Room End event, add another Execute Code icon with the following GML code:
Now all that's left to do is place the
obj_city object into the room like you placed the listener before. Run the game, and experiment with the position of the mouse cursor to change the location of the listener in relation to the emitter to see exactly how 3D audio works in practice.
A Moving Emitter With the Doppler Effect
In the second example, we will attach the emitter to a moving object and apply the Doppler effect while using the same listener object that we created earlier.
A bullet object is moving from the right side of the screen to the left. When the bullet reaches the edge of the screen, it wraps around and continues from where it started. (We won't go through how to create this movement.) The bullet object emits a looping sound that can be described as a "whizzing" or "buzzing" sound. When the object approaches the listener, the frequency of the sound will change, due to the Doppler effect.
Creating the Emitter
Create and a
Step event to the
obj_bullet object, and add the Execute Code action to both events.
Create event, add the following code to the GML editor:
s_emit2 = audio_emitter_create(); audio_emitter_falloff(s_emit2, 25, 200, 1.5); audio_play_sound_on(s_emit2, snd_bullet, true, 1);
s_emit2 is the name of our new emitter.
This emitter will use the same falloff model defined in the previous room, since only one type of falloff model can be active at once, and our original model is suitable for this example. The new falloff properties utilize a falloff reference of
25 pixels, a falloff maximum of
200 pixels, and a falloff factor of
2.5 for calculation. The
snd_bullet 3D sound asset begins playing as soon as the object is created, with a priority of
1, and will loop indefinitely.
In the GML editor of the
Step event, add this line of code:
audio_emitter_position(s_emit2, x, y, 0);
Since the object moves and the x- and y-values are constantly changing, the position of the emitter will also be updated to the new position at every step.
Don't forget to add the bullet object to the room, and be sure to add the
sound_stop(snd_bullet) code to the object's
Room End event.
Creating the Doppler Effect
The 3D sound system is now in place for the bullet object, and the listener works properly, but let's make things more interesting with the Doppler effect.
Adding the Doppler effect is very simple, actually. Just open the Execute Code action in the
Create event, and add the following line of code:
audio_emitter_velocity(s_emit2, -25, -25, 0);
audio_emitter_velocity(emitter, vx, vy, vz) allows us to define the properties of the Doppler effect:
vxis the velocity (in pixels per step) along the x-axis.
vyis the velocity (in pixels per step) along the y-axis.
vzapplies to velocity along the z-axis, but our game is in 2D, so we are setting this to
Typical Doppler effect setups use the
vspeed variables of an object to determine the velocity values, but in this case, we just use a value of
-25 to create a more realistic bullet sound. You can test the object with the following code to notice the difference:
audio_emitter_velocity(s_emit2, hspeed, vspeed, 0);
3D audio can add a great deal of depth and ambience to your game project, with minimal effort required. Here are just a few examples to get you started:
- A hide-and-seek game where you have to listen to audio clues to find a hidden object.
- Electricity or fire hazards in a platformer that warn you of the dangers as you get closer.
- A Frogger-style game where cars zoom past you using the Doppler effect.
- Running water in a cave or wind blowing through an air duct to lead you to the exit.
- A spy game where you have to get close to a target to hear a conversation.