Unlike other screen manager tutorials, this tutorial requires XNA 3.1. XNA 3.0 is not supported in this tutorial. I will more than likely write another tutorial for those who want to use XNA 3.0 instead of XNA 3.1 at a future date.
Quite often, you will want different music in different parts of your game. In this tutorial, I will show you how you can change the music in your game when your game state changes. There will be music for the main menu and for the action screen. You are not limited to just these though. You could also change the music in the middle of the action screen if something happened to warrant that.
I will not be using XACT but you will need two WAV files for the music. You can download the two I used below. The way I am going to do this is using the SoundEffect and SoundEffectInstance classes. I will also be using events. I will make an event in the GameScreen class. This event can be subscribed to in the Game1 class, or another class if you decided to have a class dedicated completely to audio. I decided to go with the Game1 class.
Warning: This is a 7MB download. You may be better to find two MP3s and convert them to WAV yourself.
Wave Files
I decided to load all of the songs for the project into a Dictionary<string, SoundEffectInstance>. This way if you want to change the music you just need a string to retrieve the music from the dictionary. You will see why in a moment. You will also want a field to hold the current instance being played. In the Game1 class, with the SpriteBatch field, add the following fields.
Dictionary<string, SoundEffectInstance> music; SoundEffectInstance musicInstance;
Now that you have the fields it is time to load in the music. You first will have to add in the music. Go a head and right click the Content folder, select Add Existing item, and add the iron-man.wav and midnight-ride.wav files. If you use different music, make sure to change the names when you load them in. The music is from SoundJay. They offer a variety of free sounds and music. I changed them from MP3 to WAV. Change the LoadContent method to the following. I will explain it after you have read it.
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
music = new Dictionary<string, SoundEffectInstance>();
SoundEffect soundEffect = Content.Load<SoundEffect>("iron-man");
SoundEffectInstance instance = soundEffect.CreateInstance();
instance.IsLooped = true;
music.Add("music1", instance);
soundEffect = Content.Load<SoundEffect>("midnight-ride");
instance = soundEffect.CreateInstance();
instance.IsLooped = true;
music.Add("music2", instance);
startScreen = new StartScreen(
this,
spriteBatch,
Content.Load<SpriteFont>("menufont"),
Content.Load<Texture2D>("alienmetal"));
Components.Add(startScreen);
startScreen.Hide();
actionScreen = new ActionScreen(
this,
spriteBatch,
Content.Load<Texture2D>("greenmetal"));
Components.Add(actionScreen);
actionScreen.Hide();
activeScreen = startScreen;
activeScreen.Show();
}
So, what is going on here is I first create the Dictionary<string, SoundEffectInstance> to hold the music. Next I load in the iron-man music using the content pipeline as a SoundEffect. I then use the CreateInstance method of the SoundEffect class to create a SoundEffectInstance. I then set the IsLooped to true. This means that when the end of the music is reached it will loop back to the start. I then add the music to the dictionary using the name music1 and the SoundEffectInstance I created. You will want to use more descriptive names in your games. I follow the same process for the next song. Load it, create an instance of the SoundEffectInstance class, set it to repeat, and add it to the dictionary. This time I call it music2.
Now that you have the music you need to get it playing. The first thing to do is to create an event in the GameScreen class. I will be creating an event that conforms to the .NET standard, EventName(object sender, EventArgs e). I will be use a class that inherits from EventArgs called MusicChangeEventArgs. This class will take a parameter that will be the name of the music you want to change to as a string. Right click your game project in the solution explorer, select Add, and then class. Name the class MusicChangeEventArgs. This is the code for that class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ScreenManager
{
public class MusicChangeEventArgs : EventArgs
{
string musicName;
public string MusicName
{
get { return musicName; }
private set { musicName = value; }
}
public MusicChangeEventArgs(string musicName)
{
MusicName = musicName;
}
}
}
Short and sweet and probably a little over kill. I could have made the field public but I try and follow good object-oriented programming concepts when I program. The field, musicName, will hold the string that maps to the music to be played. The property, MusicName, is a get and set property. The get part is public but the set part is private and is only usable in the class. Just a quirk I've developed when it comes to properties. The constructor takes a string parameter that will be the string for the music to be played. It then sets the field using the property.
Now that we have our event arguments class we can create our event. Add the following code to the GameScreen class, just below the SpriteBatch field.
public static event EventHandler<MusicChangeEventArgs> OnMusicChange;
What this does is create a static event that can be subscribed to. Because it is static it is shared by all objects that of type GameScreen, or classes that inherit from GameScreen. This was the best option because you have a single event that any class that inherits from GameScreen can raise, not directly though. You can call a method in the GameScreen class that will actually raise the method if it is subscribed to.
Now you need to be able to raise this method from other game screens. The best way I found to do it was to create a protected method inside the GameScreen class that takes as a parameter the string for the music to be played from the dictionary. Like I said, you will probably want to use more descriptive names than music1 and music2 for your games. You can add the following method to the GameScreen class, below the Show and Hide methods.
protected void ChangeMusic(string musicName)
{
if (OnMusicChange != null)
{
OnMusicChange(this, new MusicChangeEventArgs(musicName));
}
}
Events should only be fired if they are subscribed to. If they are not subscribed to they will be null. So, I check to make sure the event is not null. If it is, I fire the event using this for the sender and create an instance of MusicChangeEventArgs with the name of the music to be played.
Almost to the finish line. The event needs to be raised in the StartScreen and ActionScreen classes. I decided to change the music when the Show method is called. The code is identical except for the string for the music to be played. If you recall, the Show method is virtual. That means that you can override it in any class that inherits from GameScreen. So, when you call the Show method to set the screen to active, you can raise the event there. The code for the StartScreen is first and the ActionScreen second.
public override void Show()
{
base.Show();
ChangeMusic("music1");
}
public override void Show()
{
base.Show();
ChangeMusic("music1");
}
The only thing that remains is to subscribe to the event and process it in the Game1 class. You subscribe to the event like you do in a Windows Forms application. You create a method using the signature of your event handler and assign it to the event. Change the constructor of the Game1 class to the following.
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
GameScreen.OnMusicChange +=
new EventHandler<MusicChangeEventArgs>(GameScreen_OnMusicChange);
}
The Game1 class is now subscribed to the event. Now you need to handle the event in the method GameScreen_OnMusicChange method. I will show the code and then explain.
void GameScreen_OnMusicChange(object sender, MusicChangeEventArgs e)
{
if (music.ContainsKey(e.MusicName))
{
if (musicInstance != null)
musicInstance.Dispose();
musicInstance = music[e.MusicName];
musicInstance.Play();
}
}
What happens is I check to see if the string for the name of the music is in the dictionary using the ContainsKey method. Not necessary, but will keep your game from throwing an exception if the music isn't found. I next check to see if the musicInstance is not null and if it is true I call the Dispose method do dispose the object. You do this because if you try and assign another SoundEffectInstance to it the will both play at the same time. You should dispose the instance and assign the new one. I assign the SoundEffectInstance referenced to the musicInstance field. I then call the Play method to actually play the music.
That, in a nutshell, is a simple way to have your music change in your XNA games. I may write up a version using XACT as well as one that uses XNA 3.0.
Good luck in your game programming adventures!
Edit:
Forgot a set of code tags.
This post has been edited by SixOfEleven: 26 June 2010 - 09:11 AM





MultiQuote






|