Wednesday, June 8, 2011

XNA Game Design: ContentManager and Audio

Unlike the SpriteBatch, the ContentManager is actually a lot easier to spawn instances of, and just requires a variable we can make a static in our game1 class. In our Game1 properties, add the following;

public static GameServiceContainer services;
and in the constructor, initialize it with

services = Services;
What is a GameServiceContainer? It seems like one of those generally random variables we seem to be needing here and there (and it is). A ContentManager needs a particular service that can import and read the files it's loading.*  GameServiceContainer objects simply contain a collection of all Game.Services so that we can pass it in lieu of picking out each service from each service provider and passing that. It's like passing a box of different cookies to children in a classroom instead of asking each child what they want, running to the factory and getting it for them. Nifty huh?


Our game will have a significant number of services, most if not all of whom are default initialized in the Microsoft.Xna.Framework.Game class. These are contained in the private Services object, which is not static and we would like public static access to. With this done, from here on out we can make ContentManagers anywhere using Game1.services .
*(Contentmanagers really only need a service for loading texture files, so this is the equivalent of giving a lumberjack an ax to trim a bonsai tree. He just needs the ax in case he's suddenly asked to chop a tree, which shouldn't happen. God these metaphors are getting weird...)


Let's create a class to play audio now! This audio playing class will contain methods designed to do the following:
  • In a single line of code, take in a String that will correspond to an asset name in our "Content/Audio" folder, and play that sound at default settings (full volume, normal pitch, no pan).
  • Allow us to change the volume, pitch or pan of the sound in a single line of code.
  • Allow us to pause the sound in a single line of code.
  • Allow us to resume the sound in a single line of code.

static class Audio
{
    public static SoundEffect s_bgm;
    public static SoundEffectInstance BGM;
    //Our Audio ContentManager will be called Player
    public static ContentManager Player = 
    new ContentManager(Game1.services, "Content/Audio");
}

We see our ContentManager is initialized, but hold on a second; there's a class... called SoundEffectInstance? What's the difference between a SoundEffect and a SoundEffectInstance? Is an instance of a SoundEffect a SoundEffectInstance? What is this I don't even-


Chillax. A SoundEffect is the direct output from the ContentManager. SoundEffect objects contain mostly metadata and no methods actually related to playing the sound. This metadata includes length, channels of audio, kbps, and other things related to sound we wouldn't want to touch with a ten foot pole. SoundEffectInstance, on the other hand, contains all our fun methods for playing, pausing, resuming, looping, paning, pitching and general tinkering with our sound. SoundEffect has a method called CreateInstance() which, as you can imagine, returns a SoundEffectInstance object. With our content manager, we can load the SoundEffect, and then use CreateInstance() to create our playable SoundEffectInstance. It's just a two step process.


So, here is our method to take in the string and play the audio:



public static void bgm_play(string name)
{
    if(BGM != null) BGM.Stop(); 
    //Without this the current sound would not stop playing
    s_bgm = Player.Load<SoundEffect>(@"BGM\" + name);
    //The @ before the string indicates that escape codes 
    //should be ignored. It should be noted that both / and \
    //would be read as a sign to look into that folder. 
    //Therefore, ("BGM/" + name) would work just as well.
    BGM = s_bgm.CreateInstance();
    BGM.IsLooped = true;
    BGM.Play();
}

That's all there is to it. From here on out, we just have to say Audio.BGM.Pause();,  Audio.BGM.Stop()Audio.BGM.Volume = x, etc. just to change things. Everything from one line of code- that's how to design a game.


Still, there is a slight problem; with this design, we only get one channel to play sound on. I named our SoundEffect s_bgm and our SoundEffectInstance BGM. BGM is designed to be background music that will loop upon completion (notice the BGM.IsLooped = true;). But because this is our only channel of audio, we would not be able to play other noises, such as spoken voices, running water, explosions off in the distance and so forth. As such, this is the homework:


Homework: In your Audio folder, create four new subfolders named BGM, BGS, SE, and ME. Initialize and create the variables for these four audio channels (BGM is already there), and create methods so that following occurs:
  • Play_BGM() is looped (Background Music)
  • Play_BGS() is looped and will play at 80% volume. (Background Sound)
  • Play_SE() is not looped, will play at 80% volume (Sound Effect)
  • Play_ME() is not looped, and when called will pause BGM and BGS. (Music Effect)
  • Each method will look for it's audio file in the respective audio subfolder 

No comments:

Post a Comment