Monday, November 03, 2008

XNA & EXTENSION METHODS 1

With the release of XNA GS 3, two new tutorials were included into the "What's New In This Release" section in the help file: "TopDownShooter" and "FuelCell" games. For what follows, we'll be referring to the first tutorial game: TopDownShooter.

Sometimes we need to get an instance of a sound effect without playing it. The good news is that it can be done. The bad news is that there's no built-in method provided yet to get that instance in one step.

So, what's next then? Open the AudioManager.cs file provided in the TopDownShooter example. You should find the following code on the LoadContent() method:

// We call Play silently to retrieve a specific instance we can
// use for subsequent Pause and Play calls
SeekerInstance = Seeker.Play(0, 0.75f, 0, true);
SeekerInstance.Stop();
SeekerInstance.Volume = FXVolume; // set the real volume

As you can see, the above-mentioned code gives an idea of what you should use to get the instance of a sound effect "without playing it".

But, what if we want to get that functionality in one method (as if it were provided built-in) without extending the SoundEffect class? Well, in that case since we are targeting the .NET Framework 3.5 we can use the magic of "Extension Methods".

I'm not going to explain what Extension Methods are (to get a first read on the subject, please refer to this page), but how we can take advantage of them for this particular case.

Our goal is to extend the SoundEffect class in a way that:

  1. We do not create a specification of that class,
  2. We get a new SoundEffectInstance without playing it,
  3. We do not generate unnecessary garbage when getting a new instance, and
  4. Our method runs efficiently in the .NET Compact Framework.

Thus, our solution should be the following:

  1. We'll use Extension Methods technique,
  2. We'll mimic the code of the "TopDownShooter" tutorial,
  3. We cannot declare a local reference of type SoundEffectInstance within our method, and
  4. We should pass a parameter by reference of type SoundEffectInstance that can be null.

Therefore, our final implementation should be something like this:

using System;
using Microsoft.Xna.Framework.Audio;

namespace MyNamespace.Audio
{
public static class AudioExtensions
{
private static float VolumeLevelByDefault = .5f;

public static void GetCue(this SoundEffect soundEffect, out SoundEffectInstance soundEffectInstance)
{
GetCue(soundEffect, out soundEffectInstance, VolumeLevelByDefault, 0f, 0f, false);
}

public static void GetCue(this SoundEffect soundEffect, out SoundEffectInstance soundEffectInstance, float volume, bool loop)
{
GetCue(soundEffect, out soundEffectInstance, volume, 0f, 0f, loop);
}

public static void GetCue(this SoundEffect soundEffect, out SoundEffectInstance soundEffectInstance, float volume, float pitch, float pan, bool loop)

{
// Play the sound silently and stop it immediately.
soundEffectInstance = soundEffect.Play(0f, pitch, pan, loop);
soundEffectInstance.Stop();

// Set the passed volume level.
soundEffectInstance.Volume = volume;
}
}
}

As you can see, I'm naming the static method "GetCue" but you can call it, say, "GetInstance" if you prefer. Also, since we cannot yet use optional parameters -we'll have to wait for C# 4.0 for that- you will have to create as many overloaded methods as you need.

Now, in the LoadContent() method of the AudioManager.cs file, all we should use instead is:

// Get the new instance of the seeker sound effect.
Seeker.GetCue(out SeekerInstance, FXVolume, 0.75f, 0, true);

Don't forget to add the proper "Using" statement on the AudioManager.cs, linking to the namespace where the extension method was declared and implemented (in my example "MyNamespace.Audio"), or your code won't compile.

Also, you will need to manually add a reference in your project to the "System.Core" assembly -in case it is not already referenced.


Well, this is it. Now you know how to extend the SoundEffect class to get a new instance of a sound effect in just one method call, virtually speaking ;)

One final note: by using "Extension Methods" we are not breaking any design principles, since we have no direct access to private members of the "extended" type (being the latter, in this example, the SoundEffect class).

Enjoy!
~Pete


3 comments:

Any thoughts? Post them here ...