Spritebatch (XNA Game Studio 4.0 Programming)

When you first create a new project, a SpriteBatch object is declared and instantiated in the LoadContent method.This is the main object used to render 2D graphics, and virtually all games (even full 3D games) need to render 2D graphics at some time.This object can be used to draw a single sprite like you just did, or it can draw many sprites with a wide variety of options that can be used to control exactly how you want your images drawn.

Drawing

First, let’s look at the Draw method, which is the one that actually got the picture on to the screen:

spriteBatch.Draw(texture, GraphicsDevice.Viewport.Bounds, Color.White);

This is only one overload of the Draw method (there are seven total), but it is one of the simplest.The first parameter is obvious—what texture do you want to draw? In the project earlier, you loaded your image into this texture, so that is what is rendered on the screen. This is one of the two parameters required and it is in every overload for the Draw method.

The other nonoptional parameter to Draw is the last one in the call, the color. This is the color your image is "tinted" with (in actuality, it is the color of each pixel in your image multiplied by the color specified, which is discussed later). Passing in Color.White, as done here, causes your image to appear with the same colors as the original image, whereas passing in Color.Black causes the image to be replaced by solid black. If you change this to Color.Red, and you will notice that the image is now tinted red. In many cases, you can simply leave this Color.White.


The middle parameter in the Draw call is the destination rectangle. As the name implies, it is the rectangle where you want the drawing to occur (hence, the destination). This rectangle is specified in screen coordinates where 0,0 is the upper left corner of the rendering area, which in this case is the window. When in full-screen mode, it is the upper left corner of the monitor as mentioned earlier.The destination rectangle is useful because if the image you are drawing isn’t the exact size of the rectangle you’re drawing, it automatically stretches or shrinks to fit exactly in that area.

In this case, the destination rectangle is calculated using a property of the Graphics Device object.The Graphics Device is the object that encapsulates the portions of the graphics card you can control, whereas the Viewport property contains information about where the device renders to on the screen.You used the Bounds property because it returns the rectangle that encompasses the entire rendering area, which is why the image you chose covers the entire window, regardless of what size it was originally when it was loaded.

Note

The Graphics Device object and its properties are discussed in depth in topic 3, "The

Game Object and the Default Game Loop."

Before we look at other methods used on the sprite batch, let’s look at the more overloads for drawing images on the screen. In your project, right-click the content project and add a reference to cat.jpg, which you can find in the downloadable examples.Any-one familiar with many of the samples that are available for Game Studio realizes that cats appear all over the samples. This one, however, is a different cat—it is my own. He is a little bit camera shy though, so please be gentle with him.

Change the code that loaded the previous image to load the cat image, as the following:

tmpD-11_thumb

This image is 256×256 pixels, and your window by default is 800×480 pixels. Running the project right now shows you a stretched version of the cat covering the entire rendering surface, much like you saw earlier. Now change the Draw method to the following:

spriteBatch.Draw(texture, Vector2.Zero, Color.White);

Notice that a different parameter of type Vector2 replaced the destination rectangle. Like the name implies, Vector2.Zero returns a vector with both the X and Y value as zero. The new parameter, called the position, tells the sprite batch where to begin drawing, so the upper left pixel of the image draws at the spot specified by this parameter (in this case 0,0, or the upper, left corner of the rendering area).

Note

The Vector2 class is a value type used to store two-dimensional coordinates, namely an X and Y value.

Using this overload and specifying a position means that no stretching of the image occurs; it renders whatever size the source image actually is. Run the application and notice that the cat is now in the upper, left corner of the window, but there are large areas of empty background everywhere else.To render the cat in the center of the window, modify the Draw call as follows:

tmpD-12_thumb

This combines information from the Viewport, along with information about the texture to properly position the image in the center of the window as in Figure 2.3. It still isn’t exciting though because it is a static image that doesn’t do anything.

The cat centered on the screen

Figure 2.3 The cat centered on the screen

Notice that the two overloads so far do remarkably similar things. They both draw an image to a particular place on the screen of a particular size.The only difference between them is the one with the destination rectangle requires you to specify a width, a height, and a position.As a matter of fact, these three lines produce the same results.

tmpD-14_thumb

Moving Things Around

You have much more control over the rendering than this, however. Change your Draw call to the following and run the application:

tmpD-15_thumb

Notice that the cat is drawn with the upper, left corner at 100,100 as you specified in the position vector, but it is now rotated slightly. This overload is more complex than the previous ones, so let’s look at the new parameters individually.

The first new parameter is the third one, which is listed as null.This is the source rectangle, and unlike the destination rectangle, it controls what you draw rather than where you draw (discussed more in depth later in the topic). Directly after the tint color is type float, which is the rotation angle you want to render at. This angle is specified in radians.

Note

To convert angles in degrees to radians, use the MathHelper.ToRadians method.

The next parameter is the origin (another vector), which we discuss in a moment. Directly after the origin is the scale parameter, which you use to uniformly scale the image.A scale of 1.0f is normal size, 2.0f is twice the size, and 0.5f is half the size.

Next up is the SpriteEffects enumeration, which has three options: None, FlipHorizontally, and FlipVertically. Each of these do exactly as the name implies. Passing in None does no special processing of the image, whereas passing in either of the other two flips the image before drawing it, either horizontally or vertically. For example, if you use SpriteEffects.FlipVertically, the cat is drawn upside down.

The final parameter is called the layer depth.This value should be between 0.0f and 1.0f with 1.0f is "on top" and 0.0f is "on bottom."This enables you to control how the images are sorted when you draw more than one, and it is ignored unless the sort mode is set to either BackToFront or FrontToBack.We talk about sort modes later in the topic.

This is the largest overload, and it has every feature you need in drawing a sprite. There is another overload that has the same number of parameters, but replaces the single scale parameter with a Vector2.This enables you to scale your image with different scaling values for the X and Y axis. So if you passed in a scale vector of 2.0f, 1.0f, the image would be twice as wide, but the same height.A scale vector of 0.5f, 2.0f causes the image to be half as wide, but twice the height.

Now it is time to go back to the mysterious origin parameter. The origin is the point around which the rotation occurs. In the previous example, you used Vector2.Zero, so your rotation is around the upper left corner of the image. If you use new Vector2(tex-ture.Width/2, texture.Height/2) instead, the image rotates around the center (see Figure 2.4).

Rotation origin

Figure 2.4 Rotation origin

Animation

There is only one parameter to the Draw overloads left to discuss, which is the source rec-tangle.As mentioned, the source rectangle lets you control the portion of the image you draw. Up until now, you drew the entire image, which is the default behavior if this parameter is not specified or if it is null.What if you had a single image that had multiple smaller images inside it, and you only wanted to draw a portion of it? That is what the source rectangle is for.The cat image is 256×256 pixels, but if you specified a source rectangle of (0,0,256,128), it renders the top portion of the cat and not the bottom portion.

One common usage of the source rectangle is for simple 2D animations.Your image contains several "frames" of animation that you swap through in succession to give the illusion of motion. Cartoons have been doing this for years, and it is a common technique.

In your content project, add another existing item and this time, include the spriteani-mation.png image from the downloadable examples. Like before, update the LoadContent method to change which texture you load.

tmpD-17_thumb

If you run the project now, you can see a weird image. It looks like there are several images instead ofjust a single one.That is exactly what the image is—a lot of smaller images stored in a larger one. In this case, there are ten separate 96×96 images stored within a single 960×96 image. If you change the Draw call in the Draw method as in the following, a single image is now drawn:

tmpD-18_thumb

The source rectangle of 0,0,96,96 tells the sprite batch to render only from the first smaller image within the file and to ignore the rest. By itself, though, it is still bland. Replace the Draw call with the following code to see the animation play out:

tmpD-19_thumb

Now you see a guy running in place at a position of 100,100! What you’ve done here is select which portion of the source image to draw from based on the current amount of time the game has run.You’ve taken the total number of seconds the game has run (this is a fractional value, so it records portions of a second, too), and multiplied it by 20, which forces the animation to run 20 times per second.You then pick the current frame by doing a modulus against the total number of frames and some quick math to pick the correct source rectangle based on that frame.

This technique is a common way 2D games render animations.

Controlling State

With all of the possible parameters of the Draw overloads discussed, now it is time to look at the multiple overloads of the Begin method.Although we use this method in every example up until this point, you used only the overload with no parameters, and that doesn’t require much explanation. Each example up until now has had only a single Draw call, but as the name sprite batch implies, you can draw many images at once, and you need some way to control how each image interacts with every other. This is what the Begin overloads do.

Before we get into that, however, add an existing item to your content project and include the image layers.jpg from the downloadable examples.This image has a few pictures and numbers on it to help better demonstrate the behavior of multiple Draw calls in a single batch. Update your load content method as always when you add new content to your project:

tmpD-20_thumb

Again, replace the contents of your Draw method with the following:

tmpD-21_thumb

This uses the largest overload of the Draw method and renders four different squares of the texture each at a different spot on the screen. Notice also the layer depth is passed so that each image is rendered at a different depth with the image containing the number 1 drawn at a depth of 0.1f and with the image containing the number 4 drawn at a depth of 0.4f. Also notice that this code renders the images in the order of 1,2,3,4. However, when you run the code, it is not drawn like that—it draws seemingly random, much like in Figure 2.5.

However, if you change the call to Begin with the following, it draws the images with the first image on the bottom and the last image on top, much like you see in Figure 2.6.

tmpD-22_thumb

This is because this overload of the Begin call includes the first parameter of type SpriteSortMode, which controls how multiple sprites sort within this batch before drawn on the screen.The options for this enumeration include FrontToBack as seen here, which renders images with the highest layer depth "on top" and the lowest layer depth "on bottom."The image with the 4 on it is rendered on top because it has the highest layer depth. If you instead switched this to BackToFront, the order reverses itself, and the lowest layer depth is "on top," and in this case, the image with the 1 is rendered on the top.

Another option for sorting is the default sorting option Texture. This causes the Draw calls to sort by the texture. In the previous example code, all of the Draw calls use the same texture, so there is no special sorting. This is the default because drawing a few images, switching textures, drawing a few more images, switching back to the original texture, and drawing a few more images can hinder performance.

Rendering multiple sprites with no control  

Figure 2.5 Rendering multiple sprites with no control

Rendering multiple sprites with layer control

Figure 2.6 Rendering multiple sprites with layer control

Other options for the sorting mode include Deferred. Each image sorts in the order it is used in the Draw calls.This means that all Draw calls should be "batched up" to make as few actual rendering calls to the hardware as possible. This is because an actual rendering call on the hardware can be expensive, and it is better to make fewer rendering calls that render lots of data instead of a large amount of render calls that render small amounts of data.All options in the sorting mode except for one also infers the behavior of Deferred.

The last sort mode is the one that doesn’t follow the Deferred behavior and is called Immediate.This tells the sprite batch to make a call to the rendering hardware for every call to Draw you make.

The other parameter to Begin shown previously and that is currently null is the BlendState you want to use for this sprite batch.Although we discuss the blend state (and the other states) in later topics, now is a good time to understand the basics of blending. As the name implies, blending controls how multiple images are combined. The default value (what is used if you pass in null) is BlendState.AlphaBlend. The alpha values control the transparency of objects drawn, so this blending state tells the sprite batch that for every pixel it draws, it should render the top-most pixel if the pixels are opaque (have no alpha value), or it should blend the top most pixel with the pixels "underneath" it.

In your content project, add another existing item,AlphaSprite.png.This item is a png (much like you did when you picked the animated sprite), because this file format can include alpha data. Instead of using the same texture object you’ve been using, though, add a new texture variable to your class so you can see how things blend together: Texture2D alphaTexture;

Of course, you need to load this texture, so add that to the LoadContent method:

tmpD-25_thumb

Then replace the Draw method with the following:

tmpD-26_thumb

This renders the full four-picture sprite you used previously along with a little character in the middle of it (see Figure 2.7).

Now change the blend state from alpha blending to BlendState.Opaque, which doesn’t attempt to blend the two images. Because of the Deferred sort mode, it renders them in the order they appear, so it renders the character last. Notice that you can’t see anything other than the character, and the portions that used to be transparent are now black. Change it again to BlendState.Additive and notice a weird combination of the two images that look too "bright." This is caused by "adding" the colors together.

Rendering multiple sprites with alpha blending

Figure 2.7 Rendering multiple sprites with alpha blending

There are other parameters to other overloads of the Begin method, including the depth stencil state, sampler states, rasterizer state, which effect is used, and a matrix for transformations.

Next post:

Previous post: