Page 1 of 1

XNA Screen Manager Part 2A Different background music for different screens

#1 SixOfEleven  Icon User is offline

  • using Caffeine;
  • member icon

Reputation: 942
  • View blog
  • Posts: 6,342
  • Joined: 18-October 08

Posted 26 June 2010 - 06:34 AM

This tutorial is about how to add music to your game using my XNA Screen Manager tutorials. To get started you will want to open your project from the previous tutorials I wrote on creating an XNA screen manager. If you haven't completed those tutorial go and read them first and come back when you are done. You can find the first one at this link and the second one at this link.

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


Is This A Good Question/Topic? 3
  • +

Replies To: XNA Screen Manager Part 2A

#2 eZACKe  Icon User is offline

  • Garbage Collector

Reputation: 120
  • View blog
  • Posts: 1,278
  • Joined: 01-June 09

Posted 26 June 2010 - 04:53 PM

I'm just having a bit of trouble with this line:
 public static event EventHandler<MusicChangeEventArgs> OnMusicChange;



It is giving me an error that says:
"Inconsistent accessibility: field type 'System.EventHandler<fullGame1.MusicChangeEventArgs>' is less accessible than field 'fullGame1.GameScreen.OnMusicCahnge'


Not sure why I'm getting this.

This post has been edited by eZACKe: 26 June 2010 - 05:01 PM

Was This Post Helpful? 0
  • +
  • -

#3 SixOfEleven  Icon User is offline

  • using Caffeine;
  • member icon

Reputation: 942
  • View blog
  • Posts: 6,342
  • Joined: 18-October 08

Posted 26 June 2010 - 05:13 PM

With out seeing the entire code for your program it is hard to tell. However, it looks like one of your classes isn't public. Make sure that the MusicChangeEventArgs class is public. If it is, it is one of your other classes that isn't public. You could try making all classes public and see if that solves the problem.
Was This Post Helpful? 1
  • +
  • -

#4 eZACKe  Icon User is offline

  • Garbage Collector

Reputation: 120
  • View blog
  • Posts: 1,278
  • Joined: 01-June 09

Posted 26 June 2010 - 05:27 PM

Thanks! It works! I finally have background music!

Though now it lead to another problem. When I'm on my ActionScreen and enter my pop up screen, and hit either yes for quit or no for keep playing, the game freezes.

Then I look at my code and this line has an error:
musicInstance.Play();



It says ObjectDisposedException was unhandled. How do I handle it?


Edit: If you answer this, feel free to answer it in my thread in the XNA forum. I wouldn't want to hijack a good tutorial!

This post has been edited by eZACKe: 26 June 2010 - 05:30 PM

Was This Post Helpful? 0
  • +
  • -

#5 SixOfEleven  Icon User is offline

  • using Caffeine;
  • member icon

Reputation: 942
  • View blog
  • Posts: 6,342
  • Joined: 18-October 08

Posted 26 June 2010 - 05:43 PM

Probablye better to answer it here. That way anybody following the tutorials will get the answer as well, with out having to search for it. Let me see if I can recreate the error and fix it.
Was This Post Helpful? 0
  • +
  • -

#6 SixOfEleven  Icon User is offline

  • using Caffeine;
  • member icon

Reputation: 942
  • View blog
  • Posts: 6,342
  • Joined: 18-October 08

Posted 26 June 2010 - 06:16 PM

Updated tutorial. Basically what I had to do is instead of a dictionary of SoundEffectInstance I created a dictionary of SoundEffect. When I disposed the instance of SoundEffectInstance in that method I was also disposing the SoundEffectInstance in the dictionary because it is a reference type. Now in the method where the music is changed I create the SoundEffectInstance, set it to loop, and then play it.


Updated Tutorial

This tutorial is about how to add music to your game using my XNA Screen Manager tutorials. To get started you will want to open your project from the previous tutorials I wrote on creating an XNA screen manager. If you haven't completed those tutorial go and read them first and come back when you are done. You can find the first one at this link and the second one at this link.

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.

I decided to load all of the songs for the project into a Dictionary<string, SoundEffect> his 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, SoundEffect> 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, SoundEffect>();
    SoundEffect soundEffect = Content.Load<SoundEffect>("iron-man");
    music.Add("music1", soundEffect);

    soundEffect = Content.Load<SoundEffect>("midnight-ride");
    music.Add("music2", soundEffect);

    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 add the music to the dictionary using the name music1 and the SoundEffect I loaded. You will want to use more descriptive names in your games. I follow the same process for the next song. Load it 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].CreateInstance();
        musicInstance.IsLooped = true;
        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 then create a SoundEffectInstance using the CreateInstance method of the SoundEffect class. I set the IsLooped property to true so that the music will loop. 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.
Was This Post Helpful? 2
  • +
  • -

#7 eZACKe  Icon User is offline

  • Garbage Collector

Reputation: 120
  • View blog
  • Posts: 1,278
  • Joined: 01-June 09

Posted 26 June 2010 - 06:29 PM

Perfect! Thank you so much for this! :bigsmile:
Was This Post Helpful? 0
  • +
  • -

#8 Stuart444  Icon User is offline

  • D.I.C Head

Reputation: 6
  • View blog
  • Posts: 55
  • Joined: 23-March 07

Posted 29 June 2010 - 04:27 PM

I've seen your screen manager tutorials before but haven't actually tried them out and now I see you have another one up here for music :)

I'll be going through the first two Screen Manager tutorials probably in the next day or two when I get time so it's nice to see that there is new ones for me to follow afterwards.

Thanks for these
Was This Post Helpful? 0
  • +
  • -

#9 Leeb65  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 20-May 11

Posted 20 May 2011 - 12:20 PM

The above does not work in XNA 4 but a slight modification to the code and it works perfectly:

 void GameScreen_OnMusicChange(object sender, MusicChangeEventArgs e)
        {
            if (music.ContainsKey(e.MusicName))
            {
                if (musicInstance != null)
                {
                    musicInstance.Stop();
                }

                musicInstance = music[e.MusicName];
                //musicInstance.IsLooped = true;
                musicInstance.Play();
            }
        }


But it's a great post, I have followed all the tutorials so far and I have the basic framework for a game with 3 different menu screens, although I made some small mods to the code to make it work the way I want it.

Thanks.
Was This Post Helpful? 0
  • +
  • -

#10 jamesjiao  Icon User is offline

  • New D.I.C Head

Reputation: 1
  • View blog
  • Posts: 2
  • Joined: 18-December 11

Posted 19 December 2011 - 04:13 PM

View PostLeeb65, on 20 May 2011 - 12:20 PM, said:

The above does not work in XNA 4 but a slight modification to the code and it works perfectly:

 void GameScreen_OnMusicChange(object sender, MusicChangeEventArgs e)
        {
            if (music.ContainsKey(e.MusicName))
            {
                if (musicInstance != null)
                {
                    musicInstance.Stop();
                }

                musicInstance = music[e.MusicName];
                //musicInstance.IsLooped = true;
                musicInstance.Play();
            }
        }


But it's a great post, I have followed all the tutorials so far and I have the basic framework for a game with 3 different menu screens, although I made some small mods to the code to make it work the way I want it.

Thanks.


Thanks for the heads-up. Just wondering why you needed to comment out the islooped line? What sort of error were you getting? Although isLooped shows up in MSDN as a virtual property, it can still be called because the SoundEffect.CreateInstance() method most likely creates an instance of a class derived from SoundEffectInstance.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1