Chapter 14: SoundEffects and Music
Learn how to load and play sound effects and background music in MonoGame including managing audio volume, looping, and handling multiple sound effects at once.
In Chapter 12, we implemented collision detection to enable interactions between game objects; the slime can now "eat" the bat, which respawns in a random location, while the bat bounces off walls of the dungeon. While these mechanics work visually, our game lacks an important element of player feedback: audio.
Audio plays a crucial role in game development by providing immediate feedback for player actions and creating atmosphere. Sound effects alert players when events occur (like collisions or collecting items), while background music helps establish mood and atmosphere.
In this chapter, you will:
- Learn how MonoGame handles different types of audio content.
- Learn how to load and play sound effects and music using the content pipeline.
- Implement sound effects for collision events.
- Add background music to enhance atmosphere.
Let's start by understanding how MonoGame approaches audio content.
Understanding Audio in MonoGame
Recall from Chapter 01 that MonoGame is an implementation of the XNA API. With XNA, there were two methods for implementing audio in your game: the Microsoft Cross-Platform Audio Creation Tool (XACT) and the simplified sound API.
Important
XACT is a mini audio engineering studio where you can easily edit the audio for your game like editing volume, pitch, looping, applying effects, and other properties without having to do it in code. At that time, XACT for XNA games was akin to what FMOD Studio is today for game audio.
![]() |
---|
Figure 14-1: Microsoft Cross-Platform Audio Creation Tool |
While XACT projects are still fully supported in MonoGame, it remains a Windows-only tool that has not been updated since Microsoft discontinued the original XNA, nor has its source code been made open source. Though it is possible to install XACT on modern Windows, the process can be complex.
For these reasons, this tutorial will focus on the simplified sound API, which provides all the core functionality needed for most games while remaining cross-platform compatible.
The simplified sound API approaches audio management through two distinct paths, each optimized for different use cases in games. When adding audio to your game, you need to consider how different types of sounds should be handled:
- Sound Effects: Short audio clips that need to play immediately and often simultaneously, like the bounce of a ball or feedback for picking up a collectable.
- Music: Longer audio pieces that play continuously in the background, like level themes.
MonoGame addresses these different needs through two main classes:
Sound Effects
The SoundEffect class handles short audio clips like:
- Collision sounds.
- Player action feedback (jumping, shooting, etc.).
- UI interactions (button clicks, menu navigation).
- Environmental effects (footsteps, ambient sounds).
The key characteristics of sound effects are:
- Loaded entirely into memory for quick access
- Can play multiple instances simultaneously:
- Mobile platforms can have a maximum of 32 sounds playing simultaneously.
- Desktop platforms have a maximum of 256 sounds playing simultaneously.
- Consoles and other platforms have their own constraints, and you would need to refer to the SDK documentation for that platform.
- Lower latency playback (ideal for immediate feedback)
- Individual volume control per instance.
Music
The Song class handles longer audio pieces like background music. The key characteristics of songs are:
- Streamed from storage rather than loaded into memory.
- Only one song can be played at a time.
- Higher latency, but lower memory usage.
Throughout this chapter, we will use both classes to add audio feedback to our game; sound effects for the bat bouncing and being eaten by the slime, and background music to create atmosphere.
Loading Audio Content
Just like textures, audio content in MonoGame can be loaded through the content pipeline, optimizing the format for your target platform.
Supported Audio Formats
MonoGame supports several audio file formats for both sound effects and music:
.wav
: Uncompressed audio, ideal for short sound effects.mp3
: Compressed audio, better for music and longer sounds.ogg
: Open source compressed format, supported on all platforms.wma
: Windows Media Audio format (not recommended for cross-platform games)
Tip
For sound effects, .wav
files provide the best loading and playback performance since they do not need to be decompressed. For music, .mp3
or .ogg
files are better choices as they reduce file size while maintaining good quality.
Adding Audio Files
Adding audio files can be done through the content pipeline, just like we did for image files, using the MGCB Editor. When you add an audio file to the content project, the MGCB Editor will automatically select the appropriate importer and processor for the audio file based on the file extension.
The processor that are available for audio files file:
- Sound Effects: Processes the audio file as a SoundEffect. This is automatically selected for .wav files.
- Song: Processes the audio file as a Song. This is automatically selected for .mp3, .ogg, and .wma files.
![]() |
![]() |
---|---|
Figure 14-2: MGCB Editor properties panel showing Sound Effect content processor settings for .wav files | Figure 14-3: MGCB Editor properties panel showing Song content processor settings for .mp3 files |
Note
While you typically will not need to change the processor it automatically selects, there may be times where you add files, such as .mp3 files that are meant to be sound effects and not songs. Always double check that the processor selected is for the intended type.
Loading Sound Effects
To load a sound effect, we use ContentManager.Load with the SoundEffect type:
// Loading a SoundEffect using the content pipeline
SoundEffect soundEffect = Content.Load<SoundEffect>("soundEffect");
Loading Music
Loading music is similar, only we specify the Song type instead.
// Loading a Song using the content pipeline
Song song = Content.Load<Song>("song");
Playing Sound Effects
Sound effects are played using the SoundEffect class. This class provides two ways to play sounds:
Direct playback using SoundEffect.Play:
// Loading a SoundEffect using the content pipeline SoundEffect soundEffect = Content.Load<SoundEffect>("soundEffect"); // Play the sound effect with default settings soundEffect.Play();
Creating an instance using SoundEffect.CreateInstance:
// Loading a SoundEffect using the content pipeline SoundEffect soundEffect = Content.Load<SoundEffect>("soundEffect"); // Create an instance we can control SoundEffectInstance soundEffectInstance = soundEffect.CreateInstance(); // Adjust the properties of the instance as needed soundEffectInstance.IsLooped = true; // Make it loop soundEffectInstance.Volume = 0.5f; // Set half volume. // Play the sound effect using the instance. soundEffectInstance.Play();
- Use SoundEffect.Play for simple sound effects that you just want to play once.
- Use SoundEffect.CreateInstance when you need more control over the sound effect, like adjusting volume, looping, or managing multiple instances of the same sound.
SoundEffectInstance contains several properties that can be used to control how the sound effect is played:
Property | Type | Description |
---|---|---|
IsLooped | bool |
Whether the sound should loop when it reaches the end. |
Pan | float |
Stereo panning between -1.0f (full left) and 1.0f (full right). |
Pitch | float |
Pitch adjustment between -1.0f (down one octave) and 1.0f (up one octave). |
State | SoundState | Current playback state (Playing, Paused, or Stopped). |
Volume | float |
Volume level between 0.0f (silent) and 1.0f (full volume). |
Playing Music
Unlike sound effects, music is played through the MediaPlayer class. This static class manages playback of Song instances and provides global control over music playback:
// Loading a Song using the content pipeline
Song song = Content.Load<Song>("song");
// Set whether the song should repeat when finished
MediaPlayer.IsRepeating = true;
// Adjust the volume (0.0f to 1.0f)
MediaPlayer.Volume = 0.5f;
// Check if the media player is already playing, if so, stop it
if(MediaPlayer.State == MediaState.Playing)
{
MediaPlayer.Stop();
}
// Start playing the background music
MediaPlayer.Play(song);
Important
While SoundEffect instances can be played simultaneously, trying to play a new Song while another is playing will stop the current song in the best case, and in the worst case cause a crash on some platforms. In the example above, the state of the media player is checked first before we tell it to play a song. Checking the state first and stopping it manually if it is playing is best practice to prevent potential crashes.
Adding Audio To Our Game
Before we can add audio to our game, we need some sound files to work with. Download the following audio files:
- bounce.wav - For when the bat bounces off screen edges
- collect.wav - For when the slime eats the bat
- theme.ogg - Background music
Note
- bounce.wav is "Retro Impact Punch 07" by Davit Masia (https://kronbits.itch.io/retrosfx).
- collect.wav is "Retro Jump Classic 08" by Davit Masia (https://kronbits.itch.io/retrosfx).
- theme.mp3 is "Exploration" by Luis Zuno (@ansimuz]).
Add these files to your content project using the MGCB Editor:
- Open the Content.mgcb file in the MGCB Editor.
- Create a new directory called
audio
(right-click Content > Add > New Folder). - Right-click the new audio directory and choose Add > Existing Item....
- Navigate to and select the audio files you downloaded.
- For each file that is added, check its properties in the Properties panel:
- For
.wav
files, ensure the Processor is set toSound Effect
. - For
.mp3
files, ensure the Processor is set toSong
.
- For
Next, open the Game1.cs file and update it to the following:
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using MonoGameLibrary;
using MonoGameLibrary.Graphics;
using MonoGameLibrary.Input;
namespace DungeonSlime;
public class Game1 : Core
{
// Defines the slime animated sprite.
private AnimatedSprite _slime;
// Defines the bat animated sprite.
private AnimatedSprite _bat;
// Tracks the position of the slime.
private Vector2 _slimePosition;
// Speed multiplier when moving.
private const float MOVEMENT_SPEED = 5.0f;
// Tracks the position of the bat.
private Vector2 _batPosition;
// Tracks the velocity of the bat.
private Vector2 _batVelocity;
// Defines the tilemap to draw.
private Tilemap _tilemap;
// Defines the bounds of the room that the slime and bat are contained within.
private Rectangle _roomBounds;
// The sound effect to play when the bat bounces off the edge of the screen.
private SoundEffect _bounceSoundEffect;
// The sound effect to play when the slime eats a bat.
private SoundEffect _collectSoundEffect;
public Game1() : base("Dungeon Slime", 1280, 720, false)
{
}
protected override void Initialize()
{
base.Initialize();
Rectangle screenBounds = GraphicsDevice.PresentationParameters.Bounds;
_roomBounds = new Rectangle(
_tilemap.TileSize,
_tilemap.TileSize,
screenBounds.Width - _tilemap.TileSize * 2,
screenBounds.Height - _tilemap.TileSize * 2
);
// Initial slime position will be the center tile of the tile map.
int centerRow = _tilemap.Rows / 2;
int centerColumn = _tilemap.Columns / 2;
_slimePosition = new Vector2(centerColumn, centerRow) * _tilemap.TileSize;
// Initial bat position will the in the top left corner of the room
_batPosition = new Vector2(_roomBounds.Left, _roomBounds.Top);
// Assign the initial random velocity to the bat.
AssignRandomBatVelocity();
}
protected override void LoadContent()
{
// Create the texture atlas from the XML configuration file
TextureAtlas atlas = TextureAtlas.FromFile(Content, "images/atlas-definition.xml");
// Create the slime animated sprite from the atlas.
_slime = atlas.CreateAnimatedSprite("slime-animation");
// Create the bat animated sprite from the atlas.
_bat = atlas.CreateAnimatedSprite("bat-animation");
// Load the tilemap from the XML configuration file.
_tilemap = Tilemap.FromFile(Content, "images/tilemap-definition.xml");
// Load the bounce sound effect
_bounceSoundEffect = Content.Load<SoundEffect>("audio/bounce");
// Load the collect sound effect
_collectSoundEffect = Content.Load<SoundEffect>("audio/collect");
// Load the background theme music
Song theme = Content.Load<Song>("audio/theme");
// Ensure media player isn't already playing on device, if so, stop it
if (MediaPlayer.State == MediaState.Playing)
{
MediaPlayer.Stop();
}
// Play the background theme music.
MediaPlayer.Play(theme);
// Set the theme music to repeat.
MediaPlayer.IsRepeating = true;
base.LoadContent();
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
// Update the slime animated sprite.
_slime.Update(gameTime);
// Update the bat animated sprite.
_bat.Update(gameTime);
// Check for keyboard input and handle it.
CheckKeyboardInput();
// Check for gamepad input and handle it.
CheckGamePadInput();
// Creating a bounding circle for the slime
Circle slimeBounds = new Circle(
(int)(_slimePosition.X + (_slime.Width * 0.5f)),
(int)(_slimePosition.Y + (_slime.Height * 0.5f)),
(int)(_slime.Width * 0.5f)
);
// Use distance based checks to determine if the slime is within the
// bounds of the game screen, and if it's outside that screen edge,
// move it back inside.
if (slimeBounds.Left < _roomBounds.Left)
{
_slimePosition.X = _roomBounds.Left;
}
else if (slimeBounds.Right > _roomBounds.Right)
{
_slimePosition.X = _roomBounds.Right - _slime.Width;
}
if (slimeBounds.Top < _roomBounds.Top)
{
_slimePosition.Y = _roomBounds.Top;
}
else if (slimeBounds.Bottom > _roomBounds.Bottom)
{
_slimePosition.Y = _roomBounds.Bottom - _slime.Height;
}
// Calculate the new position of the bat based on the velocity
Vector2 newBatPosition = _batPosition + _batVelocity;
// Create a bounding circle for the bat
Circle batBounds = new Circle(
(int)(newBatPosition.X + (_bat.Width * 0.5f)),
(int)(newBatPosition.Y + (_bat.Height * 0.5f)),
(int)(_bat.Width * 0.5f)
);
Vector2 normal = Vector2.Zero;
// Use distance based checks to determine if the bat is within the
// bounds of the game screen, and if it's outside that screen edge,
// reflect it about the screen edge normal
if (batBounds.Left < _roomBounds.Left)
{
normal.X = Vector2.UnitX.X;
newBatPosition.X = _roomBounds.Left;
}
else if (batBounds.Right > _roomBounds.Right)
{
normal.X = -Vector2.UnitX.X;
newBatPosition.X = _roomBounds.Right - _bat.Width;
}
if (batBounds.Top < _roomBounds.Top)
{
normal.Y = Vector2.UnitY.Y;
newBatPosition.Y = _roomBounds.Top;
}
else if (batBounds.Bottom > _roomBounds.Bottom)
{
normal.Y = -Vector2.UnitY.Y;
newBatPosition.Y = _roomBounds.Bottom - _bat.Height;
}
// If the normal is anything but Vector2.Zero, this means the bat had
// moved outside the screen edge so we should reflect it about the
// normal.
if (normal != Vector2.Zero)
{
_batVelocity = Vector2.Reflect(_batVelocity, normal);
// Play the bounce sound effect
_bounceSoundEffect.Play();
}
_batPosition = newBatPosition;
if (slimeBounds.Intersects(batBounds))
{
// Choose a random row and column based on the total number of each
int column = Random.Shared.Next(1, _tilemap.Columns - 1);
int row = Random.Shared.Next(1, _tilemap.Rows - 1);
// Change the bat position by setting the x and y values equal to
// the column and row multiplied by the width and height.
_batPosition = new Vector2(column * _bat.Width, row * _bat.Height);
// Assign a new random velocity to the bat
AssignRandomBatVelocity();
// Play the collect sound effect
_collectSoundEffect.Play();
}
base.Update(gameTime);
}
private void AssignRandomBatVelocity()
{
// Generate a random angle
float angle = (float)(Random.Shared.NextDouble() * Math.PI * 2);
// Convert angle to a direction vector
float x = (float)Math.Cos(angle);
float y = (float)Math.Sin(angle);
Vector2 direction = new Vector2(x, y);
// Multiply the direction vector by the movement speed
_batVelocity = direction * MOVEMENT_SPEED;
}
private void CheckKeyboardInput()
{
// If the space key is held down, the movement speed increases by 1.5
float speed = MOVEMENT_SPEED;
if (Input.Keyboard.IsKeyDown(Keys.Space))
{
speed *= 1.5f;
}
// If the W or Up keys are down, move the slime up on the screen.
if (Input.Keyboard.IsKeyDown(Keys.W) || Input.Keyboard.IsKeyDown(Keys.Up))
{
_slimePosition.Y -= speed;
}
// if the S or Down keys are down, move the slime down on the screen.
if (Input.Keyboard.IsKeyDown(Keys.S) || Input.Keyboard.IsKeyDown(Keys.Down))
{
_slimePosition.Y += speed;
}
// If the A or Left keys are down, move the slime left on the screen.
if (Input.Keyboard.IsKeyDown(Keys.A) || Input.Keyboard.IsKeyDown(Keys.Left))
{
_slimePosition.X -= speed;
}
// If the D or Right keys are down, move the slime right on the screen.
if (Input.Keyboard.IsKeyDown(Keys.D) || Input.Keyboard.IsKeyDown(Keys.Right))
{
_slimePosition.X += speed;
}
}
private void CheckGamePadInput()
{
GamePadInfo gamePadOne = Input.GamePads[(int)PlayerIndex.One];
// If the A button is held down, the movement speed increases by 1.5
// and the gamepad vibrates as feedback to the player.
float speed = MOVEMENT_SPEED;
if (gamePadOne.IsButtonDown(Buttons.A))
{
speed *= 1.5f;
GamePad.SetVibration(PlayerIndex.One, 1.0f, 1.0f);
}
else
{
GamePad.SetVibration(PlayerIndex.One, 0.0f, 0.0f);
}
// Check thumbstick first since it has priority over which gamepad input
// is movement. It has priority since the thumbstick values provide a
// more granular analog value that can be used for movement.
if (gamePadOne.LeftThumbStick != Vector2.Zero)
{
_slimePosition.X += gamePadOne.LeftThumbStick.X * speed;
_slimePosition.Y -= gamePadOne.LeftThumbStick.Y * speed;
}
else
{
// If DPadUp is down, move the slime up on the screen.
if (gamePadOne.IsButtonDown(Buttons.DPadUp))
{
_slimePosition.Y -= speed;
}
// If DPadDown is down, move the slime down on the screen.
if (gamePadOne.IsButtonDown(Buttons.DPadDown))
{
_slimePosition.Y += speed;
}
// If DPapLeft is down, move the slime left on the screen.
if (gamePadOne.IsButtonDown(Buttons.DPadLeft))
{
_slimePosition.X -= speed;
}
// If DPadRight is down, move the slime right on the screen.
if (gamePadOne.IsButtonDown(Buttons.DPadRight))
{
_slimePosition.X += speed;
}
}
}
protected override void Draw(GameTime gameTime)
{
// Clear the back buffer.
GraphicsDevice.Clear(Color.CornflowerBlue);
// Begin the sprite batch to prepare for rendering.
SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
// Draw the tilemap
_tilemap.Draw(SpriteBatch);
// Draw the slime sprite.
_slime.Draw(SpriteBatch, _slimePosition);
// Draw the bat sprite.
_bat.Draw(SpriteBatch, _batPosition);
// Always end the sprite batch when finished.
SpriteBatch.End();
base.Draw(gameTime);
}
}
The key changes here are:
- Added the
using Microsoft.Xna.Framework.Audio;
andusing Microsoft.Xna.Framework.Media;
directories to access the Song and SoundEffect classes. - Added the
_boundSoundEffect
and_collectSoundEffect
fields to store those sound effects when loaded and use them for playback. - In LoadContent
- The bounce and collect sound effects are loaded using the content manager.
- The background theme music is loaded using the content manager.
- The background music is played using the media player, checking its state first.
- The MediaPlayer.IsRepeating is set to
true
so the background music loops.
- In Update:
- The bounce sound effect is played when the bat bounces off the edge of the screen.
- The collect sound effect is played when the slime eats the bat.
Running the game now, the theme music plays in the background, you can hear the bat bounce off the edge of the screen, and if you move the slime to eat the bat, you hear that as well.
Figure 14-4: Gameplay with audio. |
Conclusion
Let's review what you accomplished in this chapter:
- Learned about MonoGame's audio system including sound effects and music.
- Explored the key differences between:
- Sound effects (short, multiple simultaneous playback).
- Music (longer, streamed, single playback).
- Added audio content to your game project through the content pipeline.
- Loaded audio files using the ContentManager.
- Implemented audio feedback in your game:
- Background music to set atmosphere.
- Sound effects for bat bouncing and collection events.
- Learned best practices for handling audio playback across different platforms.
In the next chapter, we'll explore additional ways to manage audio by creating an audio controller module that will help with common tasks such as volume control, muting, and state management.
Test Your Knowledge
What are the two main classes MonoGame provides for audio playback and how do they differ?
MonoGame provides:
- SoundEffect for short audio clips (loaded entirely into memory, multiple can play at once) and
- Song for longer audio like music (streamed from storage, only one can play at a time).
Why is it important to check if MediaPlayer is already playing before starting a new song?
Checking if MediaPlayer is already playing and stopping it if necessary helps prevent crashes on some platforms. Since only one song can play at a time, properly stopping the current song before starting a new one ensures reliable behavior across different platforms.
What file formats are best suited for sound effects and music, respectively, and why?
For sound effects, .wav files are generally best because they're uncompressed and load quickly into memory for immediate playback. For music, compressed formats like .mp3 or .ogg are better suited because they greatly reduce file size while maintaining good audio quality, which is important for longer audio that's streamed rather than fully loaded.
What's the difference between using SoundEffect.Play directly and creating a SoundEffectInstance?
- SoundEffect.Play is simpler but provides limited control - it plays the sound once with basic volume/pitch/pan settings.
- Creating a SoundEffectInstance gives more control including the ability to pause, resume, loop, and change properties during playback, as well as track the sound's state.
How many sound effects can play simultaneously on different platforms?
The number of simultaneous sound effects varies by platform:
- Mobile platforms: maximum of 32 sounds.
- Desktop platforms: maximum of 256 sounds.
- Consoles and other platforms have their own constraints specified in their respective SDK documentation.