Saturday, October 18, 2008

XNA Part 10 - The Sprite Sheet


We have taken a little diversions with audio and collisions, but today I want to talk about a handy way to optimize your game. Up until now whenever we have wanted a sprite in our game we would just create an image and load it into our Content Pipeline and draw the whole thing. Now that is fine when you only have a couple...but say you have dozens of sprites. What is the best thing to do? Well here is one answer. A sprite sheet. this is basically a large image file that has your sprites separated into an organized grid. Here is an example of 4 sprites in a file, each in their own 100x100 spot in the file.

Now when we load in this sprite sheet we are not going to load it 4 times, that would be wasteful, instead we will load it once and pass it's reference to each game object that needs it. So I'm going to grab the GameObject class from my last project which keeps a Texture2D, a Position and the info needed for pixel based collisions. I can either extend that class or create a child class from it with new features. For now I will just extend the code for it. I am going to add a Rectangle object for the texture source. This will tell my object what part of the sprite image it needs to draw. I'll call it texSource. I will also add a member to the constructor to pass in a rectangle object to define the rectangle.

I will add a texture2D object to my main game class to hold the sprite sheet and in the LoadContent method, pull the sprite sheet into it, then pass that object to the new GameObjects along with the particular position of that GameObject's sprite in Rectangle format.

Now rather than creating 4 separate GameObjects, I have created an array of GameObjects


GameObject[] actors;

and after I load the sprite sheet in, I initialize the game objects.

actors = new GameObject[4];
actors[0] = new GameObject(spriteSheet, 0, 0, new Rectangle(0, 0, 100, 100));
actors[1] = new GameObject(spriteSheet, 0, 0, new Rectangle(0, 99 , 100, 100));
actors[2] = new GameObject(spriteSheet, 0, 0, new Rectangle(99, 0, 100, 100));
actors[3] = new GameObject(spriteSheet, 0, 0, new Rectangle(99, 99, 100, 100));

Now to siplify things when it comes time to draw these, I am going to make a GameObject draw itself (sort of). We will add a method to GameObject called Draw and it will take a SpriteBatch as a parameter.

public void Draw(SpriteBatch sb)
{
sb.Draw(sprite, position, texSource, Color.White);
}

So then in the Draw method of the Game class we can call

spriteBatch.Begin();
for (int i = 0; i < 4; i++)
{
actors[i].Draw(spriteBatch);
}
spriteBatch.End();

and handle the details of the Drawing call inside the object. Now of course, This will draw the 4 actors all stacked on top of each other, so for giggles, I'll add some Randomness to their initialization.

actors = new GameObject[4];

int x, y;
Random r = new Random();

x = r.Next(0, graphics.GraphicsDevice.Viewport.Width-100);
y = r.Next(0, graphics.GraphicsDevice.Viewport.Height - 100);
actors[0] = new GameObject(spriteSheet, x, y, new Rectangle(0, 0, 100, 100));

x = r.Next(0, graphics.GraphicsDevice.Viewport.Width - 100);
y = r.Next(0, graphics.GraphicsDevice.Viewport.Height - 100);
actors[1] = new GameObject(spriteSheet, x,y, new Rectangle(0, 99, 100, 100));

x = r.Next(0, graphics.GraphicsDevice.Viewport.Width - 100);
y = r.Next(0, graphics.GraphicsDevice.Viewport.Height - 100);
actors[2] = new GameObject(spriteSheet, x, y, new Rectangle(99, 0, 100, 100));

x = r.Next(0, graphics.GraphicsDevice.Viewport.Width - 100);
y = r.Next(0, graphics.GraphicsDevice.Viewport.Height - 100);

Now they should be in 4 different random locations.

From what I have read, the less Content loading you do the better, it is an "expensive" operation. So if you load and store 1 large image and reference parts of it, you are better off then loading many smaller images.

Project files for this post

1 comment:

Ben McFeeters said...

this is really useful, thanks!