Subscribe

RSS Feed (xml)

Powered By

Powered by Blogger

Google
 
xnahelp.blogspot.com

Jumat, 04 April 2008

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......

Texturing 3D Models

Now, let’s replace that texture with one we will load on the fly. We could just modify our
texture resource if we wanted to, but let’s assume that we want to keep that intact and as
we load different asteroids we want some to use that brownish texture and some to use
our newly created texture, on which we will simply remove the color. So let’s fire up our
favorite paint program and open up the .tga file and turn it into a grayscale image. If
needed, the image asteroid1-grey.tga can be taken from the CD in this chapter’s source
code. Let’s add our newly created image into our Solution Explorer (or we can just include
it into our project if we saved the new version in our /Textures/ subfolder from our paint
program).
Add the following code in our LoadGraphicsContent method:
originalAsteroid = content.Load(@”Content\Textures\asteroid1“);
greyAsteroid = content.Load(@”Content\Textures\asteroid1-grey”);
Just like adding our model, we can add our texture asset very easily. Of course, now we
have to actually declare our private member field:
private Texture2D originalAsteroid;
private Texture2D greyAsteroid;
Now we can make a couple of changes to our DrawModel method. We need to add in
another parameter of type Texture2D and call it texture. We also need to actually set our
effect to use that texture if it was passed in, which can be seen in the following code:
if (texture != null)
be.Texture = texture;
This code statement is inside of the inner foreach loop with the rest of the code that sets
the properties of our basic effect that is being used by our model. We are simply checking
to see if null is passed in; if not, we are setting the texture of the effect. As we saw earlier,
the effect is getting applied to the mesh of our model, so we only need to modify our call
to our DrawModel to pass in the new texture. We will go ahead and just create another
asteroid on our screen by replacing our current drawing code with the following:
world = Matrix.CreateTranslation(new Vector3(0, 0, -4000));
DrawModel(ref model, ref world, greyAsteroid);
world = Matrix.CreateTranslation(new Vector3(0, 0, 4000));
DrawModel(ref model, ref world, originalAsteroid);
The first line did not change, but it is added here for readability purposes. The second
statement we are actually passing in is our new texture. The last two statements are
placing another asteroid behind us and resetting the texture to the original one. We can
run this program and spin our camera to see both asteroids being drawn.
120 CHAPTER 6 Loading and Texturing 3D Objects
Now, let’s apply some transformations to our asteroids. We can add some rotation to get
them to do a little more than they are right now. To do this, we just need to replace our
Draw code with the following:
world = Matrix.CreateRotationY(MathHelper.ToRadians(
270.0f * (float)gameTime.TotalGameTime.TotalSeconds)) *
Matrix.CreateTranslation(new Vector3(0, 0, -4000));
DrawModel(ref model, ref world, greyAsteroid);
world = Matrix.CreateRotationY(MathHelper.ToRadians(
45.0f * (float)gameTime.TotalGameTime.TotalSeconds)) *
Matrix.CreateRotationZ(MathHelper.ToRadians(
45.0f * (float)gameTime.TotalGameTime.TotalSeconds)) *
Matrix.CreateTranslation(new Vector3(0, 0, 4000));
DrawModel(ref model, ref world, originalAsteroid);
Even with the code broken to fit on the page, we can still see that we only have four
statements, just like before. The only two that changed are our code that modified the
world matrix. This makes sense because we want to rotate our asteroids. We start by
looking at our first world transformation and can see that we are simply rotating it
around the y axis by 270 degrees * the number of seconds our game has been running.
This effectively makes it continuously render every frame. After rotating we still translate
it like we did before, moving it 4,000 units into our world. The second world transformation
is similar except we are only rotating by 45 degrees instead of 270 degrees. We are
also rotating around the x axis by the same amount. This should give us a decent wobble
effect. Finally we are translating 4,000 units behind us just like before. We can compile
and run our program and see our asteroids are moving (well, rotating in place).

Read More......

Loading 3D Models

The XNA Framework’s Content Pipeline handles loading .X and .FBX files automatically
when they are pasted into the Solution Explorer (or included in the project). This is when
the Content Pipeline goes through the process described in the previous section of
importing, processing, and compiling the data. We then can use our game class and the
Content Manager to read the model information.
We will start by creating a new project, which we can call Load3DObject. We can create
both the Windows and Xbox 360 game projects. After getting our solution set up, we can
then add an existing project to our solution—the XELibrary from last chapter. Once we
import both the Windows and the Xbox 360 XELibrary projects, we need to reference
them inside of the game projects we created. We reference the XELibrary in our Windows
project and the XELibrary_Xbox360 in our Xbox 360 game project.
After we have our initial setup of our solution file completed, we can jump right in and
add a using statement at the top of our Game1.cs class to access our library:
using XELibrary;
We can set up our private member fields to access the game components in our library:
private FPS fps;
private FirstPersonCamera camera;
private InputHandler input;
The following is our constructor, where we initialize the variables we just set:
public Game1()
{
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
input = new InputHandler(this);
Components.Add(input);
camera = new FirstPersonCamera(this);
Components.Add(camera);
#if DEBUG
//draw 60 fps and update as often as possible
fps = new FPS(this, true, false);
#else
fps = new FPS(this, true, false);
#endif
Components.Add(fps);
}
With just that little bit of coding (and setup) we now have access to all of the code in our
library. We automatically have a first person camera now and we can handle input as well
as display our frame rate. Game components aren’t too shabby.
As we add content to our pipeline we could pretty easily clutter up our Solution Explorer
pane with all of the files. To help alleviate that problem, we are going to create a
Contents folder with subfolders under it. To start, we will add two subfolders—Models
and Textures—under the Content folder. This is not required, but it really helps keep the
clutter minimal. After doing this we will need to make sure we include that folder inside
Loading 3D Models 115
6
of our Xbox 360 solution, otherwise the content files won’t compile and won’t be
deployed and will cause the demo to not load.
Now with the preliminary work out of the way, we can actually get down to business and
load a 3D object. Find the Spacewar project that we extracted back in Chapter 1,
“Introducing the XNA Framework and XNA Game Studio Express,” and under the
Contents\Models\ folder we need to copy the asteroid1.x file and copy it into our
Solution Explorer’s Contents\Models\ folder. When we do this, XNA Game Studio
Express flags it as XNA Framework Content. Now we can compile the code, which will
also kick off the Content Pipeline. The Content Pipeline kicked off the Content Importer,
shoved the data into the DOM, and then called the Content Processor, which passed it to
the Content Compiler. So when we compile our game, it not only compiles our code, but
also the content.
The Content Compiler threw an error, “Missing asset … \Content\Textures\asteroid1.tga,”
because the .X file we loaded has a reference to a texture inside of it. It references a sibling
folder by the name of Textures. The following is a portion of the asteroid1.x file where it
references the texture in the texture’s sibling folder:
Material phong1SG {
1.0;1.0;1.0;1.000000;;
18.000000;
0.000000;0.000000;0.000000;;
0.000000;0.000000;0.000000;;
TextureFilename {
“..\\textures\\asteroid1.tga”;
}
}
We have that folder, but we did not grab the .tga texture from the Spacewar project. Let’s
do that now and paste it into our Textures folder. Once we do this we can compile again
and it should compile without issues. After it compiles successfully, we can browse to our
/bin/x86/content/models/ and /textures/ folders to see there are a couple of files that were
created at the time we compiled. The Content Compiler took the files and compiled them
into the .xnb files we see here. The compiler gave us an error when it could not find the
texture that was associated with the .X file. We have corrected that and our code (and
content) now compiles successfully.
We could run the code but nothing would be on our screen because we have not actually
told XNA to load and draw the object. We can get that ball rolling by creating a private
member field in our Game1.cs class as follows:
private Model model;
Now we can initialize this variable by actually loading our model in our code. We do this
inside of the LoadGraphicsContent method. The following code has the entire method:
116 CHAPTER 6 Loading and Texturing 3D Objects
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
// TODO: Load any ResourceManagementMode.Automatic content
model = content.Load(@”Content\Models\asteroid1”);
}
// TODO: Load any ResourceManagementMode.Manual content
}
The only line we added was the statement inside of the condition, but it is important to
discuss this entire method, as we have just breezed over it in the past chapters. This
method is where we will load all of our graphics content. We have two options when we
load our content: We can let XNA handle the memory management of the resources automatically
by loading our content inside of the condition or we can maintain the memory
management ourselves. This method as well as its counterpart, UnloadGraphicsContent,
gets called at appropriate times. What are these appropriate times? This is a good time to
discuss the logic flow of XNA.
The XNA Framework’s logic flow works something like this:
1. The Main application calls the Game Constructor.
2. The Game Constructor will create any game components and call their constructors.
3. The XNA Framework calls the game’s Initialize method.
4. The XNA Framework calls each of the game component’s Initialize methods.
5. The XNA Framework calls each of the Drawable game component’s
LoadGraphicsContent methods.
6. The XNA Framework calls the game’s LoadGraphicsContent method.
7. The XNA Framework calls the game’s Update method.
8. The XNA Framework calls each of the game component’s Update methods.
9. The XNA Framework calls the game’s Draw method.
10. The XNA Framework calls each of the Drawable game component’s Draw methods.
11. Steps 7 through 10 are repeated many times each second.
12. If the device is lost (user moved the window to another monitor, screen resolution
is changed, window is minimized, etc.) then a call to UnloadGraphicsContent is
made.
13. If the device is reset then we start back at step 6 again.
14. The gamer exits the game.
15. The XNA Framework calls the game’s Dispose method.
Loading 3D Models 117
6
16. The game’s Dispose method calls the base object’s Dispose method, which causes …
17. The XNA Framework calls each of the game component’s Dispose methods.
18. The XNA Framework calls the game’s UnloadGraphicsContent method.
19. The game’s Dispose method gets focus back and the game exits.
Something to note about how the XNA Framework calls the game component’s
Initialize method (and LoadGraphicsContent for drawable game components) is this
only happens once when the game’s Initialize method is kicked off by the framework.
If game components are added later, their Initialize (and LoadGraphicsContent)
methods will not be called. This is important to understand when managing game
components.
At this point we have loaded our 3D content. We have created a private member field to
store the model we added. We actually loaded our model in our code and initialized our
variable. Now we just need to draw the model on the screen. We need to add the following
method to draw a model:
private void DrawModel(ref Model m, ref Matrix world)
{
Matrix[] transforms = new Matrix[m.Bones.Count];
m.CopyAbsoluteBoneTransformsTo(transforms);
foreach (ModelMesh mesh in m.Meshes)
{
foreach (BasicEffect be in mesh.Effects)
{
be.EnableDefaultLighting();
be.Projection = camera.Projection;
be.View = camera.View;
be.World = world * mesh.ParentBone.Transform;
}
mesh.Draw();
}
}
Our DrawModel method takes in a reference to our model as well as a reference to the world
matrix we need to apply to our model. The first two statements get the transforms of each
bone in the model. A model can have a parent bone with children bones associated with
it. This is mainly used in animations, but even if a model does not have animations it
might still have bones and so our code should include this unless we know for certain that
our model does not have any children. The transforms array will contain a transformation
matrix of each mesh of the model that contains its position relative to the parent. By
doing this, we can make sure that each ModelMesh of the parent Model will be drawn at the
right location. This actually happens in the last statement inside of our inner foreach loop.
118 CHAPTER 6 Loading and Texturing 3D Objects
We take the world matrix that the mesh is supposed to be transformed with and we multiply
that transformation with the ModelMesh’s parent’s transformation. This is to make sure
that that each ModelMesh is drawn correctly with the parent mesh.
We did not need to create our own BasicEffect object as XNA will apply one to a mesh
we load. We can override this and will see that in Part V of the book when we talk about
the High Level Shader Language and make our own effect files. For now, we just ensure
that the default lighting is enabled on the effect as well as setting our projection and view
matrices to what our Camera game component has updated it to be. When we did this in
the last chapter, we only moved where we are setting those values. Instead of having an
effect that we are explicitly calling, we are tying into the one that XNA applies to the
Model when it loads it. This is also happening inside of the inner foreach loop so we
could apply different effects to children meshes if we wanted to.
The other thing to notice is the fact that we did not reference our texture anywhere. Because
it was inside of the .X file and the Content Compiler stored that information, the Content
Manager knew where to look for the texture and load it automatically. We will see how to
override the texture through code a little later in this chapter. Let’s just get it to draw with
the normal texture for now! To do that, we need to actually call our DrawModel method
inside of our Draw method with the following code:
world = Matrix.CreateTranslation(new Vector3(0,0,-4000));
DrawModel(ref model, ref world);
The model we are loading is rather large and as such we are going to push it way back
into the world. In fact, we made a modification to our Camera class in our XELibrary. We
originally had our near and far planes set up as 0.0001 and 1000.0, respectively. The thing
about the plane values is that they are floating points, which means there is a finite
amount of precision we can have. We can either have the precision before the decimal
point or after the decimal point, but not both. A lot of times programmers will set the far
plane to a very high number but when that happens the depth buffer (also known as a z
buffer) could have a hard time knowing which objects to draw first. As a result during
game play the screen will almost flicker as different vertices are fighting to be drawn first
and the z buffer is confused because it cannot take into account the minor differences in
the locations of those vertices. Of course, the .0001 near plan we originally had set was
not exactly practical either. At this point having a near plane of 1 and a far plane of
10,000 should meet most of our needs without overly stressing our z buffer. We could
have our far plane even further without causing an adverse effect on the depth buffer.
Finally, we need to create our private member world field that we referenced:
private Matrix world;
Now we can compile and run our code and we should see our asteroid object sitting right
in front of us. This is not extremely exciting, but we have just successfully drawn a .X
model complete with a texture.

Read More......

Understanding the Content Pipeline

The Content Pipeline can be used to solve a very real
problem: The content we create for games is typically not
game ready. For example, 3D content is usually stored in a
proprietary format and there is a need to convert the data
before loading it into the game. This is where Content
Pipeline helps out. In general, it can take different files as
input, massage them to get them into a type that we can
work with, and then compile them into a format that can
easily be loaded when our game starts.
The XNA Framework Content Pipeline is made up of
several components. First is the Content Importer, which is
responsible for reading the data that is loaded into the
solution. If there is data in the file the Content Importer
does not know how to map to the content DOM, then the
data is not stored. It only keeps the data it cares about.
Once the importer is done reading in the data, it passes it
along to the Content DOM, which stores the strongly
typed data. It is then passed from the Content DOM to a
Content Processor, which then passes it to a Content
Compiler. If the compilation fails we get a nice message
inside of the IDE that tells us what happened: We do not
114 CHAPTER 6 Loading and Texturing 3D Objects
have to wait and see if it is going to load at runtime, and this is a huge improvement over
how things used to work. The compiler actually builds files that are read in at runtime.
These files typically have an .xnb file extension. Audio files actually create .xgs, .xwb, and
.xsb files for the actual sound project, wave bank, and sound bank content. We discuss
the sound and how it relates to the Content Pipeline in the next chapter. The Content
Pipeline is smart enough to not recompile any content that has not changed since the
last build. Finally, after all of the content is compiled into files, they are read in via the
Content Manager at runtime so our games can consume the content. We have been using
the Content Manager since our very first application in Chapter 2, “XNA and the Xbox
360.” We created an instance to the Content Manager in our game’s constructor like the
following:
content = new ContentManager(Services);
Fortunately, when we use the Content Manager we do not need to actually dispose of our
objects, as it handles disposing of our content itself. It does this in the following code,
which is always included in our new game projects:
protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent == true)
{
content.Unload();
}
}
The code simply calls the Unload method on the instance of the content manager. This
then goes through each of the objects that have been loaded and disposes of them. We
discuss the counter part of this method in the next section. The LoadGraphicsContent
method does the actual loading of the data.
As we add content to our project that is recognizable as XNA Framework content, it goes
through the process just described. The Properties window inside of the Visual C# Express
IDE will show an asset name that we can modify. If we add content types that are not
recognized, we can change the XNA Framework Content boolean value to true. We would
then need to fill in the Content Importer and Content Processor properties, specifying
how to turn the unknown content type into a format that XNA can recognize. This
would require a custom importer and processor that we discuss building in Chapter 8,
“Extending the Content Pipeline,” where we will learn about extending the pipeline.

Read More......

Creating a Split Screen

Now that we know how to set up our camera and accept input, we can look into how our
code will need to change to handle multiple players in a split-screen game. To start, we
need to make a copy of the InputDemo project we just finished. We can rename the
project SplitScreen. After we have our solution and projects renamed (complete with our
assembly GUID and title) we can look at the code we will need to change to accomplish a
split-screen mode of play.
To create a split screen, we need to two different viewports. We have only been using one
up until now and we actually retrieved it in our camera’s Initialization method. We
simply grabbed the GraphicsDevice.Viewport property to get our camera’s viewport.
Because we want to display two screens in one we need to define our two new viewports
and then let the cameras (we will need two cameras) know about them so we can get the
desired effect. To start we will need to add the following private member fields to our
Game1.cs code:
private Viewport defaultViewport;
private Viewport topViewport;
private Viewport bottomViewport;
private separatorViewport;
private bool twoPlayers = true;
private FirstPersonCamera camera2;
Then at the end of our LoadGraphicsContent method we will need to define those viewports
and create our cameras and pass the new values. We do this in the following code:
if (twoPlayers)
{
defaultViewport = graphics.GraphicsDevice.Viewport;
topViewport = defaultViewport;
bottomViewport = defaultViewport;
topViewport.Height = topViewport.Height / 2;
separatorViewport.Y = topViewport.Height – 1;
separatorViewport.Height = 3;
bottomViewport.Y = topViewport.Height + 1;
bottomViewport.Height = (bottomViewport.Height / 2) - 1;
camera.Viewport = topViewport;
106 CHAPTER 5 Input Devices and Cameras
camera2 = new FirstPersonCamera(this);
camera2.Viewport = bottomViewport;
camera2.Position = new Vector3(0.0f, 0.0f, -3.0f);
camera2.Orientation = new Vector3(0.0f, 0.0f, 1.0f);
camera2.PlayerIndex = PlayerIndex.Two;
Components.Add(camera2);
}
We discussed briefly that we would need more than one camera to pull this off. This is
because we have our view and projection matrices associated with our camera class
(which we should). It makes sense that we will have two cameras because the camera is
showing what the player is seeing. Each player needs his or her own view into the game.
Our initial camera is still set up in our game’s constructor but our second camera will get
added here. Our first camera gets the default viewport associated with it. The first thing
the preceding code is doing is checking to see if we are in a two-player game. For a real
game, this should be determined by an options menu or something similar, but for now
we have just initialized the value to true when we initialized the twoPlayer variable.
Inside of the two-player condition the first thing we do is set our default viewport to what
we are currently using (the graphic device’s viewport). Then we set our top viewport to
the same value. We also initialize our bottomViewport to our defaultViewport value. The
final thing we do with our viewports is resize them to account for two players. We divide
the height in two (we are making two horizontal viewports) on both. We then set our
bottom viewport’s Y property to be one more than the height of our bottom. This effectively
puts the bottom viewport right underneath our top viewport.
While still in the two-player condition we change our first camera’s viewport to use the
top viewport. Then we set up our second camera by setting more properties. Not only
do we set the viewport for this camera to the bottom viewport we have, but we also
set a new camera position as well as the orientation of the camera. Finally, we set the
player index.
None of these properties is exposed from our camera object, so we need to open our
Camera.cs file and make some changes to account for this. First, we need to add a new
private member field to hold our player index. We just assumed it was player 1 before. We
can set up our protected (so our FirstPersonCamera class can access it) index as an
integer as follows:
protected int playerIndex = 0;
Now, we can actually modify our input code that controls our camera to use this index
instead of the hard-coded value 0 for our game pads. In the camera’s Update method we
can change any instance of input.GamePads[0] to input.GamePads[playerIndex]. We also
need to do the same for the FirstPersonCamera object. We did not update the keyboard
code and will not for the sake of time. However, to implement multiple users where both
can use the keyboard we should create a mapping for each player and check accordingly.
In general, it is a good practice to have a keyboard mapping so that if gamers do not like
Creating a Split Screen 107
5
the controls we have defined in our games then they have a way to change them so it
works more logically for them. The same can be said about creating a mapping for the
game pads but many games simply give a choice of a couple of layouts. Because the code
does not implement a keyboard mapping, the only way for us to control the separate
screens differently is by having two game pads hooked up to our PC or Xbox 360.
After we have changed our camera to take the player index into consideration before
reading values from our game pad, we can add the following properties to our code:
public PlayerIndex PlayerIndex
{
get { return ((PlayerIndex)playerIndex); }
set { playerIndex = (int)value; }
}
public Vector3 Position
{
get { return (cameraPosition); }
set { cameraPosition = value; }
}
public Vector3 Orientation
{
get { return (cameraReference); }
set { cameraReference = value; }
}
public Vector3 Target
{
get { return (cameraTarget); }
set { cameraTarget = value; }
}
public Viewport Viewport
{
get
{
if (viewport == null)
viewport = graphics.GraphicsDevice.Viewport;
return ((Viewport)viewport);
}
set
{
viewport = value;
InitializeCamera();
}
}
108 CHAPTER 5 Input Devices and Cameras
We are simply exposing the camera’s position, orientation (reference), and target variables.
For the player index property we are casting it to a PlayerIndex enumeration type.
The final property is the Viewport property. We first check to see if our viewport variable
is null and if so we set it to the graphics device’s viewport. When we set our Viewport
property, we also call our InitializeCamera method again so it can recalculate its view
and projection matrices. We need to set up a private member field for our viewport. We
will allow it to have a default null value so we can declare it as follows:
private Viewport? viewport;
Because we are utilizing the Viewport type we will need to add the following using statement
to our code:
using Microsoft.Xna.Framework.Graphics;
The only thing left for us to do now is to actually update our game’s drawing code to
draw our scene twice. Because we are going to have to draw our scene twice (once for
each camera) we will need to refactor our Draw code into a DrawScene method and pass in
a camera reference. Our new code for the new Draw method is as follows:
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Viewport = camera.Viewport;
DrawScene(gameTime, camera);
if (twoPlayers)
{
graphics.GraphicsDevice.Viewport = camera2.Viewport;
DrawScene(gameTime, camera2);
//now clear the thick horizontal line between the two screens
graphics.GraphicsDevice.Viewport = separatorViewport;
graphics.GraphicsDevice.Clear(Color.Black);
}
base.Draw(gameTime);
}
We took all of the code that was inside of this method and put it into a new method
DrawScene(GameTime gameTime, Camera camera). The code we put into the DrawScene
method did not change from how it looked when it resided inside of the Draw method.
The first thing we do with the preceding code is set our graphics device’s viewport to be
what our camera’s viewport is. We then draw the scene passing in our camera. Then we
check to see if we have two players; if so we set the viewport appropriately and finally
draw the scene for that camera. We can run our application and see that it is using a split
screen! The screen shot of this split screen demo can be seen in Figure 5.1.
Creating a Split Screen 109

Read More......