Sunday, June 5, 2011

XNA Game Design: Explaining the Spritebatch

Note: to make reading this easier, remember SpriteBatch refers to the SpriteBatch class and spriteBatch refers to our spriteBatch object located in our Game1 class.

In XNA programming, we use the spriteBatch object used to draw items on-screen. Commands to draw items require that they already been loaded into the game through a ContentManager object. This is a fair request.

By default, we already have a ContentManager and a SpriteBatch object ready for use in our Game1 class, respectively named Content and spriteBatch. These are good and ready to use right off the bat, but we are not really given any instructions on how to use them in the first place.

Think of the spriteBatch as a go-between that communicates with the system's GPU to draw our 2D images onscreen. We really only should need one, but because the SpriteBatch instance we have lives in the Game1 class, it would make sense to create another one to work with in our other classes.

Yeah, my art is crap. And in case you wondering, The Don is the GPU, and he'd probably be confined to a wheelchair and painting all over the place. Go figure.

Here are where the problems start coming up. In order to create an instance of a SpriteBatch, we need a graphicsdevice object. A graphicsdevice object contains most of the metadata used to interact with the system's GPU, and cannot be created from scratch. By default, Microsoft.Xna.Framework.Game contains the only initialized graphicsdevice we can use (which is not static), meaning all spritebatch objects must be spawned from the game1 class.

On top of that, all commands to draw sprites must be done between a spriteBatch.begin() and a spriteBatch.end() command. These commands wake up the spritebatch and give them the orders we want sent to the GPU. Because of this, all Draw commands for our entire game must originate from the Game1.Draw() method, and therefore our spriteBatch simply cannot just be made static and called with Game1.spriteBatch .

In a sense, the SpriteBatch is much like Sander Cohen's muse. Calling our cursor.Draw() command is easy only because the cursor is a constant part of any game. Instances are not made of it, and it's implementation is organized and straightforward. For every other aspect of a video game, this is not practical or efficient.

This post has mostly been me complaining about how awful the SpriteBatch is, but what can we do? We will engineer around this mess when the time comes, but for now we just want to display our freakin mouse cursor.


Assuming you did the homework (you are supposed to the homework!) you should have found out on your own how to import assets into the game. Now we are going to load them into our game. I'm using this image to represent my cursor, and I have named it Arrow.



Classy.


In the Game1.LoadContent() method, we will use the generic method 
Content
.Load<>() method to load our assets into our game. This method is generic, so we will be using it to import images, audio, and most other content into our game.


Just to refresh your memory, a generic method returns the type specified in carrot brackets. In this case, our mouse cursor image is represented with Texture2D. Our code will therefore look like this: 



// TODO: use this.Content to load your game content here
Cursor.Texture = this.Content.Load<Texture2D>("Graphics/Arrow");

 Not too shabby, no? Our Cursor.Texture no longer equals null, and we can now finalize our Cursor.Draw() method. Go into your Cursor class and add this sole line of code into the method (remember the Draw method takes in a spritebatch argument)


spriteBatch.Draw(Texture, Pos, Color.White);


You see why we took in the spriteBatch as a parameter now? Technically, the draw command is within the confines of .begin() and .end() because Cursor.Draw is called between those two lines in Game1. Passing the spriteBatch by parameter is actually the best solution to comply by it's picky standards. The reason we had to use a Vector2 for Pos is because the spriteBatch.Draw() method only takes in coordinate parameters as Vector2 values. This makes sense for everything... except mouse cursor coordinates. Ah well, no harm done in casting a few integers to floats.


Hey. Do yourself a favor and run your game. Correct any bugs or errors it throws you, and get a finished build to run.


A hells yeah. If your windows mouse shows up alongside the game mouse, make sure IsMouseVisible = false in your Game1 constructor.


Give yourself a pat on the back. With this simple layout and knowledge, changing the mouse cursor graphic should be easy. Just remember to make things static and to load all the cursor textures at the games start. They're small images, and we don't want the game to reload /  unload every time we move over an area that would change the icon


For this tremendous feat, no homework. Next week, we will be going into (simple API) audio.

No comments:

Post a Comment