Archive for the 'XNA' Category
El XNA Content Pipeline
Que es el Content Pipeline?
El content pipeline es una API que permite a los desarrolladores y diseñadores incorporar contenidos multimedia en los proyectos creados con XNA framework, estos contenidos multimedia son por ejemplo:
- imágenes
- sonidos
- contenido 3d
- efectos
Por defecto el content pipeline soporta una amplia gama de formatos de archivo diferentes los cuales son usualmente usados en la industria de los videojuegos, el content pipeline (en adelante CPL) facilita el acceso a estos archivos y brinda un interfaz de acceso unificada que permite acceder a dichos recursos desde cualquier objeto utilizado dentro del juego sin necesidad de hacer uso de múltiples referencias cruzadas lo cual desde luego va en favor de la independencia de cada componente lo cual en un juego generalmente es una tarea muy difícil de hacer. Sin embargo las capacidades del CPL podrían verse limitadas por la cantidad de archivos que soporta puesto que es muy común que en la industria de los videojuegos se usen formatos de archivo independientes de acuerdo a las necesidades particulares de cada proyecto, es aquí donde reside una de las mas importantes características del CPL… es extensible.
El CPL incorpora un marco de trabajo que permite fácilmente incorporar soporte a diferentes tipos de archivo e incluso extender la funcionalidad de un tipo de archivo ya soportado.
Como Funciona el Content PipeLine?
Lo primero ha tener en cuenta son los elementos con los cuales trabaja:
Namespace: Microsoft.Xna.Framework.Content.Pipeline
ContentImporter
ContentProcessor
Namespace: Microsoft.Xna.Framework.Content
ContentTypeReader
Namespace: Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compile
ContentTypeWriter
El camino comienza con el ContentImporter este es el encargado de realizar lectura física del archivo a importar y colocar la información en un objeto capaz de almacenarla. Seguidamente la información colocada en el objeto es pasada a través de un ContentProcessor el cual se encarga de realizar transformaciones en la información cargada en el paso anterior. El ContentTypeWriter se encarga de escribir ese objeto en un archivo con el formato del CPL. Todos los pasos anteriores suceden en tiempo de compilación, el ultimo paso sucede en tiempo de ejecución cuando el juego ya esta ejecutándose, es allí donde el ContentTypeReader lee los archivos creados por el CPL para cargar en memoria el recurso almacenado. Debido a que se debe adicionar una referencia a estas clases en el CPL es necesario que los tres primeros objetos existan compilados en una librería dinámica, mientras que el ultimo puede hacer parte el juego directamente.
Como hacer que el Content Pipeline soporte un archivo diferente?
Normalmente con tan solo el ContentImportery el ContentProcessor es suficiente para cargar un tipo de archivo ya que basicamente lo que se hace es utilizar estos objetos para convertir los datos del archivo en uno de los tipos de dato ya soportados por el CPL, sin embargo si se requiere información adicional que no es contenida en el tipo soportado es entonces necesario que el desarrollador establezca la forma en que el CPL escribe esos datos en un archivo del CPL y la forma en que el CPL debe cargarlos una vez se solicite desde el juego, para ello son las clases ContentTypeWriter y ContentTypeReaderLa buena noticia es que para adicionar soporte a un nuevo tipo de archivo estas clases proveen los mecanismos necesarios para implementarlo de manera relativamente fácil, la mala noticia es que todas son clases abstractas así que la mayoría del trabajo lo debe hacer uno mismo.
A continuación un ejemplo de Implementación de un nuevo tipo de archivo para el CPL.
Supongamos existe un tipo de arhivo bmp2 dicho archivo posee la información habitual de un archivo bmp más tres datos muy importantes que requerimos (todos los campos son un ejemplo):
- Color de Máscara
- Número de Colores
- Prioridad
La tarea nro 1 crear un ContentImporter. Hay que recordar que ContentImporter es una clase abstracta asi que se debe crear una clase que herede de ContentImporter, adicionalmente esta clase es genérica por lo cual debemos pasar el parámetro del tipo de dato que se soportara para importar.
public class BMP2Importer : ContentImporter { }
pero un momento… de donde salio BMP2Content ? bueno dado que se esta incorporando un nuevo tipo de archivo es necesario que crear un objeto contenedor de los datos que se cargan desde ese archivo, teniendo en cuenta que el formato bmp2 lo usaremos para crear texturas 2D lo mejor que se puede hacer es crear una clase que herede de un contenedor existente, para este caso Texture2DContent :
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content.Pipeline; using Microsoft.Xna.Framework.Content.Pipeline.Graphics; public class BMP2Content : Texture2DContent { public BMP2Content(): base() {} private Color maskColor; public Color MaskColor { get { return maskColor; } set { maskColor = value; } } private int numColores; public int NumColores { get { return numColores; } set { numColores = value; } } private byte prioridad; public byte Prioridad { get { return prioridad; } set { prioridad = value; } } }
Este contenedor permite manipular los datos desde su cargue hasta su transformacion y escritura en un archivo del content pipeline, sin embargo se requiere de una estructura adicional para manipularlo, porque? bueno el contenedor puede ser realmente cualquier estructura pero se requiere que sea liviana puesto que la cantidad de recursos que tengamos puede incrementar dramaticamente el tiempo que se invcierta en la compilacion del proyecto ya que cada uno de los recursos pasara por un importer, un processor y un writer, ya cuando se va a utilizar esa informacion en un objeto util para el juego si debemos disponer de toda la infromacion necesaria.
Para este ejemplo, debido a que el archivo de tipo bmp2 va a ser usado como textura se pueden usar dos opciones, una es crear una clase de textura nueva (poco recomendable) y la otra es crear una clase que herede de un tipo de textura ya existente, para el caso lo que mas nos conviene es crear una clase que herede de Texture2D.
public class BMP2Texture2D : Texture2D { public BMP2Texture2D(GraphicsDevice device, int height, int with, int numberLevels, ResourceUsage usage, SurfaceFormat format) : base(device, with, height, numberLevels, usage, format) {} public BMP2Texture2D(GraphicsDevice device, int height, int with, int numberLevels, ResourceUsage usage, SurfaceFormat format, ResourceManagementMode resourceManagementMode) : base(device, with, height, numberLevels, usage, format,resourceManagementMode) { } private Color maskColor; public Color MaskColor { get { return maskColor; } set { maskColor = value; } } private int numColores; public int NumColores { get { return numColores; } set { numColores = value; } } private byte prioridad; public byte Prioridad { get { return prioridad; } set { prioridad = value; } } }
Esta clase ya trae todo lo que necesita una textura 2d más las cosas propias del formato bmp2 de el ejemplo.
Ahora si se puede crear el ContentImporter indicando el tipo de contenido a almacenar.
[ContentImporter(".bmp2", DisplayName = "Imagen BMP2", DefaultProcessor = "Procesador para imagenes BMP2")] public class BMP2Importer : ContentImporter<bmp2content> { public override BMP2Content Import(string filename, ContentImporterContext context) { BPM2 miBmp = new BMP2(filename); //Este objeto es de ayuda para recuperar la informacion de color y llevarla facilmente al //MipmapChain del objeto textureContent PixelBitmapContent<rgb24> pixelHelper; //Array de textura a retornar BMP2Content textureContent; //Array de bytes para capturar la informacion del archivo JKI byte[] byteArray; //Inicializa un nuevo contenedor de datos de pixeles pixelHelper = new PixelBitmapContent</rgb24><rgb24>(miBmp.Width,miBmp.Height); //Cargar la informacion de bytes para el archivo byteArray = miBmp.GetBytes(); //Envia los datos de bytes al pixel helper pixelHelper.SetPixelData(byteArray); //Inicializa un nuevo contenedor para la textura textureContent = new BMP2Content(); //Carga la informacion de la imagen en la textura textureContent.Mipmaps = new MipmapChain(pixelHelper); //Guarda el color de mascara de la imagen textureContent.MaskColor = new Color(miBmp.ColorMascara.R,miBmp.ColorMascara.G, miBmp.ColorMascara.B); //Establecer el numero de colores textureContent.NumColores = miBmp.Palette.Count; textureContent.Prioridad = 0; return textureContent; } } </rgb24></bmp2content>
La primera linea es un atributo de la clase, la cual le permitira al Content Pipeline identificar que esta es un ContentImporter y le proporcionara informacion adicional acerca de que informacion debe mostrar desde el IDE de Visual Studio respecto a los archivos que este ContentImporter es capaz de utilizar asi como un nombre descriptivo de la funcionalidad. Seguidamente lo que se hace cargar desde archvo un objeto BMP2 el cual desde luego es un objeto que ya es capaz de cargar un archivo bmp2, el objeto pixelHelper es utilizado como una ‘Helper Class’ ya que si bien no se necesita de manera directa, si es un excelente atajo para poder crear un MipmapChain el cual es requerido para cargar en un objeto Texture2DContent con la informacion de la imagen que se requiere, finalmente en la parte inferior del método se asignan la informacion adicional que se requiere.
Una vez se ha importado la informacion desde el archivo esta debe pasar por el processor, el procesor se debe hacer para que funcione igual que funciona un procesor para una imagen BMP normal asi que se puede reutilizar un processor que ya este creado en XNA y eso es todo ya que sobre el resto de la informacion no se requiere hacer ninguna modificacion. El processor en este caso es bastante sencillo:
[ContentProcessor(DisplayName = "Procesador para imagenes BMP2")] class BMP2Processor : ContentProcessor<bmp2content , BMP2Content> { public override BMP2Content Process(BMP2Content input, ContentProcessorContext context) { input[i] = (BMP2Content)context.Convert<texturecontent ,TextureContent>( (TextureContent)input[i], "SpriteTextureProcessor"); return input; } } </texturecontent></bmp2content>
El atributo inicial tiene la misma funcionalidad que en el importer y es proveer una pequeña descripcion del ContentProcessor para que aparezca en el IDE de Visual Studio. Se llama al metodo context.Convert indicandole que debe usar el builting processor SpriteTextureProcessor, esto hara que la informacion inherente a una textura 2d sea procesada de la manera habitual . Finalmente se procede a crear el ContentTypeWriter.
[ContentTypeWriter] public class BMP2ContentWriter : ContentTypeWriter<bmp2content> { protected override void Write(ContentWriter output, BMP2Content value) { //Buffer para la informacion de la imagen byte[] pixelData; //Escribe alto y ancho output.Write(value.Mipmaps[0].Height); output.Write(value.Mipmaps[0].Width); //Obtiene el contenido de la imagen pixelData = value.Mipmaps[0].GetPixelData(); //Escribe el tamaño de datos de la imagen output.Write(pixelData.Length); //Escribe la informacion de la imagen output.Write(pixelData); //Escribe el color de mascara de la imagen output.WriteObject<color>(value.MaskColor); } public override string GetRuntimeReader(TargetPlatform targetPlatform) { return typeof(BMP2ContentReader).AssemblyQualifiedName; } } </color></bmp2content>
El atributo inicial tiene la misma funcionalidad que en el importer. El método write lo que hace es escribir en el archivo del CPL la informacion relacionada con el archivo ya previamente cargado en el BMP2Content, el objeto output recibido como parametro posee metodos para escribir a nivel de tipos nativos y soporta una amplia gama de tipos incluidos en el xna framework, el método GetRuntimeReader Almacena informacion que le indica el CPL en tiempo dejecucion que clase debe instanciar para poder convertir un archivo del CPL en el objeto que se desea, es decir indica cual implementacion de ContentTypeReader debe utilizarce para tal fin. Esta es la implementacion de ContentTypeReader para leer en tiempo de ejecucion lo que el ContentTypeWriter ha guardado en tiempo de compilacion, el objeto input recibido como parametro posee metodos para leer a nivel de tipos nativos y soporta una amplia gama de tipos incluidos en el xna framework.
public class BMP2ContentReader : ContentTypeReader<bmp2texture2d> { protected override BMP2Texture2D Read(ContentReader input, BMP2Texture2D existingInstance) { //Se obtiene el dispositivo grafico GraphicsDevice graphics = ((IGraphicsDeviceService) input.ContentManager.ServiceProvider.GetService(typeof(IGraphicsDeviceService))).GraphicsDevice; //Inicializa la instancia actual, lee el ancho y alto de la imagen existingInstance = new BMP2Texture2D(graphics, input.ReadInt32(), input.ReadInt32(), 1, ResourceUsage.AutoGenerateMipMap, SurfaceFormat.Color); //Establece el contenido de la imagen //lee el tamaño de la informacion en bytes a leer //y lee la informacion en bytes existingInstance[i].SetData<byte>(input.ReadBytes(input.ReadInt32())); //Establece el color de mascara existingInstance[i].maskColor = input.ReadObject<color>(); return existingInstance; } } </color></byte></bmp2texture2d>
Eso es todo, para hacerlo funcionar, se requiere compilar estas clases en una dll la cual se debe adicionar al content pipeline, para hacer eso se debe ir a las propiedades del proyecto y luego a la ultima pestaña la cual desde luego se llama Content Pipeline, desde alli presionar el boton Add y seleccionar la dll creada. Para que desde el juego se peuda cargar la informacion se debe incluir referencia por lo menos al ensamblado donde se compilo el ContentTypeReader. En adelante tan solo se accede al recurso a travez de un content manager como se hace de manera habitual:
ContentManager content; … … … BMP2Texture myTexture = content.Load<bmp2texture>(@"myImage.bmp2"); </bmp2texture>
Espero les sea de utilidad. Saludos.
Juan Carlos Ruiz Pacheco
Ingeniero de Sistemas
Detección de Colisiones 2D básica
En el procesos de desarrollo de videojuegos las colisiones juegan un papel fundamental tanto que sin ellas se puede decir que no hay juego.
Hay diferentes formas de manejar las colisiones dependiendo de muchos factores como por ejemplo si es un juego 2D o 3D o si es juego de automóviles, uno de aventuras o uno de pelea. Para el caso especifico de los videojuegos 2D, si bien hay diferencias entre unos estilos de juego y otros, existen dos estilos fundamentales para detección de colisiones: Colisión de rectángulos y Colisión de Círculos, cada cual se usa según sea conveniente de acuerdo a la figura que mejor pueda contener al cuadro de animación.
Este articulo se enfocara a la detección de colisiones utilizando rectángulos y/o círculos los principios son aplicables en cualquier herramienta, sin embargo para efectos del articulo se utilizara C# y XNA FrameWork. El performance también es fundamental, como suele suceder hay muchas maneras de hacer las cosas y eso se vera en el desarrollo del articulo.
Cuando utilizar un sistema de colisiones determinado?
Depende principalmente de la forma del sprite, es decir desde luego todo sprite es rectangular, pero la figura descrita dentro de el es generalmente irregular y existen casos en los que un cuadrado no daría la precisión adecuada para la forma dibujada en el sprite como se puede ver en la figura descrita a continuación:

En el ejemplo se puede observar una situación tipica, un programa detector de colisiones diría que en ambos casos hay colisión, pero es evidente que solo hay colisión real cuando se usa detección de colisiones usando círculos. Desde luego también ocurren casos contrarios al anterior, como se ve en la siguiente imagen:
Es claro que el personaje al cual le calculan la colisión usando circunferencias esta en desventaja.
Detección de Colisiones Entre Círculos
Para calcular colisiones entre dos círculos se requieren conocer el radio de cada circulo y su posición, realmente y aunque no pareciera, calcular la colisión de dos círculos es sencillo, se debe determinar la distancia entre el centro de un circulo y el centro del otro círculo, si la distancia entre ambos centros es menor que la suma de los radios de ambos círculos entonces existe una colisión entre ellos .
Así que desde luego el paso a seguir es el paso 1: Establecer la distancia entre dos puntos.
Cálculo de la distancia entre dos puntos
La distancia entre dos puntos se determina haciendo uso de la ecuación:
que se deriva del álgebra de Pitágoras. La implementación de esta ecuación se muestra a continuación:
El código es C# y la estructura Vector2 hace parte de XNA Framework.
/// <summary> /// Calculate the distance between 2 points the algoritm is 100% accurate /// as well this algoritm is not extreme fast /// </summary> /// <param name="p1">Point 1</param> /// <param name="p2">Point 1</param> /// <returns>The distance between the points</returns> public static int AccurateDistance( Vector2 p1, Vector2 p2) { return (int) System.Math.Sqrt((p2.X - p1.X) * (p2.X - p1.X) + (p2.Y - p1.Y) * (p2.Y - p1.Y)); }
Con esto ya se tiene casi todo hecho, solo es necesario sumar los radios de los círculos y restarles la distancia obtenida, si la distancia es <= a 0 entonces existe colisión.
Sin embargo esta formula tiene una debilidad y es que usada de manera intensa (como suele suceder en un vjuego) puede volverse poco eficiente, así que en su lugar se puede hacer uso de la siguiente implementación, que si bien no brinda un resultado 100% preciso si ofrece una muy cercana aproximación a este y sobre todo es mucho más rápida que la versión anterior, la implementación hace uso de la Serie de Maclaurin:
El código es C# y la estructura Vector2 hace parte de XNA Framework.
/// <summary> /// Calculate the distance between 2 points, it use a fast algoritm as well this /// algoritm isn't 100% accurate /// </summary> /// <param name="p1">Point 1</param> /// <param name="p2">Point 1</param> /// <returns>The distance between the points</returns> public static int FastDistance( Vector2 p1, Vector2 p2) { int x = (int)System.Math.Abs(p2.X - p1.X); int y = (int)System.Math.Abs(p2.Y - p1.Y); int min = x < y ? x : y; return System.Math.Abs(x + y - (min >> 1) - (min >> 2) + (min >> 4)); }
Detección de Colisiones Entre Rectángulos
Para determinar si existe colisión entre dos rectángulos se necesita únicamente conocer su posición y sus dimensiones. El cálculo es sencillo, imaginemos que estamos en un sistema de coordenadas de dos dimensiones, si en el eje X el rectángulo 1 inicia antes que termine el dibujo del rectángulo 2 y si el rectángulo 2 inicia antes de que finalice el dibujo del rectángulo 1 y si hacemos el mismo ejercicio en el eje Y entonces existe una colisión entre los rectángulos, si una de las 4 condiciones falla entonces no hay colisión.
Para una implementación de colisiones básica esta implementación es mas que suficiente, informa cuando hay colisión entre dos rectángulos, he usado algunos parentesis adicionales en el if tan solo por claridad, pero estos no son necesarios.
El código es C# y la estructura Rectangle hace parte de XNA Framework.
/// <summary> /// Calculale if two rectangles are colisioning between they /// </summary> /// <param name="a">Rectangle 1</param> /// <param name="b">Rectangle 2</param> /// <returns>bool if an intersection exist if not false</returns> public static bool RectangleColission( Rectangle a, Rectangle b) { if ( (a.X < b.X + b.Width) && (b.X < a.X + a.Width) && (a.Y < b.Y + b.Height)) { return b.Y < a.Y + a.Height; } return false; }
Esta función es lo suficientemente rápida para la mayoría de los casos, sin embargo en ocasiones es necesario no solo determinar si existe colisión entre los rectángulos sino adicionalmente calcular el rectángulo resultante de dicha colisión, este caso es muy común cuando se realizan procesos de detección de colisiones mas avanzadas como por ejemplo la colisión perfecta la cual compara en que puntos del rectángulo de intersección dos colores se tocan entre si, esta seria una implementación optimizada que permite en un solo paso obtener el rectángulo de intersección y determinar si existe o no colisión.
/// <summary> /// Calculale if two rectangles are colisioning between they and obtain the resulting interect rectangle /// </summary> /// <param name="a">Rectangle 1</param> /// <param name="b">Rectangle 2</param> /// <param name="c">output intersection Rectangle </param> /// <returns>bool if an intersection exist if not false</returns> /// <remarks>if not colission exists the output rectangle is the empty rectangle</remarks> public static bool RectangleIntersection( Rectangle a, Rectangle b, out Rectangle c) { int x = System.Math.Max(a.X, b.X); int num2 = System.Math.Min(a.X + a.Width, b.X + b.Width); int y = System.Math.Max(a.Y, b.Y); int num4 = System.Math.Min(a.Y + a.Height, b.Y + b.Height); if (num2 >= x && num4 >= y) { c.X = x; c.Y = y; c.Width = num2 - x; c.Height = num4 - y; return true; } c.Height =c.Width =c.Y =c.X = 0; return false; }
En una próxima entrada del blog estare tratando el tema de colisiones por píxel.
Juan Carlos Ruiz Pacheco
Ingeniero de Sistemas
