Subscribe

RSS Feed (xml)

Powered By

Powered by Blogger

Google
 
xnahelp.blogspot.com

Jumat, 04 April 2008

Random Movement

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.

Read More......

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:
Ft = ma = m v
Collisions 295
14
Impulse
Ft 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:
Ft = (mv)
Therefore, we know that our impulse is equal to the change in momentum, which can be
written as follows:
Ft = 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

Read More......

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.

Read More......

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.

Read More......

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.

Read More......

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(@”Content\Fonts\Arial”);
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!

Read More......

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;

Read More......

Using the Skybox

We have gone through a lot of work, but we are almost done. All we need to do now is
actually use this Skybox object inside of our game. Let’s open the Game1.cs file and add
this private member field:
private Skybox skybox;
Now we need to add a skybox texture to our content folder. Let’s create another subfolder
under Content and call it Skyboxes. This is not required but it might be helpful to remind
us to change the processor type, which we will see how to do shortly. For now, we need
to actually add an image to this Skyboxes subfolder. We can find one on this book’s CD
under the Load3DObject\Content\Skyboxes\skybox.tga inside of the Chapter 8 source
code folder. Copy this texture and paste it into the Skyboxes folder through Solution
Explorer. Inside of the properties window we need to tell XNA Game Studio Express
165
8
LISTING 8.4 Continued
Using the Skybox
which importer and processor we want it to use when loading this content. We will leave
the importer alone as it is defaulted to Texture – XNA Framework. See the sidebar
“Creating a Custom Content Pipeline Importer.”
We will change the Content Processor property, but before we do we need to tell our
project that there is a custom processor of which it needs to be aware. We do this by
double-clicking the Properties tree node under our game projects. Inside of our project
properties we can open the last tab on the bottom: Content Pipeline. We can click Add
and browse to our pipeline project, then add the assembly in the bin folder.
We will add this exact same assembly (under the x86 subfolder) to our Xbox 360 game
project as well. This is because the assembly is only run on our PC when we are actually
building our project. However, we do need to make sure that our XELibrary_Xbox360
assembly has the same name as our Windows assembly. This is important because we
have told the SkyboxWriter where to find the Skybox type and the SkyboxReader object.
We told it XELibrary, not XELibrary_Xbox360.
Alternatively, we could have left the assembly names different and added a condition to
our GetRuntimeReader method in our SkyboxCompiler class. We could have returned a
different string depending on the target platform that is passed into that method.
Now that we have added our Content Pipeline extension to our game projects we can
select SkyboxProcessor from the list of content processors that are available in the property
window when we have our skybox texture selected.
CREATING A CUSTOM CONTENT PIPELINE IMPORTER
An example of setting up a custom importer is the following code:
[ContentImporterAttribute(“.ext”, DefaultProcessor = “SomeCustomProcessor”)]
public class CustomImporter : ContentImporter
{
public override CustomType Import(string filename,
ContentImporterContext context)
{
byte[] data = File.ReadAllBytes(filename);
return (new CustomType(data));
}
}
This code is a theoretical importer that would be inside of the pipeline project if we
needed to import a type of file that the Content Pipeline could not handle. We could
open up the file that was being loaded by the Content Pipeline and extract the data to
our CustomType that could handle the data. This CustomType would also be used as
the input inside of our processor object (where we used Texture2DContent). The
reason we did not make our own importer is because the XNA Framework’ s Content
Pipeline can handle texture files automatically.
166 CHAPTER 8 Extending the Content Pipeline
We are finally ready to run our game. We should see a star-filled background instead of
our typical CornflowerBlue background. We have successfully built a Content Pipeline
extension that takes a texture file and creates a skybox for us automatically. There is no
limit to the things we can accomplish because of the Content Pipeline’s opened architecture.
We could create a level editor and save it in any format and load it in at compile
time to our game. We could write an importer for our favorite 3D model file type and
work directly with the files. There are many opportunities to use the Content Pipeline.
Whenever we can take a hit up front at build time instead of runtime is always a good
thing.

Debugging the Content Pipeline Extension
Extending the pipeline is excellent, but what happens when something goes awry? We
cannot exactly step through the code because this is a component being loaded and run
by the IDE … or can we? Fortunately, we can. We discuss how to do that in this section.
If we have the SkyboxPipeline solution opened, we can close it and then add the project
to our Load3DDemo solution. This is not required to debug, but it can make it easier to
make sure the IDE is using the latest changes to the pipeline processor. Alternatively, we
could compile and change the stand-alone SkyboxPipeline solution and then compile the
Load3DDemo. Again, it really is just personal preference.
The goal is to make sure our SkyboxPipeline code gets compiled before our game code. To
do this we can either add the dependency directly to our game code or we can add it to
our XELibrary knowing that our game code already has a dependency on the XELibrary.
We can right-click our XELibrary projects (one at a time) and select Project Dependencies.
We can then add the check box to our SkyboxPipeline project. From now on, when we
compile, the solution will compile the SkyboxPipeline first, followed by the XELibrary
and finally compile our Load3DObject project.
Now that the SkyboxPipeline project is open, we can add the following line of code at the
top of the Process method inside of our SkyboxProcessor class:
System.Diagnostics.Debugger.Launch();
This will actually launch the debugger so we can step through the code to see what is
happening. If we run our program with this line of coded added, the CLR debugger will
get executed. We can then walk through the code like any other. We cannot edit and
continue just like we cannot do that on the Xbox 360. Regardless, being able to step
through the code at runtime is very beneficial. We can set break points wherever we need
to. This is an excellent way to visualize exactly which pieces of code are calling other
pieces and see the general flow of the Content Pipeline.

Read More......

Creating a Skybox

We want to add a skybox to our code. We are going to
create a project that will contain the content, content
processor, and content compiler. After creating this project
we will create another file inside of our XELibrary to read
the skybox data. Finally, we will create a demo that will
utilize the XELibrary’s Skybox Content Reader, which the
Content Manager uses to consume the skybox.
Before we actually create the project we should first
examine a skybox and its purpose in games. A skybox
keeps us from having to create complex geometry for
objects that are very far away. For example, we do not need
to create a sun or a moon or some distant city when we use
a skybox. We can create six textures that we can put into
our cube. Although there are skybox models we could use,
154 CHAPTER 8 Extending the Content Pipeline
for this chapter we are going to build our own skybox. It is simply a cube and we already
have the code in place to create a cube. We know how to create rectangles and we know
how to position them where we want them. We can create six rectangles that we can use
as our skybox. When each texture is applied to each side of the skybox we get an effect
that our world is much bigger than it is. Plus, it looks much better than the cornflower
blue backdrop we currently have!
Creating the Skybox Content Object
To start, let’s create a new Windows library project called SkyboxPipeline. There is no
need to create an Xbox 360 version of the project because this will only be run on the PC.
This SkyboxPipeline project will have three files. The first file is the SkyboxContent.cs
code file, shown in Listing 8.1.
LISTING 8.1 SkyboxContent.cs holds the design time class of our skybox
using System;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
namespace SkyboxPipeline
{
public class SkyboxContent
{
public ModelContent Model;
public Texture2DContent Texture;
}
}
The SkyboxContent object holds our skybox data at design time. We need to add a reference
to Microsoft.Xna.Framework.Content.Pipeline to our project to utilize the namespaces
needed.
Creating the Skybox Processor
The SkyboxContent object is utilized by the processor, shown in Listing 8.2.
LISTING 8.2 SkyboxProcessor.cs actually processes the data it gets as input from the
Content Pipeline
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
namespace SkyboxPipeline
{
[ContentProcessor]
class SkyboxProcessor : ContentProcessor
{
private int width = 1024;
private int height = 512;
private int cellSize = 256;
public override SkyboxContent Process(Texture2DContent input,
ContentProcessorContext context)
{
MeshBuilder builder = MeshBuilder.StartMesh(“XESkybox”);
CreatePositions(ref builder);
AddVerticesInformation(ref builder);
// Create the output object.
SkyboxContent skybox = new SkyboxContent();
// Finish making the mesh
MeshContent skyboxMesh = builder.FinishMesh();
//Compile the mesh we just built through the default ModelProcessor
skybox.Model = context.Convert(
skyboxMesh, “ModelProcessor”);
skybox.Texture = input;
return skybox;
}
private void CreatePositions(ref MeshBuilder builder)
{
Vector3 position;
//————-front plane
//top left
position = new Vector3(-1, 1, 1);
builder.CreatePosition(position); //0
//bottom right
155
8
LISTING 8.2 Continued
Creating a Skybox
position = new Vector3(1, -1, 1);
builder.CreatePosition(position); //1
//bottom left
position = new Vector3(-1, -1, 1);
builder.CreatePosition(position); //2
//top right
position = new Vector3(1, 1, 1);
builder.CreatePosition(position); //3
//————-back plane
//top left
position = new Vector3(-1, 1, -1); //4
builder.CreatePosition(position);
//bottom right
position = new Vector3(1, -1, -1); //5
builder.CreatePosition(position);
//bottom left
position = new Vector3(-1, -1, -1); //6
builder.CreatePosition(position);
//top right
position = new Vector3(1, 1, -1); //7
builder.CreatePosition(position);
}
private Vector2 UV(int u, int v, Vector2 cellIndex)
{
return(new Vector2((cellSize * (cellIndex.X + u) / width),
(cellSize * (cellIndex.Y + v) / height)));
}
private void AddVerticesInformation(ref MeshBuilder builder)
{
//texture locations:
//F,R,B,L
//U,D
//Front
Vector2 fi = new Vector2(0, 0); //cell 0, row 0
156 CHAPTER 8 Extending the Content Pipeline
LISTING 8.2 Continued
//Right
Vector2 ri = new Vector2(1, 0); //cell 1, row 0
//Back
Vector2 bi = new Vector2(2, 0); //cell 2, row 0
//Left
Vector2 li = new Vector2(3, 0); //cell 3, row 0
//Upward (Top)
Vector2 ui = new Vector2(0, 1); //cell 0, row 1
//Downward (Bottom)
Vector2 di = new Vector2(1, 1); //cell 1, row 1
int texCoordChannel = builder.CreateVertexChannel
(VertexChannelNames.TextureCoordinate(0));
//————front plane first column, first row
//bottom triangle of front plane
builder.SetVertexChannelData(texCoordChannel, UV(0, 0, fi));
builder.AddTriangleVertex(4); //-1,1,1
builder.SetVertexChannelData(texCoordChannel, UV(1, 1, fi));
builder.AddTriangleVertex(5); //1,-1,1
builder.SetVertexChannelData(texCoordChannel, UV(0, 1, fi));
builder.AddTriangleVertex(6); //-1,-1,1
//top triangle of front plane
builder.SetVertexChannelData(texCoordChannel, UV(0, 0, fi));
builder.AddTriangleVertex(4); //-1,1,1
builder.SetVertexChannelData(texCoordChannel, UV(1, 0, fi));
builder.AddTriangleVertex(7); //1,1,1
builder.SetVertexChannelData(texCoordChannel, UV(1, 1, fi));
builder.AddTriangleVertex(5); //1,-1,1
//————-right plane
builder.SetVertexChannelData(texCoordChannel, UV(1, 0, ri));
builder.AddTriangleVertex(3);
builder.SetVertexChannelData(texCoordChannel, UV(1, 1, ri));
builder.AddTriangleVertex(1);
builder.SetVertexChannelData(texCoordChannel, UV(0, 1, ri));
builder.AddTriangleVertex(5);
Creating a Skybox 157
8
LISTING 8.2 Continued
builder.SetVertexChannelData(texCoordChannel, UV(1, 0, ri));
builder.AddTriangleVertex(3);
builder.SetVertexChannelData(texCoordChannel, UV(0, 1, ri));
builder.AddTriangleVertex(5);
builder.SetVertexChannelData(texCoordChannel, UV(0, 0, ri));
builder.AddTriangleVertex(7);
//————-back pane //3rd column, first row
//bottom triangle of back plane
builder.SetVertexChannelData(texCoordChannel, UV(1, 1, bi)); //1,1
builder.AddTriangleVertex(2); //-1,-1,1
builder.SetVertexChannelData(texCoordChannel, UV(0, 1, bi)); //0,1
builder.AddTriangleVertex(1); //1,-1,1
builder.SetVertexChannelData(texCoordChannel, UV(1, 0, bi)); //1,0
builder.AddTriangleVertex(0); //-1,1,1
//top triangle of back plane
builder.SetVertexChannelData(texCoordChannel, UV(0, 1, bi)); //0,1
builder.AddTriangleVertex(1); //1,-1,1
builder.SetVertexChannelData(texCoordChannel, UV(0, 0, bi)); //0,0
builder.AddTriangleVertex(3); //1,1,1
builder.SetVertexChannelData(texCoordChannel, UV(1, 0, bi)); //1,0
builder.AddTriangleVertex(0); //-1,1,1
//————-left plane
builder.SetVertexChannelData(texCoordChannel, UV(1, 1, li));
builder.AddTriangleVertex(6);
builder.SetVertexChannelData(texCoordChannel, UV(0, 1, li));
builder.AddTriangleVertex(2);
builder.SetVertexChannelData(texCoordChannel, UV(0, 0, li));
builder.AddTriangleVertex(0);
builder.SetVertexChannelData(texCoordChannel, UV(1, 0, li));
builder.AddTriangleVertex(4);
builder.SetVertexChannelData(texCoordChannel, UV(1, 1, li));
builder.AddTriangleVertex(6);
builder.SetVertexChannelData(texCoordChannel, UV(0, 0, li));
builder.AddTriangleVertex(0);
//————upward (top) plane
builder.SetVertexChannelData(texCoordChannel, UV(1, 0, ui));
builder.AddTriangleVertex(3);
builder.SetVertexChannelData(texCoordChannel, UV(0, 1, ui));
builder.AddTriangleVertex(4);
158 CHAPTER 8 Extending the Content Pipeline
LISTING 8.2 Continued
builder.SetVertexChannelData(texCoordChannel, UV(0, 0, ui));
builder.AddTriangleVertex(0);
builder.SetVertexChannelData(texCoordChannel, UV(1, 0, ui));
builder.AddTriangleVertex(3);
builder.SetVertexChannelData(texCoordChannel, UV(1, 1, ui));
builder.AddTriangleVertex(7);
builder.SetVertexChannelData(texCoordChannel, UV(0, 1, ui));
builder.AddTriangleVertex(4);
//————downward (bottom) plane
builder.SetVertexChannelData(texCoordChannel, UV(1, 0, di));
builder.AddTriangleVertex(2);
builder.SetVertexChannelData(texCoordChannel, UV(1, 1, di));
builder.AddTriangleVertex(6);
builder.SetVertexChannelData(texCoordChannel, UV(0, 0, di));
builder.AddTriangleVertex(1);
builder.SetVertexChannelData(texCoordChannel, UV(1, 1, di));
builder.AddTriangleVertex(6);
builder.SetVertexChannelData(texCoordChannel, UV(0, 1, di));
builder.AddTriangleVertex(5);
builder.SetVertexChannelData(texCoordChannel, UV(0, 0, di));
builder.AddTriangleVertex(1);
}
}
}
The SkyboxProcessor contains a lot of code, but the vast majority of it is actually building
and texturing our skybox. We can go ahead and create this file in our pipeline project
now.
To begin we used the [ContentProcessor] attribute for our class so the Content Pipeline
could determine which class to call when it needed to process a resource with our type.
We inherit from the ContentProcessor class stating that we are going to be taking a
Texture2D as input and outputting our skybox content type. In the Process method we
take in two parameters: input and context. To create our skybox we are going to pass in a
single texture in our game projects. The processor creates a new MeshBuilder object,
which is a helper class that allows us to quickly create a mesh with vertices in any order
we wish and then apply different vertex information for those vertices that can contain
things like texture coordinates, normals, colors, and so on. For our purposes we will be
storing the texture. We create our actual eight vertices of our skybox cube in the
CreatePositions method. We are simply passing a vertex position into the
CreatePosition method of the MeshBuilder method.
Creating a Skybox 159
8
LISTING 8.2 Continued
Next up is our call to AddVerticesInformation. This method contains the bulk of the
code but it is not doing anything fancy. It is simply creating triangles in the mesh by
passing the vertices index values to the AddTriangleVertex method of the MeshBuilder
object. These vertices need to be called in the right order. We can think of this as
building the indices of the mesh. The idea is that we created our unique vertices (in
CreatePositions) and although we could have stored the value CreatePosition returned
to us, we know that it will return us the next number starting with 0. Instead of using up
memory for the index being passed back, we just made a note in the comment next to
that vertex so we could build our triangles.
Before we actually add a vertex to a triangle of our mesh, we pass in our vertex channel
information. We created a vertex channel before we started creating triangles with the
following code:
int texCoordChannel = builder.CreateVertexChannel
(VertexChannelNames.TextureCoordinate(0));
We can have multiple vertex channels. Although we are only going to store texture coordinates,
we could also store normals, binormals, tangents, weights, and colors. Because we
could store all of these different pieces of information we need to tell the vertex channel
which type of data we are storing. We then store an index to that particular channel.
Once we have that channel index we can call the SetVertexChannelData method for each
triangle vertex we add. In fact, set the channel data for the builder before adding the
vertex. If we had more than one vertex channel to apply to a vertex, we would call all of
them in succession before finally calling the AddTriangleVertex method. The following
code shows the order in which this needs to take place:
builder.SetVertexChannelData(texCoordChannel, UV(0, 0, fi));
builder.AddTriangleVertex(4);
SetVertexChannelData takes in the vertex channel ID followed by the appropriate data
for that channel. When we set up the vertex channel to handle texture coordinates we
did so by passing the generic Vector2 because texture coordinates have an x and a y
component. This means that the SetVertexChannelData for our texture coordinate
channel is expecting a type of Vector2.
For this texture mapping code to make sense, we need to discuss how the texture asset we
are going to pass into our demo or game needs to be laid out. Instead of requiring six
different textures to create a skybox, we are requiring only one with each plane of the
cube to have a specific location inside of the texture. The texture size is 1024 x 512 to
keep with the power-of-two restriction most graphic cards make us live by. We put four
textures on the top row and two textures on the bottom row. The top row will have the
cube faces Front, Right, Back, and Left in that order. The bottom row will have Up (Top)
and Down (Bottom). If we have skyboxes in other formats we can use a paint program to
get them in this format. We can also use tools to generate skybox images and output
them into this format or one we can easily work with. The great thing about this being an
extension of the Content Pipeline is that we have free reign over how we want to read in
160 CHAPTER 8 Extending the Content Pipeline
data and create content that our games can easily use. If we stick with the current single
texture it leaves part of the texture unused. We could utilize these two spots for something
else. For example, we could create one or two cloud layers to our skybox, so instead
of just rendering a cube, it would render a cube with two additional layers that could
prove to be a nice effect. We could use it for terrain generation by reading in the values
from a gray-scaled image in one of those spots to create a nice ground layout. We do not
discuss terrain generation in this book, but there are many excellent articles on the Web
about generating terrains.
Now that we know how the texture is laid out we can discuss some of the details of the
code that is applying the texture to the different panels of the cube. In the following code
we declared a variable to hold our index of the right panel in the texture:
//Right
Vector2 ri = new Vector2(1, 0); //cell 1, row 0
We are storing 1,0 in a vector signifying that the right panel’s portion of the large texture
is in the first cell in row zero (this is zero based). In Chapter 4, “Creating 3D Objects,”
we discussed how to texture our rectangle (quad) by applying different u and v coordinates
to the different vertices of our rectangle. We are using the exact same concept here.
The only difference is that we have to take into account the fact that we are extracting
multiple textures from our one texture. For example, to texture the right-side panel of
our skybox using just one texture we could simply tell the top left vertex to use texture
coordinates 0,0 and the bottom right vertex to use texture coordinate 1,1. However, our
right-side panel’s texture is not the entire texture we have in memory; instead it is from
pixels 256,0 to 512,256. We can see this in Figure 8.1 where the right panel texture is not
grayed out.
Creating a Skybox 161
8
To handle the offset issue we created a UV method that takes in the typical 0 and 1 along
with index cell from which we need to get our updated u and v coordinates. The UV
method that calculates our u and v values is as follows:
private Vector2 UV(int u, int v, Vector2 cellIndex)
{
return(new Vector2((cellSize * (cellIndex.X + u) / width),
(cellSize * (cellIndex.Y + v) / height)));
}
This method simply takes in the u and v coordinates we would normally map on a full
texture along with the cell index we want to access in the texture and it returns the calculated
u and v coordinates. The cellSize, width, and height are private member fields. We
take the size of the cell, 256, and multiply that by the sum of our x value of our cell
index and the u value passed in. We take that value and divide it by width to come up
with the correct u position of the large texture. We do the same thing to get our v value.
We pass those actual values to SetVertexChannelData so it will associate the right texture
coordinates with that vertex.
After actually creating the Skybox vertices and setting up all of the triangles needed and
applying our texture coordinates, we can finally save the mesh. We do this by calling the
FinishMesh method on our MeshBuilder object, which returns a MeshContent type back to
us. This is convenient as that is the type of object we need to pass to the default
ModelProcessor to process our mesh (just as if we loaded a .X file through the Content
Pipeline). This is done with the following code:
MeshContent skyboxMesh = builder.FinishMesh();
skybox.Model = context.Convert(
skyboxMesh, “ModelProcessor”);
After setting our texture to the texture (our input) that was actually loaded to start this
process, we return the skybox content and the compiler gets launched. We discuss the
compiler in the next section.
Creating the Skybox Compiler
This brings us to our third and final file for our pipeline project. We need to create
another code file with the name SkyboxCompiler.cs. The code for this file is found in
Listing 8.3.
LISTING 8.3 SkyboxCompiler.cs compiles and writes out the content it is passed from the
processor
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
162 CHAPTER 8 Extending the Content Pipeline
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;
namespace SkyboxPipeline
{
[ContentTypeWriter]
public class SkyboxWriter : ContentTypeWriter
{
protected override void Write(ContentWriter output, SkyboxContent value)
{
output.WriteObject(value.Model);
output.WriteObject(value.Texture);
}
public override string GetRuntimeType(TargetPlatform targetPlatform)
{
return “XELibrary.Skybox, “ +
“XELibrary, Version=1.0.0.0, Culture=neutral”;
}
public override string GetRuntimeReader(TargetPlatform targetPlatform)
{
return “XELibrary.SkyboxReader, “ +
“XELibrary, Version=1.0.0.0, Culture=neutral”;
}
}
}
We start off this class much like the last in that we associate an attribute with it. This
time we need to use the [ContentTypeWriter] attribute as it tells the Content Pipeline
this is the compiler or writer class. We inherit from the ContentTypeWriter with the
generic type of SkyboxContent (which we created in the first file of this project). This way
when the Content Pipeline gets the skybox content back from the processor it knows
where to send the data to be compiled.
We override the Write method and save our skybox as an .xnb file. The base class does all
of the heavy lifting and all we need to do is write our object out. The next method,
GetRuntimeType, tells the Content Pipeline the actual type of the skybox data that will be
loaded at runtime. The last method, GetRuntimeReader, tells the Content Pipeline which
object will actually be reading in and processing the .xnb data. The contents of these two
methods are returning different classes inside of the same assembly. They do not need to
reside in the same assembly, but it definitely made sense in this case. We store the
runtime type and runtime reader in a separate project. We do not add them to the
pipeline project because the pipeline project is Windows dependent and our actual
skybox type and reader object needs to be platform independent. We are going to set
Creating a Skybox 163
8
Creating the Skybox Reader
Let’s copy and open our Load3DObject project from Chapter 6, “Loading and Texturing
3D Objects.” Our XELibrary should already be inside of this project and we can add a
SkyboxReader.cs file to our XELibrary projects. This file will contain both our Skybox type
and our SkyboxReader type. We could have created separate files if we desired. If we had
them in different assemblies, however, we would need to update our GetRunttimeType
and GetRuntimeReader methods in our content writer. The code contained in
SkyboxReader.cs can be found in Listing 8.4.
LISTING 8.4 SkyboxReader.cs inside of our XELibrary allows for our games to read the
compiled .xnb files generated by the Content Pipeline
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
namespace XELibrary
{
public class SkyboxReader : ContentTypeReader
{
protected override Skybox Read(ContentReader input, Skybox existingInstance)
{
return new Skybox(input);
}
}
public class Skybox
{
private Model skyboxModel;
private Texture2D skyboxTexture;
internal Skybox(ContentReader input)
{
skyboxModel = input.ReadObject();
skyboxTexture = input.ReadObject();
}
public void Draw(Matrix view, Matrix projection, Matrix world)
{
foreach (ModelMesh mesh in skyboxModel.Meshes)
{
foreach (BasicEffect be in mesh.Effects)
{
be.Projection = projection;
be.View = view;
be.World = world;
be.Texture = skyboxTexture;
be.TextureEnabled = true;
}
mesh.Draw(SaveStateMode.SaveState);
}
}
}
}
Our SkyboxReader class is pretty small. It derives from the ContentTypeReader and uses a
Skybox type that we will see in a moment. We override the Read method of this class,
which gets passed in the skybox data as input as well as an existing instance of the object
that we could write into if needed. We take the input and actually create an instance to
our Skybox object by calling the internal constructor.
Inside of the Skybox class we take the input that was just passed to us and store the model
embedded inside. We expose a Draw method that takes in view, projection, and world
matrices as parameters. We then treat the model as if we loaded it from the pipeline
(because we did) and set the basic effect on each mesh inside of the model to use the
projection, view, and world matrices passed to the object. Finally, we actually draw the
object onto the screen.

Read More......

Creating a Sound Demo

Now we need to add in another Windows game project to our XELibrary solution. We can
call this new project SoundDemo. We can also set up our solution for our Xbox 360
project if we want to test it on the console. Now we need to make sure our game is referencing
the XELibrary project.
Once we have our XELibrary referenced correctly, we can start writing code to test out our
new sound class (and updated input class). We need to use the library’s namespace at the
top of our game class as follows:
using XELibrary;
We should also add a folder called Content with a Sounds subfolder to our solution. We
can then paste our XACT project file into our Sounds folder. The wave files should be put
in the folder, but do not need to be included in the project. When we compile our code
later, the Content Pipeline will find all of the waves from the wave bank and wrap them
into a wave bank .xwb file. It also creates a sound bank .xsb file while the audio engine is
stored in Chapter7.xgs (as that is what we had as our XACT project name).
We will now add in our InputHandler game component so we can kick off sound events
based on our input. We need to declare our private member field to hold the component
as well as adding it to our game’s components collection:
Creating a Sound Demo 147
7
private InputHandler input;
private SoundManager sound;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
input = new InputHandler(this);
Components.Add(input);
sound = new SoundManager(this, “Chapter7”);
Components.Add(sound);
}
We passed in “Chapter7” to our constructor because that is what we called our XACT
project. The next thing we need to do is set up our playlist. We can do this inside of our
Initialize method because we added the sound component in our constructor:
string[] playList = { “Song1”, “Song2”, “Song3” };
sound.StartPlayList(playList);
The code tells our sound manager we will be playing three different songs. The library
will keep checking to see if they are playing. If not, it will automatically play the next
one, looping back to the beginning song when it reaches the end of the list.
Now we can actually populate our Update method to check for our input to play all of the
sounds and songs we set up in XACT. We need to add the following code to our Update
method:
if (input.KeyboardState.WasKeyPressed(Keys.D1) ||
input.ButtonHandler.WasButtonPressed(0, InputHandler.ButtonType.A))
sound.Play(“gunshot”);
if (input.KeyboardState.WasKeyPressed(Keys.D2) ||
input.ButtonHandler.WasButtonPressed(0, InputHandler.ButtonType.B))
sound.Play(“hit”);
if (input.KeyboardState.WasKeyPressed(Keys.D3) ||
input.ButtonHandler.WasButtonPressed(0,
InputHandler.ButtonType.LeftShoulder))
sound.Play(“attention”);
if (input.KeyboardState.WasKeyPressed(Keys.D4) ||
input.ButtonHandler.WasButtonPressed(0,
InputHandler.ButtonType.LeftStick))
sound.Play(“explosion”);
if (input.KeyboardState.WasKeyPressed(Keys.D5) ||
input.ButtonHandler.WasButtonPressed(0,
InputHandler.ButtonType.RightShoulder))
148 CHAPTER 7 Sounds and Music
sound.Play(“bullet”);
if (input.KeyboardState.WasKeyPressed(Keys.D6) ||
input.ButtonHandler.WasButtonPressed(0,
InputHandler.ButtonType.RightStick))
sound.Play(“crash”);
if (input.KeyboardState.WasKeyPressed(Keys.D7) ||
input.ButtonHandler.WasButtonPressed(0, InputHandler.ButtonType.X))
sound.Play(“complex”);
if (input.KeyboardState.WasKeyPressed(Keys.D8) ||
input.ButtonHandler.WasButtonPressed(0, InputHandler.ButtonType.Y))
sound.Toggle(“CoolLoop”);
if (input.KeyboardState.WasKeyPressed(Keys.D9) ||
input.ButtonHandler.WasButtonPressed(0,
InputHandler.ButtonType.LeftShoulder))
sound.Toggle(“CoolLoop 2”);
if (input.KeyboardState.WasKeyPressed(Keys.P) ||
input.ButtonHandler.WasButtonPressed(0, InputHandler.ButtonType.Start))
{
sound.Toggle(“CoolLoop”);
}
if (input.KeyboardState.WasKeyPressed(Keys.S) ||
(input.GamePads[0].Triggers.Right > 0))
sound.StopPlayList();
We are simply checking to see if different keys were pressed or different buttons were
pushed. Based on those results we play different cues that we set up in the XACT project.
A good exercise for us would be to run the demo and reread the section of this chapter
where we set up all of these sounds and see if they do what we expect when we press the
appropriate keys or buttons. In particular we can press the B button or number 2 key
repeatedly and see that the “hit” cue is queuing up as we told it to limit itself to only
playing once and to queue failed requests. We can also click down on our right thumb
stick or press the number 6 key to hear our crash. If the playlist is hindering our hearing
of the sounds we can stop it by pressing the S key or pushing on our right trigger.
The final piece of code for our sound demo is where we can set a global variable for the
RPC we set up as well as the volume of our default category cues. To start, we need to add
two more private member fields:
private float currentVolume = 0.5f;
private float value = 0;
Now we can finish up our Update method with the following code:
if (input.KeyboardState.IsHoldingKey(Keys.Up) ||
input.GamePads[0].DPad.Up == ButtonState.Pressed)
Creating a Sound Demo 149
7

Now we need to add in another Windows game project to our XELibrary solution. We can
call this new project SoundDemo. We can also set up our solution for our Xbox 360
project if we want to test it on the console. Now we need to make sure our game is referencing
the XELibrary project.
Once we have our XELibrary referenced correctly, we can start writing code to test out our
new sound class (and updated input class). We need to use the library’s namespace at the
top of our game class as follows:
using XELibrary;
We should also add a folder called Content with a Sounds subfolder to our solution. We
can then paste our XACT project file into our Sounds folder. The wave files should be put
in the folder, but do not need to be included in the project. When we compile our code
later, the Content Pipeline will find all of the waves from the wave bank and wrap them
into a wave bank .xwb file. It also creates a sound bank .xsb file while the audio engine is
stored in Chapter7.xgs (as that is what we had as our XACT project name).
We will now add in our InputHandler game component so we can kick off sound events
based on our input. We need to declare our private member field to hold the component
as well as adding it to our game’s components collection:
Creating a Sound Demo 147
7
private InputHandler input;
private SoundManager sound;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
input = new InputHandler(this);
Components.Add(input);
sound = new SoundManager(this, “Chapter7”);
Components.Add(sound);
}
We passed in “Chapter7” to our constructor because that is what we called our XACT
project. The next thing we need to do is set up our playlist. We can do this inside of our
Initialize method because we added the sound component in our constructor:
string[] playList = { “Song1”, “Song2”, “Song3” };
sound.StartPlayList(playList);
The code tells our sound manager we will be playing three different songs. The library
will keep checking to see if they are playing. If not, it will automatically play the next
one, looping back to the beginning song when it reaches the end of the list.
Now we can actually populate our Update method to check for our input to play all of the
sounds and songs we set up in XACT. We need to add the following code to our Update
method:
if (input.KeyboardState.WasKeyPressed(Keys.D1) ||
input.ButtonHandler.WasButtonPressed(0, InputHandler.ButtonType.A))
sound.Play(“gunshot”);
if (input.KeyboardState.WasKeyPressed(Keys.D2) ||
input.ButtonHandler.WasButtonPressed(0, InputHandler.ButtonType.B))
sound.Play(“hit”);
if (input.KeyboardState.WasKeyPressed(Keys.D3) ||
input.ButtonHandler.WasButtonPressed(0,
InputHandler.ButtonType.LeftShoulder))
sound.Play(“attention”);
if (input.KeyboardState.WasKeyPressed(Keys.D4) ||
input.ButtonHandler.WasButtonPressed(0,
InputHandler.ButtonType.LeftStick))
sound.Play(“explosion”);
if (input.KeyboardState.WasKeyPressed(Keys.D5) ||
input.ButtonHandler.WasButtonPressed(0,
InputHandler.ButtonType.RightShoulder))
148 CHAPTER 7 Sounds and Music
sound.Play(“bullet”);
if (input.KeyboardState.WasKeyPressed(Keys.D6) ||
input.ButtonHandler.WasButtonPressed(0,
InputHandler.ButtonType.RightStick))
sound.Play(“crash”);
if (input.KeyboardState.WasKeyPressed(Keys.D7) ||
input.ButtonHandler.WasButtonPressed(0, InputHandler.ButtonType.X))
sound.Play(“complex”);
if (input.KeyboardState.WasKeyPressed(Keys.D8) ||
input.ButtonHandler.WasButtonPressed(0, InputHandler.ButtonType.Y))
sound.Toggle(“CoolLoop”);
if (input.KeyboardState.WasKeyPressed(Keys.D9) ||
input.ButtonHandler.WasButtonPressed(0,
InputHandler.ButtonType.LeftShoulder))
sound.Toggle(“CoolLoop 2”);
if (input.KeyboardState.WasKeyPressed(Keys.P) ||
input.ButtonHandler.WasButtonPressed(0, InputHandler.ButtonType.Start))
{
sound.Toggle(“CoolLoop”);
}
if (input.KeyboardState.WasKeyPressed(Keys.S) ||
(input.GamePads[0].Triggers.Right > 0))
sound.StopPlayList();
We are simply checking to see if different keys were pressed or different buttons were
pushed. Based on those results we play different cues that we set up in the XACT project.
A good exercise for us would be to run the demo and reread the section of this chapter
where we set up all of these sounds and see if they do what we expect when we press the
appropriate keys or buttons. In particular we can press the B button or number 2 key
repeatedly and see that the “hit” cue is queuing up as we told it to limit itself to only
playing once and to queue failed requests. We can also click down on our right thumb
stick or press the number 6 key to hear our crash. If the playlist is hindering our hearing
of the sounds we can stop it by pressing the S key or pushing on our right trigger.
The final piece of code for our sound demo is where we can set a global variable for the
RPC we set up as well as the volume of our default category cues. To start, we need to add
two more private member fields:
private float currentVolume = 0.5f;
private float value = 0;
Now we can finish up our Update method with the following code:
if (input.KeyboardState.IsHoldingKey(Keys.Up) ||
input.GamePads[0].DPad.Up == ButtonState.Pressed)
Creating a Sound Demo 149
7
currentVolume += 0.05f;
if (input.KeyboardState.IsHoldingKey(Keys.Down) ||
input.GamePads[0].DPad.Down == ButtonState.Pressed)
currentVolume -= 0.05f;
currentVolume = MathHelper.Clamp(currentVolume, 0.0f, 1.0f);
sound.SetVolume(“Default”, currentVolume);
if (input.KeyboardState.WasKeyPressed(Keys.NumPad1))
value = 5000;
if (input.KeyboardState.WasKeyPressed(Keys.NumPad2))
value = 25000;
if (input.KeyboardState.WasKeyPressed(Keys.NumPad3))
value = 30000;
if (input.KeyboardState.WasKeyPressed(Keys.NumPad4))
value = 40000;
if (input.KeyboardState.WasKeyPressed(Keys.NumPad5))
value = 50000;
if (input.KeyboardState.WasKeyPressed(Keys.NumPad6))
value = 60000;
if (input.KeyboardState.WasKeyPressed(Keys.NumPad7))
value = 70000;
if (input.KeyboardState.WasKeyPressed(Keys.NumPad8))
value = 80000;
if (input.KeyboardState.WasKeyPressed(Keys.NumPad9))
value = 90000;
if (input.KeyboardState.WasKeyPressed(Keys.NumPad0))
value = 100000;
if (input.GamePads[0].Triggers.Left > 0)
value = input.GamePads[0].Triggers.Left * 100000;
sound.SetGlobalVariable(“SpeedOfSound”, value);
This completes our sound demo code and now if we run it and press the Up and Down
arrow keys or on the Dpad we can hear the volume of the sounds associated with our
“Default” category go up and down. We clamp our value between 0 and 1 because that is
what the engine takes to set the volume. This is an example of how checking for the
input state without considering the previous state can get us into trouble. The code will
turn the volume up and down very quickly because it is getting back a true on every call
every frame. Feel free to add Dpad to the updated InputHandler code.
Not only does this code let us test the volume settings, it also lets us set a global variable.
In the XACT project, if we used SpeedOfSound as one of the parameters when setting up
our curve then we can actually modify the way the cue sounds here at runtime. That is
pretty powerful.

Read More......

Updating Our Input Handler

Before we actually dig into the code to utilize the XACT project we just created, let’s back
up a minute and take another look at our input handler. In particular we want to extend
our keyboard and gamepad functionality. Currently, if we try to trigger any event with a
key press or a button push we will get the event kicked off several times. This is because
for every frame we are checking in our Update method, if a key is pressed for a fraction of
a second the method will return true for many frames. We already know how to solve the
problem because we did it with our Mouse class. We stored the old state and compared it
to the new state to see what has changed. We need to do the same thing here. With the
mouse code, we did it inside of the demo code, but we want this to be done inside of the
library so our game and demo code do not need to worry about the gory details. We will
not be updating the mouse code but that would be an excellent exercise to go through
once we are done with the chapter.
To get started, we need to open up the InputHandler.cs code file inside of our XELibrary
project. The code for our updated InputHandler.cs file is in Listing 7.1. We are not going
to do a deep dive on the code in this chapter (this is devoted to sound after all), but we
are going to quickly examine what has been modified from a distant view.
LISTING 7.1 InputHandler.cs
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace XELibrary
{
public interface IInputHandler
{
KeyboardHandler KeyboardState { get; }
GamePadState[] GamePads { get; }
ButtonHandler ButtonHandler { get; }
#if !XBOX360
MouseState MouseState { get; }
MouseState PreviousMouseState { get; }
134 CHAPTER 7 Sounds and Music
#endif
};
public partial class InputHandler
: Microsoft.Xna.Framework.GameComponent, IInputHandler
{
public enum ButtonType { A, B, Back, LeftShoulder, LeftStick,
RightShoulder, RightStick, Start, X, Y }
private KeyboardHandler keyboard;
private ButtonHandler gamePadHandler = new ButtonHandler();
private GamePadState[] gamePads = new GamePadState[4];
#if !XBOX360
private MouseState mouseState;
private MouseState prevMouseState;
#endif
public InputHandler(Game game)
: base(game)
{
// TODO: Construct any child components here
game.Services.AddService(typeof(IInputHandler), this);
//initialize our member fields
keyboard = new KeyboardHandler();
gamePads[0] = GamePad.GetState(PlayerIndex.One);
gamePads[1] = GamePad.GetState(PlayerIndex.Two);
gamePads[2] = GamePad.GetState(PlayerIndex.Three);
gamePads[3] = GamePad.GetState(PlayerIndex.Four);
#if !XBOX360
Game.IsMouseVisible = true;
prevMouseState = Mouse.GetState();
#endif
}
public override void Initialize()
{
base.Initialize();
}
public override void Update(GameTime gameTime)
{
Updating Our Input Handler 135
7
LISTING 7.1 Continued
keyboard.Update();
gamePadHandler.Update();
if (keyboard.IsKeyDown(Keys.Escape))
Game.Exit();
if (gamePadHandler.WasButtonPressed(0, ButtonType.Back))
Game.Exit();
#if !XBOX360
//Set our previous state
prevMouseState = mouseState;
//Get our new state
mouseState = Mouse.GetState();
#endif
base.Update(gameTime);
}
#region IInputHandler Members
public KeyboardHandler KeyboardState
{
get { return (keyboard); }
}
public ButtonHandler ButtonHandler
{
get { return (gamePadHandler); }
}
public GamePadState[] GamePads
{
get { return(gamePads); }
}
#if !XBOX360
public MouseState MouseState
{
get { return(mouseState); }
}
public MouseState PreviousMouseState
{
136 CHAPTER 7 Sounds and Music
LISTING 7.1 Continued
get { return(prevMouseState); }
}
#endif
#endregion
}
public class ButtonHandler
{
private GamePadState[] prevGamePadsState = new GamePadState[4];
private GamePadState[] gamePadsState = new GamePadState[4];
public ButtonHandler()
{
prevGamePadsState[0] = GamePad.GetState(PlayerIndex.One);
prevGamePadsState[1] = GamePad.GetState(PlayerIndex.Two);
prevGamePadsState[2] = GamePad.GetState(PlayerIndex.Three);
prevGamePadsState[3] = GamePad.GetState(PlayerIndex.Four);
}
public void Update()
{
//set our previous state to our new state
prevGamePadsState[0] = gamePadsState[0];
prevGamePadsState[1] = gamePadsState[1];
prevGamePadsState[2] = gamePadsState[2];
prevGamePadsState[3] = gamePadsState[3];
//get our new state
//gamePadsState = GamePad.State .GetState();
gamePadsState[0] = GamePad.GetState(PlayerIndex.One);
gamePadsState[1] = GamePad.GetState(PlayerIndex.Two);
gamePadsState[2] = GamePad.GetState(PlayerIndex.Three);
gamePadsState[3] = GamePad.GetState(PlayerIndex.Four);
}
public bool WasButtonPressed(int playerIndex,
InputHandler.ButtonType button)
{
int pi = playerIndex;
switch(button)
{ //start switch
case InputHandler.ButtonType.A:
{
return(gamePadsState[pi].Buttons.A == ButtonState.Pressed &&
Updating Our Input Handler 137
7
LISTING 7.1 Continued
prevGamePadsState[pi].Buttons.A == ButtonState.Released);
}
case InputHandler.ButtonType.B:
{
return(gamePadsState[pi].Buttons.B == ButtonState.Pressed &&
prevGamePadsState[pi].Buttons.B == ButtonState.Released);
}
case InputHandler.ButtonType.Back:
{
return(gamePadsState[pi].Buttons.Back == ButtonState.Pressed &&
prevGamePadsState[pi].Buttons.Back == ButtonState.Released);
}
case InputHandler.ButtonType.LeftShoulder:
{
return(gamePadsState[pi].Buttons.LeftShoulder ==
ButtonState.Pressed &&
prevGamePadsState[pi].Buttons.LeftShoulder ==
ButtonState.Released);
}
case InputHandler.ButtonType.LeftStick:
{
return(gamePadsState[pi].Buttons.LeftStick ==
ButtonState.Pressed &&
prevGamePadsState[pi].Buttons.LeftStick ==
ButtonState.Released);
}
case InputHandler.ButtonType.RightShoulder:
{
return(gamePadsState[pi].Buttons.RightShoulder ==
ButtonState.Pressed &&
prevGamePadsState[pi].Buttons.RightShoulder ==
ButtonState.Released);
}
case InputHandler.ButtonType.RightStick:
{
return(gamePadsState[pi].Buttons.RightStick ==
ButtonState.Pressed &&
prevGamePadsState[pi].Buttons.RightStick ==
ButtonState.Released);
}
case InputHandler.ButtonType.Start:
{
return(gamePadsState[pi].Buttons.Start ==
ButtonState.Pressed &&
138 CHAPTER 7 Sounds and Music
LISTING 7.1 Continued
prevGamePadsState[pi].Buttons.Start ==
ButtonState.Released);
}
case InputHandler.ButtonType.X:
{
return(gamePadsState[pi].Buttons.X == ButtonState.Pressed &&
prevGamePadsState[pi].Buttons.X == ButtonState.Released);
}
case InputHandler.ButtonType.Y:
{
return(gamePadsState[pi].Buttons.Y == ButtonState.Pressed &&
prevGamePadsState[pi].Buttons.Y == ButtonState.Released);
}
default:
throw (new ArgumentException());
} //end switch
}
}
public class KeyboardHandler
{
private KeyboardState prevKeyboardState;
private KeyboardState keyboardState;
public KeyboardHandler()
{
prevKeyboardState = Keyboard.GetState();
}
public bool IsKeyDown(Keys key)
{
return (keyboardState.IsKeyDown(key));
}
public bool IsHoldingKey(Keys key)
{
return(keyboardState.IsKeyDown(key) &&
prevKeyboardState.IsKeyDown(key));
}
public bool WasKeyPressed(Keys key)
{
return(keyboardState.IsKeyDown(key) &&
prevKeyboardState.IsKeyUp(key));
Updating Our Input Handler 139
7
LISTING 7.1 Continued
}
public bool HasReleasedKey(Keys key)
{
return(keyboardState.IsKeyUp(key) &&
prevKeyboardState.IsKeyDown(key));
}
public void Update()
{
//set our previous state to our new state
prevKeyboardState = keyboardState;
//get our new state
keyboardState = Keyboard.GetState();
}
}
}
The first thing to notice is that we added two more classes at the end of our file:
KeyboardHandler and ButtonHandler. These objects each have an Update method that
gets called by our main Update method inside of InputHandler. The Update method stores
the previous state and resets the new state. This is the key to it all. We simply check our
new state against our old state to see if keys or buttons have been pressed or released. We
have helper functions that our game code can call to check if the key was pressed or a
button was pressed. These helper functions just query the previous and new states of the
appropriate input device and return a boolean value. With this implemented we do not
run into the issue of getting multiple events kicked off because the gamer is holding the
button down. It also gives us a base from which to start working. We left our most current
state available, as we still need that for our triggers and Dpad. Dpad could also be put into
this handler because it is treated as a button, but that along with wrapping up the mouse
information is not in the code. This should be a good starting point if either of those is
needed, though.

Read More......

Sounds and Music

Stop and think for a moment what your favorite game
would be like if it did not have sound. Music sets the
atmosphere for our games and sound effects adds to the
realism of our games. In this chapter we discuss how to get
music and sounds into our demos and games.
To do this we will need to use the Microsoft Cross-Platform
Audio Creation Tool (XACT), which Microsoft provides for
us. The tool can be found in our Programs/Microsoft XNA
Game Studio Express/Tools menu. Once we have the tool
opened we will create wave banks and sound banks and
discuss global settings. We will then actually create a sound
manager that we can add to our library

Microsoft Cross-Platform Audio
Creation Tool (XACT)

Unlike textures and 3D models, we do not simply put our
raw wave files into the Content Pipeline directly. Instead
we use XACT to bundle them together and add effects to
the different sounds. The XACT tool allows us to associate
categories with our sounds. When we set a sound to have a
category of Music, it allows the Xbox 360 to ignore the
music if the gamer has a playlist playing on his or her
Xbox 360.
Before we open the actual tool we need to open up the
XACT Auditioning Utility, which is found in the same location
as XACT itself. We only need to do this if we actually
want to audition (listen to) the sounds we are making
inside of XACT. This can, of course, be beneficial, especially
when we want to add effects to the file. With that said,
XACT is not a sound editing software package. It takes
completed wave files and puts them in a format that XNA
can read and use. We can do simple effects like change the
124 CHAPTER 7 Sounds and Music
pitch and volume but the tool is not designed to be a wave file editor.
We first need to hook our XACT tool up to the Auditioning Utility we launched before.
The Auditioning Utility must be run before the XACT tool is run. To play our sounds we
need to tell XACT to connect to the Auditioning Utility by clicking the Audition menu
item and then clicking the Connect To (machine) item. After we have successfully
connected, the Auditioning Utility will say “XACT tool is now connected….”
When we open the tool we will see an empty project to which we can add .wav files.
After adding the files we can then set them up as sounds and create cue names for them
that we can kick off inside of our code. We can modify properties to get different effects
from the sounds.
Wave Banks
To get started we can create a new wave bank from inside XACT. To create a wave bank,
we can follow these steps:
1. Right-click Wave Banks and select New Wave Bank. This can be seen in Figure 7.1.
2. Inside of the Wave Bank empty pane, right-click and select Insert Wave File(s).
3. Find a wave file from the Spacewar project. We can use the Theme.wav file from
MySpacewarWin1\Content\Audio\Waves\Music.
FIGURE 7.1 The XACT tool allows us to add wave banks to utilize the sounds in our games.
4. Because we have our Auditioning Tool set up and XACT connected to it, we can
simply select the wave file and press the spacebar to hear it play. We could also
right-click and select Play, or click Play in the toolbar. As with most graphical user
interfaces (GUIs) there are many ways to accomplish the same task. This book only
lists one way in most cases.
We can go ahead and add in a few other wave files as well. Let’s follow the preceding
steps to enter all of the collision wave files from Spacewar. We can open them all up at
one time in the Open dialog box.
Sound Banks
Even though we have the wave file loaded inside our XACT project, we still could not use
it in our game. We have to create a sound bank first. To create a sound bank we can
follow these steps:
1. Right-click Sound Banks and select New Sound Bank. Just like the wave bank, we
could also accomplish this through the toolbar or the main menu items.
2. Our work pane just filled up with our Sound Bank window. We will need to work
with the Wave Bank window as well as the Sound Bank window so we will want to
position them so we can see them both. One way to do this is to click Tile
Horizontally inside of the Window menu item.
3. Now that we can see both windows we need to drag our wave file from the wave
bank into the sound bank, inside the top left pane (sounds frame).
4. A cue for the sound needs to be created in order for our code to utilize it. We can
create a cue by dragging the sound from the top left pane (sounds frame) into the
bottom left pane (cues frame).
This is the bare minimum we need to do to play sounds in our games. To hear the sound
we can select it and press the spacebar. We can press Escape to stop the sound. To get
sound effects and music into our games, these steps will work. In the next sections we
discuss more advanced ways to manipulate our sounds to get them ready for our game.
Understanding Variations
To accomplish something more than just playing sounds we need to understand variations.
With variations we can assign many waves to a track and we can create different
events for those tracks to create a sound. We can assign many sounds to a cue. We can
then set up how those sounds or waves are to be played. We will be utilizing XACT to
create different variations and then we are going to write a library component that a
demo can use to play these cues.
We can close out our existing XACT instance and open up a new one. We need to create a
wave bank and a sound bank to put our waves in. On this book’s CD, under this chapter’s
folder there is a subfolder entitled Sounds. We need to add all of these waves into our
Understanding Variations 125
7
wave bank. After adding in the waves we need to create some sounds. We are going to
add the different sounds and create different variations so we can learn how to use some
of the XACT features by example.
1. We can drag the Attention and Explosion sounds directly into our cues frame
(bottom left pane) because we are going to play these sounds as is.
2. We can drag the Axe_throw sound directly into our cues frame and rename the cue
to Bullet. We can do this by right-clicking the name in the cues frame and selecting
Rename. At this point, the screen should resemble Figure 7.2.
126 CHAPTER 7 Sounds and Music
FIGURE 7.2 Wave files can be dragged directly into the cues frame.
3. To start our more complicated tasks let’s drag our CoolLoop wave into our cues
frame.
4. In the top right pane (tracks frame) we can see that XACT created a track with a
play event that include our wave name. We want to make sure this sound loops so
we click the Play Wave tree node of the Track 1 root to select it.
5. In the properties window we can see LoopEvent under the PlayWave Properties. The
default value is No but we could either loop it a certain number of times by selecting
Finite and then entering the number of times to loop in the LoopCount property
or we can have it loop forever by selecting Infinite. We are going to let this
sound loop forever so we select Infinite. We can see this in Figure 7.3.
FIGURE 7.3 We can set our play event on a track to loop indefinitely.
6. Because this loop is going to be music we can click the CoolLoop’s sound icon and
drag it on top of the Music category on the left side of the window. When this is
successful we will see the Category change from Default to Music.
7. Next up we are going to add multiple sounds into one cue. We will use
Synth_beep_1 to start us off. Let’s drag that wave into our cues frame.
8. Now we want to make sure that the sound name is selected (in our sounds frame) so
the track frame is showing the play event for track 1.
9. We need to drag Synth_beep_2 and Synth_beep_3 waves and drop them on our play
event inside of track 1. We need to be careful to not drag over the cues frame as it
will make the tracks frame empty (it deselects the sound from the sound pane). We
drag the files by clicking on the icon (XACT does not allow us to drag by the name
of the wave). We can see where we need to release the cursor in Figure 7.4. The final
result after releasing the cursor and completing our drop operation can be seen in
Figure 7.5.
10. Rename this cue from Synth_beep_1 to Hit.
11. With Hit as our active cue, we can press the spacebar to hear it play (assuming we
are connected to the Auditioning Utility). Let’s hit the spacebar several times in a
row very fast. We can hear one sound being played every time we hit the spacebar
regardless if the previous sound was played. We see they are not played at the same
time by “pressing play” once. By putting our different waves directly into a play
event we are giving XACT a list of waves to play from when we call play. It does not
play them all at once (but we will see how to do that in a moment).
Understanding Variations 127
7
12. Because we do not want this particular cue to play more than one sound at a time
we can change the LimitInstances property in the Instance Limiting Properties
section of our properties window to True. We will need to have our cue name
selected to do this. Now when we press the spacebar multiple times it will not start
playing until the last sound has finished.
13. We can hear the sounds are random but we want them to play in the order we specified
in the play event of our track. To do this we need to select Ordered in the
PlayListType property in the Variation Playlist Properties section of the property
frame. We need to do this when we have the cue selected. Changing this value to
Ordered causes XACT to play the sounds in the order we specified. If we wanted it
to start with a random entry and then do them in order, we could have selected
Ordered From Random.
14. Now when we play the sounds, they play in order and only play one at a time.
However, we want the sounds to be queued up so that if we press the spacebar five
times, it will play all of the waves in the sound with each one starting as soon as the
previous one finishes. To do this we need to change the BehaviorAtMax property to
Queue instead of FailToPlay. This can be useful for queuing up things like voiceover
audio that we would never want to play simultaneously. This is shown in Figure 7.6.
Understanding Variations 129
7
15. Now we are going to make a crash cue. To start, let’s drag Ambulance_siren from our
waves into our cues and rename the cue to Crash.
16. Inside of our tracks frame, with our Ambulance_siren sound selected from the sound
frame, right-click and add a new track.
17. Now we can add a play event to the track we just added by right-clicking the track
and selecting Add Event > Play Wave.
18. We need to drag the Car_brake wave into the play event of the track we just created.
Remember not to drag across the cue frame, as the contents of the track frame will
disappear.
19. We need to repeat steps 16 through 18 with the wave files Bus_horn and Explosion.
When this step is completed we should have a total of four tracks, each with its
own play event that has a wave file associated with it. We have expanded the Sound
Banks window in Figure 7.7 so we can see the end result.
130 CHAPTER 7 Sounds and Music
FIGURE 7.7 We can create multiple tracks for a sound, each with one or more events in our
tracks frame.
20. Now if we play our Crash cue we will hear all four sounds at the same time. We
want them to be spaced out as to simulate an accident. We can delay the time at
which different tracks start to play by setting the TimeStamp property in the Timing
Properties section of our property frame when we have the play wave event selected.
Our Car_brake wave can be left alone as it is a little longer of a sound file. We
should modify the rest of the values as follows:
Bus_horn: 0.300
Explosion: 1.000
Ambulance_siren: 1.600
Now when we play the cue we can hear something that resembles a car colliding
with a bus, creating a large explosion and the fastest EMT response time ever!
21. Now we want to make a variant of our explosion sound. We are going to make a
gunshot sound without requiring an additional wave file to be used. To do this we
are going to drag our Explosion sound (top right pane) into the empty white space
inside of the same sound pane so it will create a copy of itself. It should have called
itself Explosion 2. Let’s drag that Explosion 2 from the sounds frame into our cues
frame and rename the cue to Gunshot.
22. When we play Gunshot, it does not sound any different than Explosion. Let’s
change that by adding a Pitch event to our only track for the Explosion 2 sound in
our tracks frame. We add this event just like we added the play event earlier, by
right-clicking the track and selecting Add Event > Pitch.
23. Now we need to actually modify the pitch to get the desired sound effect. To do this
we need to make sure our Pitch event is selected and then change the Constant
value. We can find this value in our properties frame under Pitch Properties/Setting
Type (Equation)/EquationType (Constant). Let’s change the pitch to 50.00 as shown
in Figure 7.8. Now if we play the cue we will hear something that resembles a
gunshot instead of an explosion. We did not have to add another large wave file to
accomplish this effect.
Understanding Variations 131
7
24. Next, we are going to create a cue with multiple sounds. We need to right-click
inside of our cues frame and select New Cue. We can name this cue Complex.
25. Drag the Explosion sound down on top of the Complex cue. Let’s do the same thing
with the Synth_beep_1 sound. Remember, this Synth_beep_1 sound has its own
sound variations already as it plays three different waves in order.
26. We need to change the PlayList type for this Complex cue to Random. This way it
will just play a sound randomly even if the one it picks was just played.
27. The final thing we will do is add a music playlist. Unfortunately, XACT does not
have a way to give us typical playlist functionality so we will have to put that into
our code. For now, though, we can at least add our songs to this playlist. We will
start by dragging our Song1, Song2, and Song3 waves into our sounds frame.
28. We will also drag those sounds to our music category.
29. Now we need to drag our Song1, Song2, and Song3 sounds from our sounds frame
into the cues frame.
30. We can now save our project, calling it Chapter7.
That was a lot of examples, but it was worth going through because at this point we have
good idea of how to do a lot of sound manipulation to prepare cues for our games. Our
games will always reference the cue value.
There are other actions XACT allows us to do, such as setting local and global variables,
setting up transitions through interactive audio settings, and setting up runtime parameter
controls (RPCs).
We can create RPCs when a simple pitch or volume change across the board will not do.
Perhaps we would like to add some reverb, or maybe we would like to modify the volume
as a cue plays up and down. Doing any of these things requires setting up an RPC, which
can be done by right-clicking the RPC tree node and selecting New RPC Preset as shown
in Figure 7.9.
Then we can drag that preset over to one of our sounds in the sounds frame. To test with,
we can make a copy of our CoolLoop sound and then add our preset to this new sound
by dragging the RPC on top of the new sound we just created. We can open the RPC
preset by double-clicking it. Because there is only one sound it is associated with, it will
play that one. At the top of the RPC dialog box we can select other sounds (if we have
added the RPC to multiple sounds). A default volume curve has been added for us. We
can move the points around and then the vertical horizontal bar that is the same color as
our curve can be moved left and right. The bar can be moved or we can modify the variable
above beside the curve declaration. This is the variable that we can change in our
code to cause the sound to react exactly how we want. We might want to do this, for
example, to dim the background music when dialogue is happening between characters.
We can pass a value to the global variable that will produce the sound identical to what
we are hearing as we audition the sound with our RPC added. We can add more curves
(pitch and reverb) by right-clicking the Volume item at the top of the dialog box and
selecting Add New Curve. We can add nodes to our curve by right-clicking inside of the
main white area and selecting Add. A screen shot of this dialog box is shown in Figure
7.10. Have fun coming up with a totally different CoolLoop 2 sound!
7
We can pause and resume all sounds in a category. This means that we could organize our
sounds in such a way that sounds that are used in our playing state can all be assigned to
a category we can define. Then through the code we can pause that category and it will
pause all sounds that are playing. This way we do not need to worry ourselves with
making sure all the sounds stop when someone pauses the game. When we associate a
sound with the Music category the Xbox 360 can mute that and replace it with the
playlist the gamer has his or her console playing at the time. This is a really nice feature
because no matter how good our soundtrack is, at some point gamers will most likely
want to play our games with their own music in the background.

Read More......