Besides chasing and evading we can also have our enemies move in random motion. To
do this we need to add some more properties to the enemy class. We want to change
directions every so often (each enemy should have a different timer it uses to determine
when to switch directions). The direction should be random for each enemy (each enemy
will need a random velocity). The properties we need to add to our Enemy class are as
follows:
public float ChangeDirectionTimer;
public Vector3 RandomVelocity;
public int RandomSeconds;
We will create a method called MoveRandomly that will have the logic to move our
enemies in a random motion:
private void MoveRandomly(Enemy enemy, GameTime gameTime)
{
344 CHAPTER 16 AI Algorithms
if (enemy.ChangeDirectionTimer == 0)
enemy.ChangeDirectionTimer =
(float)gameTime.TotalGameTime.TotalMilliseconds;
//has the appropriate amount of time passed?
if (gameTime.TotalGameTime.TotalMilliseconds >
enemy.ChangeDirectionTimer + enemy.RandomSeconds * 1000)
{
enemy.RandomVelocity = Vector3.Zero;
enemy.RandomVelocity.X = rand.Next(-1, 2);
enemy.RandomVelocity.Y = rand.Next(-1, 2);
//restrict to 2D?
if (!restrictToXY)
enemy.RandomVelocity.Z = rand.Next(-1, 2);
enemy.ChangeDirectionTimer = 0;
}
enemy.Velocity = enemy.RandomVelocity;
enemy.Velocity *= moveUnit;
enemy.Color = Color.Orange;
}
Sometimes random movement can appear intelligent. Our method is utilizing the game
time to keep track of how long the enemy should keep moving. It is very similar to the
code we used for our fading method. This method checks to see if enough time has
passed to change directions. Once enough time has elapsed, the enemy velocity vector is
populated by random numbers ranging from -1 to 1. We need to add the following
random number variable to our code:
Random rand = new Random();
Random numbers are very important in AI work. We have taken care to not just have our
enemies be jittery by changing directions every frame. As a result we can have enemies
that look like they are searching or patrolling. None of the code we have seen has been
rocket science. All the algorithms are pretty simple but can provide great results.
We can replace our line in Update with:
MoveRandomly(enemy, gameTime);
Before we can run the code to see the results of our randomly moving enemies, we need
to initialize our properties. We do this inside of the enemy for loop in the Initialize
method:
Random Movement 345
16
enemies[i].ChangeDirectionTimer = 0;
enemies[i].RandomVelocity = Vector3.Left;
enemies[i].RandomSeconds = (i + 2) / 2;
As we run the code we can see that each enemy is just moving around randomly. They
are not moving in synchronization and they definitely appear random.
Jumat, 04 April 2008
Random Movement
Collisions
This section is going to be pretty detailed. It is something that is best read with a physics
textbook handy to get the full effect. It is not required though, and the actual code is
easier to understand than the math involved. Regardless, it is very beneficial to understand
the reason why this works so we can do further research to determine the best way
to model other important physics needed in our games.
Momentum
When the objects stay in motion, it is because of momentum. Objects in motion have
momentum. Momentum is used to measure our object’s mass and velocity. The formula
to calculate momentum is:
p = mv
p is our object’s momentum and we know that m is our object’s mass and v is the velocity.
The reason we know about mass is because of our force formula. We can substitute
our formula for acceleration in our formula for force:
F = ma = m v / t
Now we can multiply our change in time on both sides, which produces:
Ft = ma = m v
Collisions 295
14
Impulse
Ft is called an impulse. We can do some vector math and multiply our mass by our
change in velocity (the right side of our equation) and see that it can be represented by:
Ft = (mv)
Therefore, we know that our impulse is equal to the change in momentum, which can be
written as follows:
Ft = p
Conservation of Momentum
When objects collide their momentum changes. To be more precise, the magnitude of the
momentum remains the same just in the opposite direction. This is how we can model
our collision response. We can reflect our objects off of each other, knowing that whatever
their momentum was before they collided will remain, but their direction will be
reversed. We just threw two objects into the mix but have only been discussing momentum
on a single object. How does this change our momentum formula? Fortunately, it
does not. This is called the law of conservation of momentum and it means the total
momentum for the objects is constant and does not change. This is true because any
momentum changes are equal in magnitude and opposite in direction. This is expressed
with the following formula:
p1 + p2 = p1 + p2
Kinetic Energy
Now we can discuss Newton’s Third Law of Motion, which basically says that for every
action there is an equal and opposite reaction. Whatever momentum one object
decreases, the other object increases. As momentum is transferred from one object to
another there is another physical property that takes place—kinetic energy. Kinetic energy
is energy associated with moving objects. It is the amount of energy needed to make an
object that is sitting still move. It is also the amount of energy needed to make an object
moving stop and sit still. The formula for kinetic energy is:
Ek = 1⁄2 m v2
When a collision occurs and the amount of kinetic energy is unchanged it is considered
to be an elastic collision. When kinetic energy is lost it is considered to be an inelastic
collision. Objects that collide in the real world will deform and cause a loss of kinetic
energy. If the objects do not deform, no energy is lost.
Coefficient of Restitution
The coefficient of restitution is the measurement of how elastic or inelastic our collision
is based on the types of object that are colliding. The formula for coefficient of restitution
is:
296 CHAPTER 14 Physics Basics
e = (v2f - v1f) / (v1 - v2)
The coefficient of restitution models the velocity before and after a collision takes place
and the loss of kinetic energy happens. The typical value for e is between 0.0 and 1.0
inclusive. A value of 0.0 means the collision is inelastic and 1.0 means the collision is
elastic. The values in between will have a proportionate elastic collision effect. The
subscripts on the preceding formula are specifying which vectors we are using: 1 and 2 are
the two objects and f is the final velocity of that vector after the impact of the collision.
Conservation of Kinetic Energy
We need to discuss the conservation of kinetic energy, which says that the sum of the
kinetic energy of two objects before they collide will be equal to the sum of the kinetic
energy of the two objects after they collide. The formula for the conservation of kinetic
energy is:
Ek1 + Ek2 = Ek1 + Ek2
Broken down into its components, the formula becomes:
1⁄2 m1 v1
2 + 1⁄2 m2 v2
2 = 1⁄2 m1 v1f
2 + 1⁄2 m2 v2f
2
Solving Our Final Velocities
When we are modeling collisions we need to determine our final velocities (which is what
the f in the earlier formula represents). Before we can do that, we need to expand our
conservation of momentum formula from earlier. We will break down the momentum p
into its components as follows:
(m1 v1) + (m2 v2) = (m1 v1f) + (m2 v2f)
Now, we can solve for our final velocity by combining both of our earlier conservation
formulas with our coefficient of restitution formula. Our final velocities will equate to:
v1f = ( (e + 1) m2 v2 + v1(m1 - e m2) ) / (m1 + m2)
v2f = ( (e + 1) m1 v1 - v2(m1 - e m2) ) / (m1 + m2 )
This uses the conservation of kinetic energy formula with our conservation of momentum
formula, along with our coefficient of restitution, which allows us to solve our final velocity
for each object. That is all we need to start modeling realistic collisions.
www.cyrosella.com
Managing Game States
When we think about our overall game design we should consider the different states our
game will be in. When we made the SimpleGame in Chapter 11, we just used an enumerated
type that we could pick from to change our game state. This was sufficient for that
game, although the method we are going to discuss now would still fit it well without
adding a lot of complexity to the game code.
A common question from those starting out making games is this: “How should I structure
my game code?” This is a valid question with many different answers, each having
its own merit. As with every other aspect of programming, tasks can be in completed
many different ways. For this discussion, we use a structure that is not very hard to implement
and yields some very nice results in terms of flexibility.
Consider the following as a concrete example of the problem we are trying to solve:
1. Game is loaded and the start or title screen is displayed.
2. Player presses A or Start so the start or title screen is removed and the main menu
screen appears.
3. Player selects single player game, the main menu is removed, and a submenu is
displayed.
4. The game is a trial game, so a warning message is also displayed prompting the
gamer to purchase the game or continue with limited play. The message is displayed
on top of our submenu.
Managing Game States 309
15
5. The player accepts the message and the submenu no longer has the message
obstructing the view.
6. The player selects Quick Game from the Single Player menu. The Single Player menu
is removed and we start the level by displaying a start level loading screen.
7. When the level finishes loading, the start level screen is removed and we load up
our game’s scene.
8. The player pauses the game and we bring up a paused screen that overlays our
paused game play, which is a little blurred in the background.
Most of those state changes could be done with a simple enumerated type, but there are a
couple of situations where a simple enumerated type is not sufficient, such as when we
display our message in a modal dialog box that does not allow the gamer to continue
until he or she closes the box. However, we do not want to lose sight of the state we were
just in. We might use this same message dialog box at other times in our game for a tutorial
or hints, and so on. Another example is when the gamer pauses the game. We could
just switch our current state with our paused state and then return to our playing state
when the pause button is toggled by the gamer, but then we could not display our
current game’s scene in a blurred (or grayscaled) manner behind our pause screen.
We are going to create a method that will allow us to handle both of those situations and
give us the flexibility of having access to the different states from within each state. We
are going to create a game state manager class that will control a stack of our different
game states. Instead of just switching between states, we are going to implement a stack.
WHAT IS A STACK?
The typical real-world example computer scientists use to explain a stack is a stack of
plates. We push and pop items on and off of the stack. In our plates example, we
push a plate onto the top of the stack. We can then pop a plate off the top of a stack
(but we need to be careful so we don’t break it). It is a last in, first out (LIFO) method
of processing. We do need to make sure we do not try to pop off a plate if the stack is
empty, but that is easy enough to handle.
We are going to use a stack so we can easily handle situations like adding a pause menu
on top of our currently playing scene state. This way we can continue to draw our scene
even though we are paused. We do not want to update our scene, though, as our enemies
would keep coming for us, our timer would continue to count down, and so on. So when
we pause, we really do want to pause our game play, but we still want to draw our game
scene in its paused state. In fact, we might want to use some of the postprocessing techniques
we learned about to blur out, turn gray, or change our scene some other way when
we are in a paused state. By using a stack we can accomplish any of those things.
Using a stack for game state management is also beneficial when trying to handle things
like dialog boxes, as it effectively pauses the game to give players a hint, tell them the
demo’s time is up, and so on. Another benefit is multiple menus. We can push a menu
310 CHAPTER 15 Finite State Machines and Game State Management
state on top of our game play if the user backs out (or pauses) and then offer an options
menu and sound options or controller options under that. With a stack we have the flexibility
to leave our previous menus displayed, or have our screen replace them. With a
simple switch statement on an enumerated type this would not be possible.
Fortunately, with all of this flexibility we do not increase the complexity of our code very
much. The principle is an easy one to grasp. We are going to have a game state manager
that will manage the different states in our game. It will be implementing a stack to hold
our states. Each state will contain logic that determines what happens next. The states
themselves will drive the game flow.
Each state will inherit from a base game state abstract class. This abstract class will inherit
from the DrawableGameComponent class. Each game state that inherits from our abstract
state will be a game service. There are two reasons for this. One is that we want our state
objects to be singleton objects because we do not want more than one instance of our
state created. The second reason we are making it into a game service is because our game
state manager will need access to the states.
Our game state manager also inherits from GameComponent because it is a game service
that our states need a reference to. The game state manager will include an
OnStateChange event. Normally, other objects would register for an event like this.
Instead, we are going to expose the event handler in our game states and have our game
manager manage the event registration process for our game states. The exposed event
handler in our game states will be called StateChanged.
This StateChanged method in our game state class can be overridden but by default it will
simply check to see if the current state of the game is itself. If it is, it will set its Enable
and Visible properties to true; otherwise it will set them to false. So by default, when a
state is loaded, but not at the top of the stack, it will not update itself nor will it draw
itself. All active game states will have this event handler executed whenever our game
changes state. Each state could also search the stack to see if it contains some other state
and if so do some different processing. Because our StateChanged event handler will be
protected, the actual game states can implement any functionality they want to as the
game state changes. We can see that this system is pretty flexible.
Because we want our objects to use the singleton pattern and we want our states to be
game services so each state can access other states if needed, we can combine those
two requirements because the game service collection can only have one object in its
collection with a particular interface. Therefore each one of our states will need its
own interface, but we saw earlier that this can be a blank interface. In our situation, we
are going to have an IGameState interface, but then each one of our states will have its
own interface that implements the IGameState interface.
We need to be able to make comparisons between our game states. Mainly, we need to
make comparisons between our game manager’s current state and a particular game state.
Enumerated types obviously lend themselves to comparisons easily, but what about actual
game state objects? Fortunately, we can just as easily compare our object references and
this is why it is important that there is only one instance of the object—so our reference
is always the same. We will create a property called Value, which is of type GameState.
This property allows us to perform the comparisons that we need.
Force
Force changes the acceleration of an object. For example, when we hit a baseball the
acceleration of the ball is changed because we applied force (in the form of swinging a
bat) to the ball. If the ball was pitched to us, then it had a positive acceleration coming
toward us; when we hit it we changed the acceleration to the negative direction.
Force is a very important concept in physics. This is understandable because it is
constantly in effect. For example, even when we are sitting down there is a force of
gravity keeping us in the chair. The first example with the baseball is considered contact
force, as an object (the bat) made contact with the ball to make it change its acceleration.
Gravity is considered a field force (or a force-at-a-distance) because it does not have to
have contact with the object while applying force. Gravity causes items to be drawn to
the Earth’s core. This is why we can sit (and stand) and not float about. This is why we
have weight. It is all because of gravity. If there were an object on the floor it would be
sitting on the floor because of gravity. If we tried to pick it up, we would have to apply
enough force to counteract the force that gravity is applying to it. In outer space, objects
float around and are very light because regardless of their mass (assuming it is not as large
as a planet) they do not weigh much at all because there is no force pushing them down.
Mass is the amount of physical matter an object comprises. With the force of gravity our
mass has weight. The more mass an object has, the more it weighs on Earth. The larger
the mass, the more force is applied against that object to bring it toward the Earth’s core.
When we try to move an object, we have to apply enough force to accelerate the object.
CHAPTER 294 14 Physics Basics
If we do not apply as much force to lift the object upward as gravity is applying to the
object downward, the object will not move.
Newton’s Second Law of Motion describes the relationship between an object’s mass and
acceleration with the force applied to the object. The formula is:
F = ma
We know that acceleration is a vector, and force is a vector as well. This makes sense as we
think about the fact that when we hit that baseball it can go in multiple directions (pop
fly, foul ball, etc.). This is because we are creating acceleration when we apply force to the
object at different angles. Our mass is a scalar value (one dimension) and not a vector
(multiple dimensions). We could also write the preceding formula as:
a = F/m
In our previous code example we had an invisible force acting on our sphere that was
generating the acceleration of the sphere. Newton’s First Law of Motion basically states
that objects that are in motion stay in motion and objects that are sitting still stay still.
Our previous demo shows this as well, as if we do not press any keys to accelerate our
sphere it stays at rest. Once we do apply some force to the object, it accelerates and stays
in motion.
Adding Sounds
We can add in some sounds to help the game out some. We already have our sound
manager class from Chapter 7, “Sounds and Music,” so we do not need to write any new
code for that. Instead of going through the hassle of creating another XACT project, we
will simply copy the XACT project and sound files from our Chapter 7 Sound Demo
project folder. We should create a Sounds subfolder in our Content folder in our project
to store the files. We only need to have the XACT project included in the project with the
wave files just in the same directory, but not in the solution itself.
We need to declare our sound manager variable and we need to initialize it in our
constructor like all of our other game components:
sound = new SoundManager(this, “Chapter7”);
Components.Add(sound);
Now, we can create our playlist just like we did in the SoundDemo project by adding the
following code to our Initialize method:
string[] playList = { “Song1”, “Song2”, “Song3” };
sound.StartPlayList(playList);
Finally, we should play the explosion sound we set up back in Chapter 7. We should do
this right after we start our explosion when our enemy dies, so we need to add the following
statement at the end of the player.Attacking condition inside of the
CheckForCollisions method:
sound.Play(“explosion”);
Now, we have our songs playing and when we kick a robot we not only see an explosion
but we hear one, too.
Using Sprite Fonts
The XNA Framework 1.0 Refresh includes built-in font support. We can use any TrueType
font in our games. It also allows use of bitmaps, which can either be drawn by hand or be
generated with the Bitmap Font Make Utility (ttf2bmp). This utility can be found on XNA
Creators Club Online at
http://creators.xna.com/Headlines/utilities/archive/2007/04/26/Bitmap-Font-Maker-
Utility.aspx.
BE CAREFUL NOT TO DISTRIBUTE COPYRIGHTED FONTS
Most font files are licensed pieces of software. They are not typically sold. Most do
not allow distribution of fonts in any applications, even games. This even includes
distributing bitmap reproductions of the fonts and many of the fonts that are distributed
with Microsoft Windows. Be careful not to distribute any copyrighted fonts with
your game.
Importing TrueType Fonts
We want our library to handle fonts, so we will add a TrueType font to our XELibrary
project. We need to create a Fonts subfolder under our Content folder in our XELibrary
project. We can import TrueType fonts by following these steps:
1. Right-click the Fonts subfolder and click Add.
2. Click New Item and choose Sprite Font. We can name our file Arial.spritefont. This
will open the newly created .spritefont XML file.
188 CHAPTER 9 2D Basics
3. In the .spritefont XML file we can change the FontName element to the friendly
name of the font we want to load, Arial.
TIP
We can find the name of our font by looking in the Fonts folder in the Control Panel.
We can use any TrueType font but we cannot use bitmap (.fon) fonts.
4. (Optional) We can change the Size element to be the point size we want the font to
be. We can also scale the font, which we will see later.
5. (Optional) We can change the Spacing element, which specifies the number of
pixels there should be between each character in our font.
6. (Optional) We can change the Style element, which specifies how the font should
be styled. This value is case sensitive and can be the following values: Regular, Bold,
Italic, or Bold Italic.
7. The CharacterRegions element contains the start and end characters that should be
generated and available for drawing.
Now that we have added this spritefont file, when we compile our code the compiler will
generate the resource needed so we can utilize the font in our game. The XML file is
simply a description to the compiler as to which TrueType font to grab and which characters
to create in the .xnb file.
Creating Bitmap Fonts
Not only can we use TrueType font resources, we can make our own bitmap fonts to be
used. To do this we need to actually create the bitmap. We can either do it by hand or by
using the Bitmap Font Maker Utility mentioned at the beginning of this section. After
getting the base bitmap generated, we can modify it in our favorite paint program.
The background of the image must be a fully opaque, pure magenta color (R: 255, G: 0,
B: 255, A: 255). It needs to include an alpha channel that specifies which parts of
the characters are visible. Once the image is created or modified we can import the
image into our Fonts subfolder. XNA Game Studio Express will think it is just a normal
texture so we need to modify the Content Processor value in our properties panel to be
Font Texture.
Drawing 2D Text
Now that we have our font imported we can actually use it. We are not going to create a
demo for this. Instead, we are going to modify our ProgressBarDemo project and have it
display the text Loading … right above the progress bar. We need to add the following
private member fields:
private Vector2 loadingPosition = new Vector2(150, 120);
private SpriteFont font;
Using Sprite Fonts 189
9
We already added the font to our XELibrary project. We could have added it to our game,
but it is most likely we will want to print text out on the screen in the future so now we
have it loaded and don’t need to worry about it any more. However, we need to actually
load the font to our game. We do this just like any other content inside of our
LoadGraphicsContent method:
font = content.Load
Finally, inside of our Draw method, above our call to end our sprite batch, we need to add
the following statement:
spriteBatch.DrawString(font, “Loading ...”, loadingPosition, Color.White);
We can run our ProgressBarDemo and see the word Loading … displayed above our
progress bar. Things are shaping up nicely!
2D Basics
The XNA Framework not only provides easy ways for us
to utilize 3D objects, but it also provides excellent 2D
support. There are actually a couple of ways we can achieve
2D inside of XNA. There is true 2D, which is sprite manipulation.
We will discuss this kind of 2D. The other kind is
actually setting up a 3D environment, but locking the
camera so that it is always looking at the same angle and
cannot be moved—it is a 3D world with a stationary
camera.
Whereas 3D uses models to display our scene, 2D uses
images to create and animate our scene. The two dimensions
are x and y—there is no z in 2D. It is very common
to mix 2D and 3D into the same game. Scores, menus, and
timers are examples of things that are typically 2D in a 3D
world. Even if you are not interested in writing 2D games,
the next three chapters will prove beneficial, as there will
be things that can be incorporated into your 3D masterpieces.
Sprite Batches
First we need to understand the term sprite. A sprite is an
image. XNA represents sprites with a Texture2D object. We
load them just like we load textures for our 3D applications,
as they are both images and get processed the same
way. The difference is that in 3D we texture an object with
the image, whereas in 2D we draw the image (or part of the
image) on the screen. We saw an example of drawing part
of an image in the last chapter. We applied different parts
of an image to our skybox. The concept is the same, but
the process is a little different. Really, we just pass in whole
numbers instead of floats. Sprites are represented by x and
y and as such have int values. It is impossible to draw
something at pixel location 0.3, 0.3. If these types of values
172 CHAPTER 9 2D Basics
are passed in, XNA will do some anti-aliasing to make it appear that it is partially in the
pixel area but the actual image location is rounded to a whole number.
We discussed the 3D coordinate system earlier and now we discuss the 2D coordinate
system. The top left of the screen is the origin of the screen (0,0). The x axis runs horizontally
at the top of the screen and the y axis runs vertically down the left side of the
screen. It is identical to how a texture’s coordinate system is used. The difference is that
instead of running from 0.0 to 1.0 like in a texture, the values run from 0 to the width
and height of the screen. So if the resolution is 1024 x 768, the x values would run from
0,0 to 1024,0. The y values would run from 0,0 to 0,768. x runs from left to right and y
runs from top to bottom. The bottom right pixel of the screen would be 1024,768. This
can be seen in Figure 9.1.
x+
y+
(0,0) (1024,0)
(0,768) (1024,768)
FIGURE 9.1 The 2D coordinate system origin is the top left of the screen.
Although the origin of the screen is 0,0 the origin of a sprite may or may not be. The
default is 0,0 but the origin can be overridden if needed. When we draw sprites to the
screen we need to be aware of where the origin of the sprite is because when we pass in a
coordinate (via a Vector2 type), XNA will draw the object to that location starting with
the origin of the sprite. So if we drew the sprite at location 100,200 and we did not touch
the origin of the sprite, then the top left of the sprite would get drawn in 100,200. If we
wanted 100,200 to be the center then we would either need to define the origin of our
sprite or we would need to offset our position manually. When we rotate our sprites, they
will rotate around the origin of the sprite. When we need to rotate our sprites we will
typically set the origin of the sprite to be the center of the sprite. This way the sprite will
rotate around the center. Otherwise, the sprite would rotate around 0,0. The differences
between these two can be seen in Figure 9.2.
We can also scale a sprite. Scaling comes in three different flavors. We can either give a
number to scale the entire sprite by or we can just scale one portion of the sprite. The
final way we can scale is to specify a source rectangle and then specify a destination
rectangle. XNA can scale our source rectangle to fit into our destination rectangle.
Sprite batches are simply batches of sprites. When we draw a lot of sprites on the screen it
can put a load on the machine as we have to send a separate instruction to the graphics
card each time we draw something to the screen. With a sprite batch we have the ability
to batch up our draw functions to happen within the same settings and send one draw
call to the graphics card. When we create a SpriteBatch in our code we can pass in the
following values to its Begin method: SpriteBlendMode, SpriteSortMode, and
SaveStateMode.
Sprite Blend Modes
The blend mode can be set to AlphaBlend, Additive, and None. The default blend mode is
AlphaBlend. AlphaBlend does just that: It blends the sprites drawn (source) with the pixels
it is being drawn on (destination) based on the alpha value of both. This includes transparency
but can be used to create different effects depending on blend values we provide.
We will cover overriding the blending values in the next chapter. Additive is another
common blend mode that XNA provides support for with our sprite batches automatically.
Finally, None simply does not set any blending modes. It just overwrites the destination
area with the source sprite.
Alpha blending is used for normal translucent things like glass, windows, water, and
objects that fade in or out. Additive blending, on the other hand, is good to use when
creating glowing objects like explosions, sparks, and even magic effects. We will discuss
these two blending modes and even more that are not directly supported by sprite
batches in the next chapter.
Sprite Sort Modes
The sort mode determines how different sprites that are drawn actually get displayed on
the screen. In previous times, game developers had to take great care with how they
displayed images to the screen to make sure the background did overwrite their foreground
and that textures with alpha values rendered correctly. Although we still need to
take care as we approach this task, a lot of work has been done for us so that we need to
worry about it less. We still need to be aware of how XNA handles this for us so we can
use it effectively and keep our frame rates high.
Sprite Batches 173
9
FIGURE 9.2 The origin of the sprite determines how the sprite will be rotated.
The sort mode can be set to BackToFront, Deferred, FrontToBack, Immediate, and
Texture. The sort mode defaults to Deferred when we call Begin with no parameters.
Immediate is faster than Deferred. Immediate works a little different than the rest of the
sort modes. Immediate updates the graphics device settings immediately when the Begin
method is called. Then as sprites are drawn to the screen they immediately appear with
no sorting. This is the fastest method available, but it requires us to sort the images the
way we want them to be displayed. We draw the background images first and move up to
the foreground. An interesting thing we can do with immediate mode is change our
graphics device settings after Begin is called and before we actually draw our sprites to the
screen. We will discuss this in more detail in the next chapter.
The rest of the sort modes will update the graphics device settings when End is called on
the SpriteBatch instead of Begin. This means there is only one call out to the graphics
device. The Deferred sort mode is like Immediate in that it does not do any sorting, it just
defers talking to the graphics device until the end of the process.
The next two sort modes are BackToFront and FrontToBack. When we draw our sprites we
can set our layer depth of that sprite. That value is a float between 0.0 and 1.0. The sprites
will draw in this order for the two modes. BackToFront is typically used for transparent
sprites and FrontToBack is typically used for nontransparent (opaque) sprites.
Finally, we can pass in Texture as a sort mode to our sprite batch’s Begin method. The
Texture sort mode will check to see all of the draws that are required and will sort them
so that the graphics device does not need to have its texture changed for each draw if
possible. For example, if we have five sprites drawing and three of them use the same
texture (could be different parts of the same texture) then the graphics device is sent the
texture and then the three draws. Then the other two draws occur with their textures.
This can really help performance. However, we might need foreground and background
images in the same texture. Sorting by texture alone is going to give us good performance,
but we need to also sort by our layer depth. Fortunately, we can set up two sprite
batches at the same time (as long as we are not using Immediate sort mode).
So we could have our first batch sort by texture and our second batch sort by layer depth
(BackToFront or FrontToBack). We can then draw our items including the layer depth
value, and as long as we call End on our sprite batches in the appropriate order our screen
will display as we expect and the performance will be good as well. Because the End
method is what actually does the drawing, we need to make sure we call them in order
from background to foreground. It is best to draw our opaque sprites first in one batch
and then our transparent sprites after that.
Save State Modes
As we complete different tasks inside of our Draw method, we might need to change
settings on our graphics device. For example, we might want our depth buffer to be on
when we are drawing certain items and off when drawing other items. When the sprite
batch is executed (when Begin is called in Immediate sort mode, when End is called for all
others) the graphics device will have the properties in Table 9.1 modified. We might want
to save our properties so we can reset them when the sprite batch is done.
174 CHAPTER 9 2D Basics
SaveStateMode.SaveState does exactly that. The other option is SaveStateMode.None,
which is the default and does not save any state. It is quicker if we just reset the states
ourselves if needed, especially on the Xbox 360.
TABLE 9.1 The SpriteBatch.Begin Method Modifies These Graphics Device Properties
Property Value
RenderState.CullMode CullCounterClockwiseFace
RenderState.DepthBufferEnable False
RenderState.AlphaBlendEnable True
RenderState.AlphaTestEnable True
RenderState.AlphaBlendOperation Add
RenderState.SourceBlend SourceAlpha
RenderState.DestinationBlend InverseSourceAlpha
RenderState.SeparateAlphaBlendEnabled False
RenderState.AlphaFunction Greater
RenderState.ReferenceAlpha 0
SamplerStates[0].AddressU Clamp
SamplerStates[0].AddressV Clamp
SamplerStates[0].MagFilter Linear
SamplerStates[0].MinFilter Linear
SamplerStates[0].MipFilter Linear
SamplerStates[0].MipMapLevelOfDetailBias 0
SamplerStates[0].MaxMipLevel 0
When we mix 2D and 3D together we will definitely want to set the following properties
before drawing our 3D content:
GraphicsDevice.RenderState.DepthBufferEnable = true;
GraphicsDevice.RenderState.AlphaBlendEnable = false;
GraphicsDevice.RenderState.AlphaTestEnable = false;
GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;