jueves, 12 de febrero de 2009

XNA Y METODOS DE EXTENSION - PARTE 1

El artículo original fue escrito el 03-Nov-08.

Con la liberación de XNA GS 3, dos nuevos tutoriales fueron incluidos en la sección "What's New In This Release" en el archivo de ayuda: los juegos "TopDownShooter" y "FuelCell". En lo que sigue, nos vamos a referir al primer juego de ejemplo: TopDownShooter.

A veces precisamos obtener una instancia de un efecto de sonido sin ejecutarlo. La buena noticia es que puede hacerse. La mala noticia es que no se ha provisto ningún método por defecto para obtener dicha instancia en un sólo paso.

Y bien, que sigue entonces? Abre el archivo AudioManager.cs del ejemplo TopDownShooter. Encontrarás el siguiente código en el método LoadContent():

// Podemos ejecutar silenciosamente un sonido para obtener
// una instancia específica para luego utilizar con llamadas
// "
Pause" y "Play"
SeekerInstance = Seeker.Play(0, 0.75f, 0, true); SeekerInstance.Stop();
SeekerInstance.Volume = FXVolume; // restablece el volúmen

Como puedes ver, el código anterior da una idea de qué deberías usar para obtener una instancia de un efecto de sonido "sin ejecutarlo".

Pero, que si quierer obtener dicha funcionalidad en un sólo método (como si fuera provisto por defecto) sin extender la clase "SoundEffect"? Bueno, en ese caso dado que apuntamos al .NET Framework 3.5 podemos usar la magia de los métodos de extensión ("Extension Methods").

No voy a explicar que son los llamados métodos de extensión (para hacer una primera lectura del tema, por favor visita esta página), sino cómo poder beneficiarnos de los mismos para este caso en particular.

Nuestra metar es extender la clase "SoundEffect" de forma tal que:

  1. No creamos una especificación de la clase,
  2. Obtengamos una instancia de un efecto de sonido ("SoundEffectInstance") sin que lo ejecutemos o escuchemos,
  3. No generemos resíduos innecesarios al obtener dicha instancia, y
  4. Nuestro método se ejecute efficientemente en el "Compact Framework" de .NET.

Por ello, nuestra solución debería ser la siguiente:

  1. Usaremos la técnica métodos de extensión,
  2. Imitarémos el código de nuestro juego de ejemplo "TopDownShooter",
  3. No podrémos declarar una referncia local del tipo "SoundEffectInstance" dentro de nuestro método, y
  4. Deberíamos pasar un parámetro por referencia del tipo "SoundEffectInstance" que no sea nulo.

Por lo tanto, nuestra implementación final debería lucir algo así:

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)
{
// Ejecuta el sonido silenciosamente y
               // deténlo inmediatamente.
soundEffectInstance = soundEffect.Play(0f,
                   pitch, pan, loop);
soundEffectInstance.Stop();
// Configura el nivel de volúmen indicado. soundEffectInstance.Volume = volume;
}
}
}

Como puedes ver, estoy nombrando el método estático "GetCue" pero puedes llamarlo si es que lo prefieres, digamos, "GetInstance". También, puesto que no podemos usar aún parámetro opcionales -tendrémos que esperar a C# 4.0 para eso- tendrás que crear tantas sobrecargas como precises.

Ahora bien, in el método "LoadContent()" del archivo "AudioManager.cs", todo lo que debemos usar es:

// Obtiene la instancia del efecto de sonido "Seeker". Seeker.GetCue(out SeekerInstance, FXVolume, 0.75f, 0,
    true);

No olvides agregar la sentencia "Using" apropiada en AudioManager.cs, la cual refiera al espacio de nombres ("namespace") donde el método de extensión fue declarado e implementado (en mi ejemplo de arriba es "MyNamespace.Audio"), o tu código no se compilará.

Además, deberás agregar manualmente en tu proyecto la referencia al ensamblado de 3.5 llamado "System.Core" -en caso que no esté ya referenciado.

Bien, ya está. Ahora sabes cómo extender la clase SoundEffect a fin de obtener una nueva instancia de un efecto de sonido en tan sólo una única llamada a un método, virtualmente hablando ;)

Una nota final: al usar "Extension Methods" no estamos rompiendo ningún principio de diseño, puesto a que no hay acceso directo a miembros privados del tipo "extendido" (siendo esto último, en el ejemplo, la clase "SoundEffect").

Que lo disfutes!
~Pete