Por Juan Carlos Ruiz Pacheco
Normalmente con tan solo el ContentImporter y el ContentProcessor es suficiente para cargar un tipo de archivo ya que básicamente 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 ContentTypeReader.
La 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 archivo 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 así 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 salió BMP2Content ? bueno dado que se está 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 transformación 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 dramáticamente el tiempo que se invierta en la compilación 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 información en un objeto útil para el juego si debemos disponer de toda la información 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 más 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 del 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>
(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 número de colores
textureContent.NumColores = miBmp.Palette.Count;
textureContent.Prioridad = 0;
return textureContent;
}
}
La primera línea es un atributo de la clase, la cual le permitirá al Content Pipeline identificar que esta es un ContentImporter y le proporcionara información adicional acerca de que información debe mostrar desde el IDE de Visual Studio respecto a los archivos que este ContentImporter es capaz de utilizar así como un nombre descriptivo de la funcionalidad.
Seguidamente lo que se hace cargar desde archivo 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 información de la imagen que se requiere, finalmente en la parte inferior del método se asignan la información adicional que se requiere.
Una vez se ha importado la información desde el archivo esta debe pasar por el processor, el proccesor se debe hacer para que funcione igual que funciona un proccesor para una imagen BMP normal así que se puede reutilizar un processor que ya este creado en XNA y eso es todo ya que sobre el resto de la información no se requiere hacer ninguna modificación. 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;
}
}
El atributo inicial tiene la misma funcionalidad que en el importer y es proveer una pequeña descripción del ContentProcessor para que aparezca en el IDE de Visual Studio. Se llama al metodo context.Convert indicándole que debe usar el builting processor SpriteTextureProcessor, esto hará que la información inherente a una textura 2d sea procesada de la manera habitual. Finalmente se procede a crear el ContentTypeWriter.
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 información relacionada con el archivo ya previamente cargado en el BMP2Content, el objeto output recibido como parámetro posee métodos para escribir a nivel de tipos nativos y soporta una amplia gama de tipos incluidos en el XNA framework, el método GetRuntimeReader Almacena información que le indica el CPL en tiempo de ejecución que clase debe instanciar para poder convertir un archivo del CPL en el objeto que se desea, es decir indica cual implementación de ContentTypeReader debe utilizarse para tal fin.
Esta es la implementación de ContentTypeReader para leer en tiempo de ejecución lo que el ContentTypeWriter ha guardado en tiempo de compilación, el objeto input recibido como parámetro posee métodos para leer a nivel de tipos nativos y soporta una amplia gama de tipos incluidos en el XNA framework.
[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;
}
}
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 allí presionar el botón Add y seleccionar la dll creada.
Para que desde el juego se pueda cargar la información se debe incluir referencia por lo menos al ensamblado donde se compilo el ContentTypeReader. En adelante tan solo se accede al recurso a través de un content manager como se hace de manera habitual:
ContentManager content;
...
...
...
BMP2Texture myTexture = content.Load<BMP2Texture>
(@"myImage.bmp2");
Espero les sea de utilidad. Saludos.
Juan Carlos Ruiz Pacheco
Core Group BogotaDotNet
Ingeniero de Sistemas
OCA - Oracle Certified PL/SQL Developer Associate
OCP - Oracle Certified Forms Developer Professional
MCP - Microsoft Certified Professional
DCE - Desarrollador 5 Estrellas + Estrella dorada
Blogs Técnico Personal |