Tag Archives: Pipeline

This is one of XNA’s most powerful features and one of its hardest to start working with.

So for example we are going to create a new class that will hold a texture and some additional details, such as the number of frames and the size of the frames, and could also include some data such as data read from the texture the end class will look like this:

namespace GameLib
{
	public class TextureWithData
	{
		public Rectangle FrameSize;
		public object ProcessedData;
		public Texture2D Texture;
		public Point FrameCount;

		public void ChangeFrame(int x, int y)
		{
			FrameSize = new Rectangle(x * FrameSize.Width, y * FrameSize.Height, FrameSize.Width, FrameSize.Height);
		}
	}
}

Now because of the texture we will have to have an “process” class and an in game class, above is the in game class, this is the process class:

namespace PipelineLib
{
	// Because of the Texture2D we end up needing these 2 classes that mostly match each other.
	// The attribute at the top tells us what class it will end up being.
	[ContentSerializerRuntimeType("GameLib.TextureWithData, GameLib")]
	public class TextureWithDataContent
	{
		public Rectangle FrameSize;
		public object ProcessedData;
		public Texture2DContent Texture;
		public Point FrameCount;
	}
}

Now note the ContentSerializerRuntimeType at the top of the class, it tells xna what in in game class will be. It has both the full path to the class (including the namespace) and the namespace as a second param. Also note that this function doesn’t have the helper function but does have the same named variables.

The next point is where these classes go:


Note that the 2 classes have gone into there own libraries, the reason for this is the gameLib is needed in the pipeline and in the game, so we have to have them in separate libs or we will get cyclic references.
On the subject of references, the pipelineLib includes a reference to the gameLib, and the content Project has a reference to the pipelineLib.
The Game has a reference to the gameLib.

Now for this example there are 2 ways for us to generate the data, one is in the importer and one is in the processor.

First of all I will show the importer:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;

using TImport = PipelineLib.TextureWithDataContent;
using System.Drawing;
using System.ComponentModel;

namespace PipelineLib.ProcessorAndImporter
{

	// This importer will create the texture2D but please note this won't create mipmaps, or compress the texture.
	// This means it does not need a processor.
	[ContentImporter(".png", DisplayName = "ImporterOnly Importer")]
	public class ImporterOnlyImporter : ContentImporter<TImport>
	{
		public override TImport Import(string filename, ContentImporterContext context)
		{
			// Here we are going to use a mix of Windows GDI/winforms programming and XNA
			// Please note that XNA has different Color to winforms Color

			// Load the Texture into a GDI bitmap
			Bitmap bitmap = (Bitmap)Image.FromFile(filename);


			if (bitmap != null)
			{
				// Create a Texture2D content
				Texture2DContent texture = new Texture2DContent();
				PixelBitmapContent<Microsoft.Xna.Framework.Color> newBitmap = new PixelBitmapContent<Microsoft.Xna.Framework.Color>(bitmap.Width, bitmap.Height);

				// Here is coping the data into the texture from the bitmap, so here would be a great place todo your checks and fill out the extra data
				for (int y = 0; y < bitmap.Height; y++)
				{
					for (int x = 0; x < bitmap.Width; x++)
					{
						System.Drawing.Color source = bitmap.GetPixel(x, y);
						Microsoft.Xna.Framework.Color dest = new Microsoft.Xna.Framework.Color(source.R, source.G, source.B, source.A);
						newBitmap.SetPixel(x, y, dest);
					}
				}

				texture.Mipmaps.Add(newBitmap);

				TImport data = new TImport();
				data.Texture = texture;
				data.FrameSize = new Microsoft.Xna.Framework.Rectangle(0,0,bitmap.Width / 4, bitmap.Height / 4);
				data.FrameCount = new Microsoft.Xna.Framework.Point(4, 4);
				return data;
			}

			throw new Exception("Failed to load the texture or process something");
		}
	}
}

Note in the code the hard coded frame size, this is because importers can not have additional properties.
once this is complied you can use it with the following settings:

So if we want to do a processor here is the same code for the processor:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

using TInput = Microsoft.Xna.Framework.Content.Pipeline.Graphics.Texture2DContent;
using TOutput = PipelineLib.TextureWithDataContent;
using System.ComponentModel;
using System.Diagnostics;

namespace PipelineLib.ProcessorOnly
{
	/// <summary>
	///
	/// This should be part of a Content Pipeline Extension Library project.
	/// 
	/// The Bonus of using the processor is that it can have addtional properties
	/// </summary>
	[ContentProcessor(DisplayName = "PipelineLib.ProcessorOnly.ProcessorOnlyProcessor")]
	public class ProcessorOnlyProcessor : ContentProcessor<TInput, TOutput>
	{
		[DisplayName("Sprite Width")]
		[DefaultValue(1)]
		[Description("The size of each sprite's width within the sprite map.")]
		public virtual int FrameWidth { get; set; }

		[DisplayName("Sprite Height")]
		[DefaultValue(1)]
		[Description("The size of each sprite's width within the sprite map.")]
		public virtual int FrameHeight { get; set; }

		public override TOutput Process(TInput input, ContentProcessorContext context)
		{
			if (FrameWidth == 0)
				FrameWidth = 1;

			if (FrameHeight == 0)
				FrameHeight = 1;

			// we get the Texture2DContent in, so we can get the colour data from the first mipmap on the texture
			// This is a little more complex as we don't know the source format of the texture.
			BitmapContent content = input.Mipmaps[0];

			// I'm assuming the pixel is in xna colour format
			if (content is PixelBitmapContent<Microsoft.Xna.Framework.Color>)
			{
				PixelBitmapContent<Microsoft.Xna.Framework.Color> bitmap = content as PixelBitmapContent<Microsoft.Xna.Framework.Color>;


				for (int y = 0; y < bitmap.Height; y++)
				{
					for (int x = 0; x < bitmap.Width; x++)
					{
						Color source = bitmap.GetPixel(x, y);

						// Do your tests here!
					}
				}

				TOutput data = new TOutput();
				data.Texture = input;
				data.FrameSize = new Microsoft.Xna.Framework.Rectangle(0, 0, bitmap.Width / FrameWidth, bitmap.Height / FrameHeight);
				data.FrameCount = new Microsoft.Xna.Framework.Point(FrameWidth, FrameHeight);
				return data;
			}
			else
			{
				throw new Exception("Texture is in the wrong format");
			}
		}
	}
}

This allows us to have custom properties:


Finally in game we can now load this data and show it:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using GameLib;

namespace InDepthPipeline
{
	/// <summary>
	/// This is the main type for your game
	/// </summary>
	public class Game1 : Microsoft.Xna.Framework.Game
	{
		GraphicsDeviceManager graphics;
		SpriteBatch spriteBatch;

		TextureWithData m_Data;
		Rectangle m_Position;
		Point m_Place = new Point(0, 0);

		float m_Timer;
		float m_WaitTime = 0.25f;

		public Game1()
		{
			graphics = new GraphicsDeviceManager(this);
			Content.RootDirectory = "Content";
		}

		protected override void Initialize()
		{
			base.Initialize();
		}

		protected override void LoadContent()
		{
			spriteBatch = new SpriteBatch(GraphicsDevice);

			m_Data = Content.Load<TextureWithData>("TestTexture");

			m_Position = m_Data.FrameSize;
			m_Position.X = 100;
			m_Position.Y = 100;
		}

		protected override void UnloadContent()
		{

		}

		protected override void Update(GameTime gameTime)
		{
			// Allows the game to exit
			if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
				this.Exit();


			m_Timer += (float)gameTime.ElapsedGameTime.TotalSeconds;

			if (m_Timer > m_WaitTime)
			{
				m_Timer -= m_WaitTime;

				m_Place.X++;

				if (m_Place.X >= m_Data.FrameCount.X)
				{
					m_Place.X = 0;
					m_Place.Y++;
				}

				if (m_Place.Y >= m_Data.FrameCount.Y)
				{
					m_Place.Y = 0;
					m_Place.X = 0;
				}

				m_Data.ChangeFrame(m_Place.X, m_Place.Y);
			}


			base.Update(gameTime);
		}

		/// <summary>
		/// This is called when the game should draw itself.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param>
		protected override void Draw(GameTime gameTime)
		{
			GraphicsDevice.Clear(Color.CornflowerBlue);

			spriteBatch.Begin();

			spriteBatch.Draw(m_Data.Texture, m_Position, m_Data.FrameSize, Color.White);

			spriteBatch.End();

			base.Draw(gameTime);
		}
	}
}

This loads the file, and then shows the texture in an animated format using the helper function.

Linked here is the source for the solution: solution


Tags: ,