Monday, November 03, 2008

XNA & EXTENSION METHODS 2

Continuing with the topic of how to use the handy functionality provided by "Extension Methods" with our XNA-based creations, for this article we are going to need the "FuelCell" tutorial (remember that you shall find this tuto -along with the "TopDownShooter" one- in the help file under the "What's New In This Release" section).

Ok. In the chapter called "Finishing Touches" of the FuelCell' s tutorial, you'll find the following method at the very end of the "FuelCellGame.cs" file:

private Rectangle GetTitleSafeArea(Viewport viewport)
{
Rectangle retval = new Rectangle(viewport.X, viewport.Y, viewport.Width, viewport.Height);

#if XBOX
retval = viewport.TitleSafeArea;
#endif

return retval;
}

If you remember my last article on the subject, you'll be anticipating by now that the Viewport struct could be "extended" so as to retrieve its "unsafe" area with one method call.

If so, you guessed it right! We can use Extension Methods to declare an operation called, say "GetFullArea", and give it the proper implementation.

Not only can we pass again a parameter by reference (using the "out" reserved word) -like we did in Part 1 of this series- but also in this particular case, since we're dealing with a struct, we can afford to implement an overloaded method that locally declares, creates and returns an instance of a Rectangle (I know what you're thinking, but unfortunately, there are no "Extension Properties" in C#, so we need to get the rectangle from a method call).

Using Extension Methods, our solution should look like the following:

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace MyNamespace.Math
{
public static class MathExtensions
{
public static void GetFullArea(this Viewport viewport, out Rectangle area)
{
area = new Rectangle(viewport.X, viewport.Y, viewport.Width, viewport.Height);
}

public static Rectangle GetFullArea(this Viewport viewport)
{
return new Rectangle(viewport.X, viewport.Y, viewport.Width, viewport.Height);
}
}
}


Now, the GetTitleSafeArea(...) method could be re-implemented like this:

private Rectangle GetTitleSafeArea(Viewport viewport)
{
Rectangle retval;

#if
XBOX
retval = viewport.TitleSafeArea;
#else
viewport.GetFullArea(out retval);
#endif

return retval;
}

Or also like this:

private Rectangle GetTitleSafeArea(Viewport viewport)
{
#if XBOX
return viewport.TitleSafeArea;
#else
return viewport.GetFullArea();
#endif
}


Remember two important things:

  • You'll have to add the proper "Using" statement in the file that will use the extension method (in my example: "MyNamespace.Math"), and
  • You'll have to manually add a reference to the "System.Core" assembly in your project.

This technique can be used to get, say -among other things, a Vector2 containing the width and height of the Viewport (as well as for the display and the client area). I leave it as an exercise for you to try (you'll see it's actually quite easy to implement).

I hope you find this article useful.

'till next time,
~Pete

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