miércoles, 25 de febrero de 2009

DESVELADO EN SEATTLE

Estoy muy entusiasmado: estaré asisitiendo a la cumbre de MVPs por primera vez. Es grandioso econtrarse con ambos, otros MVPs y los miembros del equipo de XNA.

Nuevamente, como hiciera el año pasado luego del Gamefest, visitaré a mi hermana y cuñado en Vancouver por unos días luego de la cumbre. Así que no esperen artículos técnicos hasta mi regreso :)

Nos vemos,
~Pete

-> Vínculo a la versión en inglés.

viernes, 20 de febrero de 2009

XBLCG: LA CONEXION DEL PANEL DE CONTROL

Si sos nuvo en el mundo de los Juegos de la Comunidad de la XBox 360, probablemente no sepas cómo y/o dónde encontrarlos.

Kathleen Sanders -XNA Community Manager-también conocida como "Cookiecups" ha subido un video explicándo exactamente eso.

Luego de mirar el video no vas a tener excusas para jugar y comprar juegos de la comunidad.

Que lo disfrutes,
~Pete

-> Vínculo a la versión en inglés.

lunes, 16 de febrero de 2009

TAREA FINALIZADA!

He completado la tarea de traducir una selección de artículos del archivo al español.

Por ende, de ahora en más, todo vuelve a su estado normal aquí, así que pueden adivinar que aparecerán nuevos artículos a la brevedad.

No cambien de canal!
~Pete

-> Vínculo a la versión en inglés.

PREMIO MVP PARA XNA/DIRECTX

El artículo original fue escrito el 03-Ene-09.

Chequeando mi bandeja de entrada, quedé extremadamente felíz al encontrar un mensaje de Microsoft notificándome que se me había otorgado el reconocimiento "Most Valuable Professional" para las tecnologías XNA/DirectX.

Es realmente motivador saber que todas mis contribuciones técnicas y mi soporte para la comunidad de XNA son reconocidos, bienvenidos y apreciados.

Gracias a quienes me hayan nominado para el premio, a quienes de Uds. que leen este blog, a quienes me hacen preguntas técnicas vía email, a quienes me invitan a participar en eventos de XNA, y por último pero no menos importante, a Microsoft por otorgarme este gran honor. [... la música de fondo comienza a tocar indicando que mi discurso debe finalizar ...]

Este año voy a continuar publicando artículos, enviando recomendaciones para XNA a través de MS Connect (nones, no se van a deshacer de mí!), participando en foros técnicos, y ayudando a otros miembros de la comunidad XNA a esparcir la voz de que XNA manda!

Salúd!
~Pete

[Seguiré de licencia hasta el día 15 de Enero.]

PLANTILLA DRAWABLEGAMECOMPONENT

El artículo original fue escrito el 15-Dic-08.

Si haz estado usando XNA GS desde hace un tiempo, deberías saber qué son los denominados componentes de juego.

Lo que es más, además deberías saber que gracias a la integración de XNA GS con Visual Studio, a fin de agregar un nuevo componente sólo tienes que:

  1. Ir al explorador de soluciones,
  2. Navegar entre la lista de proyectos,
  3. Hacer click con el botón derecho del ratón sobre el proyecto elegido,
  4. Seleccionar Agregar -> Nuevo Item,
  5. Elegir "Game Component" de entre la lista de componentes disponibles,
  6. Pulsar el botón "Agregar", y
  7. Voilá ... tu nuevo componente de juego se incluyó en el proyecto.

Esto es fenomenal!

Ahora bien, y de nuevo: si has estado utilizando el framework de XNA deberías saber que hay una especificación de la clase de componentes de juego para aquellos que se dibujen en pantalla; esto es: "DrawableGameComponents".

Desafortunadamente, si quieres crear uno de esos componentes no hay atajos, lo cual significa que o bien lo creas desde el principio o bien utilizas un componente de juego como archivo fuente y luego lo modificas como corresponda. En resúmen, no hay aún una plantilla de componentes de juego dibujables integrada a Visual Studio.

Para cambiar esto, sin embargo, podrías crear tu propia plantilla "DrawableGameComponent", llendo en VS a "Archivo -> Exportar Plantilla", y luego eligiendo el item que quieres exportar como la plantilla.

Luego de realizar la totalidad del proceso de exportación puedes integrar tu nueva plantilla a la lista de items de entre los cuales puedes elegir al utilizar la funcionalidad "Agregar Nuevo Item" de Visual Studio.

El problema con esta solución es que la nueva plantilla será listada por defecto en la sección "My Templates" del cuadro de diálogo "Agregar", completamente desvinculado a la sección de items de "XNA Game Studio 3.0". Quizás te parezca aceptable la solución brindada "por defecto", pero si no te lo parece, entonces deberás modificar un poco la plantilla generada recientemente, cambiar su ubicación y una cosa más que voy a explicar a continuación.

Primero, he subido una plantilla de clase para el componente de juego dibujable aquí:

http://www.megaupload.com/es/?d=74ITZDJX

Este archivo .zip contiene a todos los archivos apropiados que nos ayudarán a integrar de la mejor manera a la plantilla en Visual Studio.

Así que, sigue estos pasos:

  1. Descarga el arhivo zip antedicho,
  2. Opcional: abre el archivo ".vstemplate" paa averiguar como funciona el truco,
  3. Busca la carpeta "ItemTemplates" en el directorio donde está instalada tu versión de Visual Studio 2008, y
  4. Copia el archivo .zip a dicho directorio.

Usualmente, la carpeta "ItemTemplates" está ubicada en "%vs90comntools%\Common7\IDE", así que digamos que si estás usando la edición de VS08 Standard, Pro or Team, probablemente encuentres algo así: "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ItemTemplates".

Casi estamos ahora por terminar. Hay una tarea pequeña que debemos hacer previo a alcanzar nuestra meta:

  1. Ve a "Inicio -> Microsoft Visual Studio 2008 -> Herramientas",
  2. Abre "Visual Studio 2008 Command Prompt" (línea de comandos de Visual Studio '08) como un Administrador,
  3. Ejecuta el siguiente comando auto-explicativo "devenv /installvstemplates".
  4. Cuando el comando finalice su ejecución, abre Visual Studio de nuevo.

En caso que estes utilizando la versión express, tienes que ubicar la carpeta en menu "Inicio" para dicha edición y, en lugar de escribir "devenv" tal cual indiqué en el punto (3.) arriba, debes pués escribir "VCSExpress".

Si todo sale bien, cada vez que desees agregar un nuevo item verás algo así:

O mejor aún, así:
Dicho sea de paso, puedes usar tu propio diseño de ícono en caso que no te agrade el que está incluído con el archivo zip. Para reemplazarlo, sólo borra el "viejo" e incluye el tuyo propio dentro del archivo zip, respetando el viejo nombre (esto es, el nombre solicitado por el archivo plantilla XML).

Bueno, esto es todo. Espero que lo encuentres útil. Y si, puedes repetir el mismo procedimiento para cada plantilla que deses incluir dentro de la sección de items "XNA Game Studio 3.0".

Salúd!
~Pete

VIDEOS DEL C.N.V.

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

Uruguay Gamer ha publicado los vínculos a los videos grabados durante la ceremonia de cierre del tercer concurso nacional de desarrollo de videojuegos realizado en Uruguay.

En el sitio oficial encontrarán los comentarios de Chaim Gingold para cada videojuego premiado.

Nota: el presentador en ambos videos es Gonzalo Frasca, CEO de Powerful Robot Games (una de las más reconocidas empresas uruguayas de desarrollo de videojuegos) y el organizador principal del la competencia.

Aquí está el video 1 (el que recibe el primer premio so yo):

Aquí está el video 2 (aquí pueden ver a Frank Baxter -embajador de US en Uruguay- felicitando al gran ganador y al final del video, verán a Chain Gimgold mismo en el panel de jueces):

Que lo disfruten!
~Pete

VIDEOJUEGO MEJOR PROMESA 3D

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

Mi juego para PC "The Riegel Battle" (creado con XNA GS) ha sido hoy galardonado con el primer premio en la categoría "Promesa 3D" durante el tercer Concurso Nacional de Videojuegos en Uruguay.

Si bien mi juego no ganó el primer premio de todo el concurso, como un promotor local de XNA, fue un gran honor para mí estar entre los ganadores.

Junto con los organizadores, el concurso fue respaldado por la embajada de EEUU en Uruguay, así que el embajador F. Baxter estuvo presente hoy durante la ceremonia de cierre para felicitar a los ganadores.

Chaim Gingold (uno de los diseñadores del juego Spore) integró el jurado, y también estuvo hoy presente durante la ceremonia antedicha, no sólo como juez sino también para dar una charla sobre los conceptos de diseño base para el editor de caracteres de Spore (gran charla, por cierto).

Aqui tienen un video de mi juego:

Tan pronto consiga fotos oficiales de la ceremonia de hoy, las publicaré aquí.

Salúd!
~Pete

viernes, 13 de febrero de 2009

XNA Y METODOS DE EXTENSION - PARTE 3

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

Con este tercer artículo, estoy culminando mi serie introductoria al mundo de los métodos de extensión y XNA.

Al principio, este artículo se centraría en cómo extender la clase "Random" para obtener nuevos colores, pero "Ziggy", en su reciente artículo titulado "Particles Using LINQ" -el cual vale la pena leer, por cierto- ha mostrado con claridad cómo lograrlo, así que tomaré ese gran artículo como punto de partidapara profundizar en la discusión del tema.

Si ves el método de Ziggy llamado "RandColor" -al cual me referiré en lo que sigue como "NextColor" para estar en línea con la clase Random, el da muestra de una de las nuevas características del Framework 3 de XNA: cuando usas "floats" no hay necesidad de crear un "Vector3" para luego crear un "Color", no más.

En previas versiones de XNA, para usar floats a fin de cear un color, debías declarar:

public static Color NextColor(this Random rand)
{
    return new Color(new Vector3(rand.NextFloat(), rand.NextFloat(), rand.NextFloat()));
}

Pero desde el XNA Framework 3 en adelante, lo siguiente también funciona:

public static Color NextColor(this Random rand)
{
    return new Color(rand.NextFloat(), rand.NextFloat(), rand.NextFloat());
}

Ahora bien, si ves la clase "Particle2D" en el artículo de referencia, entonces notarás el campo llamado "Color", el cual es declarado como una variable que comienza en mayúscula; un nuevo método de extensión podría ser creado para dicho campo, como sigue:

public static void NextColor(this Random rand, out Color color)
{
    color = new Color(rand.NextFloat(), rand.NextFloat(), rand.NextFloat());
}

Por ende, podemos usarlo dentro del método de actualización "Update" en el referido ejemplo, reemplazando la siguiente línea:

p.Color = rand.NextColor();

Por esta nueva línea:

rand.NextColor(out p.Color);

Dado que el tipo "Color" es un estructurado (struct), la ventaja de usarlo sobre la primer opción podría considerarse en general como marginal, pero eventualmente te encontrarás con la situación donde incluso una mejoría mínima en tu código es deseada, especialmente cuando te manejas con el compact framework.

Nueva pregunta: que tal si sigues la guía de diseño de C#, declarando el campo "color" en minúscula como un campo privado y así mismo, incluyes un "getter" y un "setter" en la clase, en la forma de una propiedad pública llamada "Color"?

En ese caso, la nueva sobrecarga no puede usarse dado que Propiedades e Indexadores no pueden pasarse como parámetros "out" y/o "ref". Por qué? Bueno, simplemente recuerda que cuando usas propiedades e indexadores no estás tratando con variables sino métodos. En este caso particular, la primera implementación -esto es, la cual se incluye en el artículo de Ziggy- sería la mejor solución.

Reconozcámoslo! Podrías estar tentado a pasar la instancia existente de "Particle2D" como "ref" para salvar la restricción antedicha, tal como sigue:

public static void NextColor(this Random rand, ref Particle2D particle)
{
    particle.Color = new Color(rand.NextFloat(), rand.NextFloat(), rand.NextFloat());
}

Debería compilar bien PERO SOLO si NO PASAS a ese método la variable temporal creada por un bucle "foreach" -en contraposición al ejemplo base. Esta operación simplemente NO ESTA PERMITIDA para las variables de iteración de bucles "foreach". Mi consejo aquí es simple: evita esta tentadora implementación y como dije antes sólo utiliza lo presentado por Ziggy. En general, la solución más simple es la correcta.

Una nota al márgen: en caso que seas nuevo con C#, es importante notar que en este caso estamos utilizando "ref" en vez de "out", puesto que necesitamos una instancia existente (esto es, no nula) de la clase a fin de asignar el nuevo color. De lo contrario, al usar "out" tendríamos que crear una instancia del tipo "Particle2D" desde dentro del método de extensión mismo ANTES de asignar el nuevo color al mismo.

Ahora bien, pasemos a una situación interesante. Que tal si:

  1. No estamos ni estaremos dentro de un bucle "foreach",
  2. No queremos exponer la totalidad del tipo "Particle2D" a un método de extensión, y al mismo tiempo
  3. Precisamos usar este método para aquellas clases donde estén declaradas propiedades de asignación de colores ("setters")?

Si ese es el caso, entonces deberías echarle un vistazo a la siguiente implementación:

public interface IColor
{
    Color Color { get; set; }
}
 
public static class HelperMethods
{
 
    ...
 
    public static void NextColor<T>(this Random rand, ref T instance)
        where T : class, IColor
    {
        instance.Color = new Color(rand.NextFloat(), rand.NextFloat(), rand.NextFloat());
    }
}

Como puedes ver, estamos aqui combinando el poder de "Generics" con métodos de extensión.

Pero, por qué la restricción "IColor"? Lo que estamos diciendo aquí es simple: únicamente tipos que implementen la propiedad "Color" pueden llamar a este método de extensión. Y aquellas instancias no nulas pasadas al método como parámetro (recuerda que estamos utilizando la palabra "ref" aquí) serán manejados bajo el tipo "IColor".

Ok, pero por qué la restricción "class" adicional? Pregunta engañosa. Como corolario de la anterior restrcción, sin no especificamos que sólo se aceptan instancias de clases como parámetros, entonces podrían además pasarse estructurados al llamar al método, y para ello también deberán implementar la interface "IColor" ... Lo cual siginifica? ... Tic tac tic tac ... Pseee, "boxing". Ummm! Esa palabra desagradable. Manejar estructurados mediante interfaces causa "boxing". Da miedo, no? Ahora ya lo sabes, en caso que no lo supieras.

Para resumir, como puedes ver los métodos de extensión son muy prácticos, pero tienes que usarlos con cuidado si quieres evitar ingresar en una trampa de diseño. Como es habitual, no todas las situaciones pueden resolverse de la misma forma, incluso si al principio te suena lógico. Puedes encontrarte diciendo "Quéeee? Pero por quéeee? ...". Así que espero que estos ejemplos te muestren qué usar y cuándo, y qué evitar y porqué.

Algunos pensamientos finales sobre métodos de extensión:

  1. Son fenomenales para tareas habituales: créelo o no, dos tipos comúnes donde siempre me encuentro repitiendo tareas usuales son las clases "TYPE" y "RANDOM", pero esta bondad puede ser utilizada para tantos tipos como precises,
  2. Son de utilización intuitiva: antes de que esta funcionalidad fuera introducida en el .NET Framework, uno usualmente escribía clases estáticas llenas de ayudantes ("helpers"); por favor no me malentiendas, aun tienes que hacerlo, pero ahora estos ayudantes se presentan en una forma más intuitiva y amigable al usuario. Con los métodos de extensión, no tienes que buscar métodos "navegando" la clase estática donde los declaraste e implementaste; en cambio, únicamente miras las instancias de los tipos que estés utilizando. Y por último pero no menos importante,
  3. Cumplen con los principios de diseño: como dije en mi primer artículo de la serie, no estamos rompiendo ningún principio de diseño, debido a que no contamos con acceso directo a los miembros privados de los tipos "extendidos".

En resúmen, si utilizas métodos de extensión con cuidado y sabiduría, pueden convertirse en un amigo verdadero a la hora de programar tu juego u aplicación basada en XNA: simplemente te dan una mando para entender cualquier tipo con nuevos métodos COMO SI éstos originalmente fueran parte de dichos tipos, lo cual a su vez puede volverse útil, por ejemplo:

  1. Cuando no quieres crear una especificación de una clase, o por otro lado,
  2. Cuando quieres agragar mas funcionalidad dentro de una clase sellada ("sealed") de alguna manera -hasta cierto punto, claro está.

Así que vamos, prueba los "Extension Methods" y comparte lo que encuentres con la Comunidad XNA. Sería grandioso y divertido saber para que los estas utilizando!

Bueno, espero que hayas encontrado esta serie útil. Comentarios y sugerencias constructivas son siempre bienvenidas.

Otra ronda, salud!
~Pete

XNA Y METODOS DE EXTENSION - PARTE 2

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

Continuando con el tema de cómo usar la muy útil funcionalidad provista por los "Extension Methods" en nuestras creaciones basadas en XNA, para este artículo necesitaremos el juego de ejemplo "FuelCell" (recuerda que encontrarás este tutorial -junto con el "TopDownShooter"- en los archivos de ayuda en la sección "What's New In This Release").

Ok. En el capítulo llamado "Finishing Touches" del ejemplo "FuelCell", verás el siguiente método al final del archivo "FuelCellGame.cs":

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;
}


Si recuerdas mi último artículo sobre este tema, debes estar anticipando a esta altura que el estructurado "Viewport" puede ser "extendido" tal que se obtenga el área "insegura" en una sola llamada a un método.

Si esto es así, adivinaste! Podemos usar los métodos de extensión para declarar una operación llamada, digamos "GetFullArea", y brindarle la implementación adecuada.

No sólo pasaremos otr vez un parámetro por referencia (usando la palabra reservada "out") -como hicimos en la Parte 1 de esta serie- sino también para este caso particular, puesto que estamos manejándonos con un estructurado, podemos darnos el lujo de implementar una método sobrecargado que localmente declare, cree y retorne una instancia de un "Rectangle" (sé lo que estás pensando, no hay propiedades de extensión en C#, así que necesitaremos obtener el rectángulo llamando a un método).

Usando métodos de extensión, nuestra solución debería lucir como sigue:

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);
}
}
}

Ahora bie, el método "GetTitleSafeArea(...)" podría ser reimplementado de esta forma:

private Rectangle GetTitleSafeArea(Viewport viewport)
{
Rectangle retval;

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

 return retval;
}

O también así:

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

Recuerda dos cosas importantes:

  • Tendrás que agregar la sentencia "Using" correspondiente en el archivo que usará el método de extensión (que en este ejemplo es "MyNamespace.Math"), y
  • Tendrás que agregar manualmente una referencia al ensamblado "System.Core" en tu proyecto.

Esta técnica puede usarse para obtener, digamos -entre otras cosas, un "Vector2" que contenga el ancho y la altura de un "Viewport" (idem para las áreas de visualización y cliente). Dejo ésto como un ejercicio para que lo intentes (verás que es muy fácil de implementar).

Espero que encuentres este artículo útil.

Hasta la próxima,
~Pete

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

GESTORES DE CONTENIDO "101"

El artículo original fue escrito el 15-Oct-08.

Si has estado utilizando XNA GS desde hace un tiempo, entonces conocerás de memoria los beneficios del ducto de contenidos. Si no, sólo para mencionar unos pocos, aquí está una breve losta para que cheques antes de seguir leyendo esta publicación:

  • Mejora el proceso de importación de activos a tus propios juegos basados en XNA,
  • Tiempo de carga más rápido para activos pre-construidos (léase, compilados a un formato binario), y
  • Fácil de extender para importar contenido personalizado en nuestras creaciones basadas en XNA.

Ok, ahora continuemos.

I. LO BASICO

Durante el proceso de creación de tu juego, una vez construidos todos tus activos en tiempo de compilación, precisarás entonces un Gestor de Contenido ("Content Manager") para cargarlos en tiempo de ejecución. Afortunadamente, como quizás ya puedas saber, XNA ya provée uno para ti. Lo que es más, la plantilla de proyecto de juego define una referencia y crea una instancia de la misma en la clase "Game".

El gestor se encargará de ambos, los procesos de carga y descarga de activos pre-construidos. Esto significa que detrás de cámaras, no sólo gestionará la instanciación de los activos que están siendo cargados sino también, cuando se requiera apropiadamente, desechará por ti aquellos que sean desechables ("disposable").

Para cargas, tendrás que usar una sentencia tal como "this.Content.Load<...>(...);" o también como ser "this.Game.Content.Load<...>(...);" y el lugar usual para incluir algúna de ellas es dentro del método suplantado "LoadContent" de tu clase "Game" y/o componentes de juego dibujables.

Y para descargas, todo lo que tienes que hacer es llamar al método "Unload()" de tu gestor. De nuevo, el lugar usual para inluir dicha sentencia es dentro del método suplantado "UnloadContent" de tu clase "Game" y/o componentes de juego dibujables.

II. DETRAS DE LA PANTALLA

Ahora bien, si has estado leyendo atentamente notarás que puedes cargar tus activos de forma individual pero no hay forma de descargarlos individualmente. Al principio puedes pensar que esto no es más que un restricción desagradable, pero como puedes llegar a concluir luego de leer este artículo -o al menos como espero que suceda, en realidad no lo es tanto. De hecho, en cierta forma contribuye a gestionar tus activos de manera prolija.

En general, por cuestiones de performance, de asignación eficiente de memoria y mejoras en la experiencia de usuario, diseñas tu juego de forma tal que todos los activos requeridos sean cargados de "una sola vez", digamos, al comienzo de cada nivel. E incluso si cargas tus activos de forma individual, ese comportamiento desde la óptica de "un período de tiempo compartido" -para llamarlo de alguna manera, muestra que has programado la lógica de tu juego a los efectos de tratar a todos los activos cargados como un grupo.

El razonamiento antedicho puede pués extenderse al proceso de descarga de activos. No hay necesidad de asumir a priori que cada activo sea descargado individualmente en distintos momentos durante el juego. Y por lo tanto, los activos serán tratados como un grupo, en este caso durante el nivel de juego respectivo, y descargados en forma conjunta, otra vez, en "un período de tiempo compartido", cuando ello sea requerido.

Puedes cuestionarte: "Ok, pero que si quiero desechar cierto activos en tiempo de ejecución y, al mismo tiempo, mantener otros en memoria?". La respuesta a esa inquietud en verdad es simple: crea más de un gestor de contenido.

Dentro de instantes vamos a volver a este punto, pero primero veamos otro aspecto importante del gestor de contenido.

III. LO QUE NO SE DEBE HACER

Evita desechar manualmente tus activos!!! Al gestor de contenido no le gusta que manejes la destrucción de activos a través del uso de sentencias como "this.myAsset.Dispose();". De hecho, el gestor NO monitorea si lo haces, y por ende, podría llegar a confundirse cuando ello sucede.

Veamos un ejemplo. Digamos que tienes las clases "Screen1" y "Screen2" tal que:

  1. Ambas comparten el gestor de contenido de todo el juego,
  2. Ambas cargan la misma texture pre-construida en sus respectivos campos locales "Texture2D",
  3. Screen1 se crea y muestra primero, y
  4. Screen2 se crea y muestra únicamente luego que Screen1 es desechada de forma manual.

Si Screen1 manualmente desechara la textura (ya sea mediante el uso del patrón de diseño "Dispose" o llamando al método Dispose dentro del método UnloadContent) sin llamar a "this.Game.Content.Unload();" primero, cuando Screen2 intente dibujar dicha textura en pantalla obtendrás una excepción indicando que la texura ha sido desechada, incluso si Screen2 cargó dicha textura. Como dije antes, el gestor de contenido se confunde en situaciones como ésta.

Por lo tanto, evita estas implementaciones:

protected override void Dispose(bool disposing) {
if (disposing) {
if (this._myTexture != null) {
this._myTexture.Dispose(); this._myTexture = null;
}
} base.Dispose(disposing);
}

... y ...

protected override void UnloadContent() {
if (this._myTexture != null) {
this._myTexture.Dispose(); this._myTexture = null;
} base.UnloadContent();
}

Siendo la forma apropiada de desechar activos:

protected override void UnloadContent() {
this.Game.Content.Unload(); // O el gestor que uses. base.UnloadContent();
}

Nota por favor que el gestor de contenido también permite lo siguiente:

protected override void Dispose(bool disposing) {
base.Dispose(disposing); if (disposing) {
if (this._myTexture != null) {
this._myTexture.Dispose(); this._myTexture = null;
}
}
} protected override void UnloadContent() {
this.Game.Content.Unload(); // O el gestor que uses. base.UnloadContent();
}

Cúmple con lo antedicho, y lo harás bien.

IV. QUE HACER

Volviendo a la cuestión de cómo descargar activos en diferentes momentos durante el tiempo de ejecución, hay una práctica que ayudará a entender porqué es sano y por ello, recomendable, tener más de un gestor de contenido en tus juegos creados con XNA.

Cuando creas un programa declaras variables globales y locales. Esta práctica muy común incorporada a nuestras tareas de programación diarias es la clave que nos conducirá a pensar en activos globales y locales.

Puedes considerar a un activo como "global" si su tiempo de vida dura todo el juego, o al menos, la mayoría del mismo. En este sentido, los activos deben ser considerados "locales" en cambio si se espera y pretende descargarlos al terminar un nivel del juego pero cuando el juego mismo aún no ha finalizado y no la hará, al menos, por algún tiempo más.

Por ende, cuando estes creando un juego con XNA usa: 1) Un gestor de contenido "global": que es instanciado pro defecto dentro de la clase "Game" (al cual puedes acceder usando la propiedad Content), y 2) Uno (o más) gestor(es) de contenido para activos "locales": digamos un gestor de contenido por nivel, pantalla, o por cualquier categoría que satisfaga al diseño de tu juego.

De nuevo, agrupa tus activos en conjuntos ("clusters") basados en su tiempo de vida, y sabrás cuáles son globales y cuáles son locales. Si todas tus pantallas usarán un mismo "spritefont" en común, entonces no hay motivo para cargarlo y descargarlo para cada pantalla; simplemente cárgalo una vez y mantenlo en la colección de activos globales. Si una modelo de nave será usado en un único nivel, adminístralo de manera local a dicho nivel.

Como puedes ver, usar más de un gestor de contenido es una práctica sana: fácil de aplicar, fácil de depurar, de fácil mantenimiento y fácil de extender.

A veces no hay necesidad de contar con más de un gestor de contenido para todo el juego, lo cual depende del juego mismo por supuesto, pero aún es una buena práctica sana de la cual estar al tanto y a la cual acostumbrarse, dado que al final ayudará a administrar nuestros activos en tiempo de ejecución de una manera más ordenada y conveniente.

Ergo, mi recomendación es: acostúmbrate. Cuanto antes, mejor.

V. CONCLUSION

El ducto de contenido ("Content Pipeline") es una gran herramienta para importar activos a nuestros juegos creados con XNA, lo cual se complementa con el gestor apripiado para manejar el contenido en tiempo real: la clase "ContentManager".

Mediante el uso de instancias de esta clase de forma sabia y apropiada, podríamos obtener (más) eficiencia en materia de: implementación, depuración, mantenibilidad y extensibilidad.

A efectos de lograr esta meta, ciertas prácticas deben aplicarse:

  1. Para activos globales, usa una instancia de la clase "ContentManager",
  2. Para activos locales, usa al menos una instancia de la clase "ContentManager", y
  3. No deseches lo activos manualmente; en cambio, llama al método "Unload()" del gestor de contenido.

Espero que encuentres este artículo útil. Tus comentarios y sugerencias son bienvenidas.

Hasta la próxima!
~Pete

miércoles, 11 de febrero de 2009

EJEMPLOS NO OFICIALES PARA XNA 3 BETA

El artículo original fue escrito el 02-Oct-08.

Hola migos!

Desde el momento que el CTP salió pasé bastante tiempo actualizando todos los ejemplos disponibles en el sitio de XNA Creators a la versión 3.0 de XNA, para mi propio uso.

Luego, cuando el beta fue publicado, y teniendo en cuenta que talvez no veamos grandes cambios al mismo hasta la versión final, decidí recrearlos -cumpliendo con los términos y condiciones de licenciamiento- de forma tal de poder publicarlos en la Web para aquellos que eventualmente los necesiten.

Una nota importante: estos archivos NO son oficiales por lo que úsalos a tu propio riesgo!!! De lo contrario, puedes esperar hasta que la versión 3.0 final sea liberada y con ello, por los archivos de educación finales.

Los únicos cambios en el código que verás están relacionados con los proyectos 2D que usaban el método WrapAngle (creado en la misma clase que lo usa). En cambio, MathHelper.WrapAngle(...) está ahora en su lugar. Y ... sí! Casi me olvido: cada archivo zip contiene un proyecto sólo para Windows.

Bueno, aca van (aférrate a algo):

Primera Ola:

Segunda Ola:

Sí! Tercera Ola: Creo que ... ésta es la Cuarta Ola (?) Ok. Quién Soy? Y Por Qué Estoy Escribiendo Esto? No, En Serio ... (Por Qué Sigo Haciendo Esto?)

Fiuuuu! Al fin, terminé ... loco, dejé de contar luego de los primeros 20 hipervínculos.

Bueno, yo ya estoy, así que en caso que estes usando el beta de XNA 3.0, espero que los encuentres útiles.

Salud, brindemos!
~Pete