The Viewport class is used to create a viewport object: sim-Viewport viewport = new sim-Viewport; Each viewport object has several properties to set the position and area covered inthe g
Trang 1in a SoundEffect object A SoundEffectInstance is created from this SoundEffect ject which gives you the ability to loop, pause, and resume your individual sound ef-fects The firing audio is loaded into a SoundEffect object which is used later forplayback whenever theBbutton is pressed.
ob-// load and play background audio
backgroundAudio = Content.Load<Song>("Audio\\telemetricBeep"); MediaPlayer.Play(backgroundAudio);
MediaPlayer.IsRepeating = true;
// load and play engine as SoundEffectInstance for playback control
engineSound = Content.Load<SoundEffect>("Audio\\engine0");
engineInstance = engineSound.Play(VOLUME, PITCH, PAN, LOOP);
// load fire sound effect for playback later
fireSound = Content.Load<SoundEffect>("Audio\\fire");
Lastly, inside Update(), you can add this code to trigger the firing audio whenevertheBbutton is pressed This code will also allow your players to pause and resume theship engine and telemetric beep whenever they press the center of the Zune pad.// get new game pad states
GamePadState gpCurrent = GamePad.GetState(PlayerIndex.One);
// play fire sound at each new B button press event
if (gpCurrent.IsButtonDown(Buttons.B)&& gpPrevious.IsButtonUp(Buttons.B)) fireSound.Play(VOLUME, PITCH, PAN, !LOOP);
// toggle engine audio pause and resume states for DPad.Down events
if (gpCurrent.Buttons.A == ButtonState.Pressed &&
Trang 2We are sure you will notice how much more enjoyable your games are when you
add audio using any of the alternatives provided in XNA
To get the most from this chapter, try out these chapter review exercises
1. Implement the step-by-step example in this chapter to create your own
XACT audio project file Then load your audio and play it from code
2. Using the solution for the arcing projectiles example from Chapter 19, add
in audio to handle a launch sound and a 3D audio-enabled explosion
sound when the rocket hits the ground
3. Using the solution for “Adding a Car as a Third-Person Object” from
Chapter 14, add a looping noise that repeats to create a continuous engine
sound whenever the car moves forward or backward
Trang 4CHAPTER 28
Multiplayer Gaming
Trang 5You could actually split the screen into more than four sections, but the controllerlimit is four You might want additional dimensions, though, if you were to show dif-ferent views of the world For example, maybe you want to create a radar screen with
an aerial view of your entire world in addition to the main viewer for navigation Thesplit-screen technique offers many useful possibilities for dividing up the graphicsthat are rendered in your window
The code changes required to enable a split-screen game are surprisingly simple.TheViewportclass makes it easy to split your screen And if your camera is care-fully designed, as it is in the examples this book, you can easily create separate in-stances to give each additional player control over her own viewport
A viewport is a section of the window that you use to draw a scene from a specific
view As you’ll see, using theViewportclass to split the screen is actually very ple The Viewport class is used to create a viewport object:
sim-Viewport viewport = new sim-Viewport();
Each viewport object has several properties to set the position and area covered inthe game window Each section of the window is assigned values for the startingtop-left pixel, the width and height in pixels, and the depth for clipping, so your phys-ical game objects draw properly:
int viewport.X // top left pixel X coordinate
int viewport.Y // top left pixel Y coordinate
int viewport.Width // width in pixels
int viewport.Height // height in pixels
float viewport.MinDepth // minimum depth of clip volume (usually 0) float viewport.MaxDepth // maximum depth of clip volume (usually 1)
492
Trang 6The bulk of the code changes needed to convert to a multiplayer game are in
han-dling a separate instance of the camera for each player However, even this task is
rel-atively simple
When your multiplayer games are rendered on the Xbox 360, your viewports may
be truncated on the televisions where they are played In fact, it is possible that up to
20 percent of the screen will be hidden This issue can be addressed by implementing
a routine to create margins that equal 10 percent of the window height at the top and
bottom and 10 percent of the window width for the left and right An example of
how to do this is presented in the demonstration later in this chapter
PLAYER
To give each user the ability to navigate through the world, a separate camera
in-stance is required for each player The camera inin-stance gives the players the ability to
change their position and view within the 3D world
Adjusting the View
For the graphics engine used in this book, whenever a player moves the mouse or
shifts the right thumbstick, he changes his view In other words, his position in the
world stays the same, but hisLookdirection changes as his view target changes A
separate view is needed for each player in the game For example, in a racing game
you might need to focus your camera to watch the contours of a hairpin turn so you
don’t crash Your friend might need to watch out for an oil slick to maintain control
of the car, and yet another player might be focused on the finish line
When you assign a separate viewport for each player, every object that is drawn in
the viewport must be rendered according to that player’s view Even the base code,
which draws nothing but ground, must draw the ground once for each viewport
ac-cording to the viewport owner’sLookdirection To handle this need for separate
views, the camera’s view matrix is updated separately for each player
Adjusting the Projection
The Projection matrix transforms vector coordinates into clip space (a cone that the
viewer sees through) In a split-screen window, you must also adjust the projection to
match the area in each viewport If you do not resize the perspective’s aspect ratio
properly, you could end up with a viewport(s) that displays everything in a bloated
manner—as if the scene were either viewed through a fish-eye lens or in a house of
mirrors The aspect ratio considers the viewport width relative to the viewport
height Until now we have used the window width over height to calculate this ratio
Now though, the current viewport’s width over height is needed
Trang 7To implement this modification for a split-screen environment, for each differentviewport size on display, a Projection matrix is defined when the application beginswith the following syntax:
// parameters are field of view, viewport w/h, near clip, far clip
Matrix projection = Matrix.CreatePerspectiveFieldOfView(
float fieldOfView, float aspectRatio, float nearClip, float farClip)
If you divide the window into top and bottom viewports, the aspect ratio comes this:
be-Window.ClientBounds.Width/(Window.ClientBounds.Height/2)
It is possible to have up to four game controllers on one Xbox 360 or PC, so youcould write code to handle up to four different players and split the screen accord-ingly at run time For the PC, you can even use the mouse and keyboard as one ofthese inputs Handling the different controllers is easy with the GamePadState
class because each controller is referenced by a separate instance of the class Thestates for each control on the game pad can be obtained using the GetState()
method with aPlayerIndexattribute as a parameter to specify each player
This example demonstrates multiplayer 3D gaming in a split-screen environment.Two aliens will be rendered and controlled in two separate viewports Each playerhas her own viewport and is given control of one alien’s spaceship, which moves withher camera as she travels Figure 28-1 shows a split screen for two players Eachplayer can control her view and position inside the viewport and ultimately travelwithin the world independently of the other player
A multiplayer racing game or first-person shooter game uses the same code tion, so converting the logic to suit a different type of 3D multiplayer game is a simpletask Converting this logic to handle more than two players is also straightforward.When you run this code on the Xbox 360, you will be able to handle two players,each with her own controller When the code is run on the PC, you can handle eithertwo controllers, or one controller and a mouse/keyboard combination If you runthis code on the PC with only a mouse and keyboard, you will be able to control one
founda-of the viewports, but the other viewport will be disabled until a controller is nected
Trang 8con-This example begins with either the MGHWinBaseCode or MGH360BaseCode
project, which can be found in the BaseCode folder in the download available from
this book’s website
To enable a two-player game, and to identify each player, you declare the
NUMPLAYERS,ALIEN0, andALIEN1definitions at the top of the game class:
const int NUMPLAYERS = 2;
const int ALIEN0 = 0; const int ALIEN1 = 1;
To give each player control to move through the 3D environment, and to allow
them to view it independently, you declare an array with two separate instances for
the camera Use this revision to replace the existing camera object declaration:
private Camera[] cam = new Camera[NUMPLAYERS];
Trang 9When you’re initializing each camera, the starting position and view position foreach person needs to be different Otherwise, with just a default position and view,when the game begins, the players would all be positioned in the same place, one ontop of the other To set the players up at opposite ends of the world, and to have themlooking at their opponent, each instance of the camera is initialized with parameters
to set the position and view
An override to the camera constructor allows you to set the position and view ofthe camera when it is initialized for each player:
public Camera(Vector3 startPosition, Vector3 startView){
Vector3 position, view;
position = new Vector3( 0.5f, 0.9f, BOUNDARY - 0.5f);
view = new Vector3( 0.5f, 0.7f, BOUNDARY - 1.0f);
cam[0] = new Camera(position, view);
position = new Vector3(-0.5f, 0.9f,-BOUNDARY + 0.5f);
view = new Vector3(-0.5f, 0.7f,-BOUNDARY + 1.0f);
cam[1] = new Camera(position, view);
As mentioned earlier in this chapter, because both viewport heights are half the tual window height, the aspect-ratio parameter in the Projection matrix must be ad-justed The aspect ratio for the projection becomes (width/(height/2)) To apply this
ac-to the Projection matrix, after initializing each camera, replace the call ac-to initializethe Projection matrix in theInitialize()method:
for (int player = 0; player < 2; player++)
cam[player].SetProjection(Window.ClientBounds.Width,
Window.ClientBounds.Height/2);
Now that you have properly set up the projection matrix for a multiplayer ronment, you will need to comment out the original call statement to initialize theprojection matrix You can find this call statement inside the
envi-InitializeBaseCode()method of the game class:
Trang 10// cam.setProjection(Window.ClientBounds.Width,
// Window.ClientBounds.Height);
A routine is needed in the game class to determine how many game controllers are
connected so it can assign control to both players accordingly The
TotalControllersUsed()method needed for this example only considers a
sit-uation where up to two controllers are used:
TheChangeView(),Move(),Strafe(), andDrawGround()methods inside
the game class need to be modified so they can be used for each player Since each
player is a viewport owner, these method headers must be adjusted to accept the
player number, as follows:
Vector2 ChangeView(GameTime gameTime, int player)
float Move(int player)
float Strafe(int player)
void DrawGround(int player)
TheChangeView(),Move(), andStrafe()methods are called once for each
viewport owner These methods must then select the correct input device to allow
each player to control their view and position
For this example, by default, aGamePadStateobject is set for the first player
us-ing the PlayerIndex.One parameter value regardless of whether a controller is
connected or not If zero or one controllers are connected on a PC, the mouse is
desig-nated for the first player to control their viewport If two game controllers are
con-nected on the PC, theGamePadStateobject is set for the second player using the
PlayerIndex.Twoparameter
The following code must be added in the ChangeView(), Move(), and
Strafe()methods after theGamePadStateobject,gp, is declared:
bool useMouse = false;
int totalControllers = TotalControllersUsed();
Trang 11// when fewer than two controllers connected use mouse for 1st player
if (totalControllers <2 && player == 0)
useMouse = true;
// when 2 controllers connected use 2nd controller for 2nd player
else if (totalControllers == 2 && player == 1)
gp = GamePad.GetState(PlayerIndex.Two);
Also, to ensure that code for the game pad is executed on the PC when either one
or two controllers are connected, inside the ChangeView(), Move(), and
Strafe()methods, replace:
if(gp.IsConnected == true)
withif(!useMouse)
The code that sets the WorldViewProjection matrix insideDrawGround()mustalso be adjusted according to the player’s view This adjustment sets theWorldViewProjection matrix so it is drawn according to each viewport owner’sview:
textureEffectWVP.SetValue(world * cam[player].viewMatrix
* cam[player].projectionMatrix);
Because each player has a separate instance of the camera, each camera must beupdated separately; this gives the players the ability to travel independently in thegame world To enable this, insideUpdate(), replace the code that handles the cam-era’s time tracking, forward movement, backward movement, and strafing with thisrevision to update these values for each player:
for (int player = 0; player < NUMPLAYERS; player++){
// update timer in camera to regulate camera speed
When the viewports are being drawn (while your code is run on the Xbox 360), it
is possible that the full window may not be visible—it may fall outside the title-saferegion As discussed previously in examples where 2D graphics are used, on some
Trang 12televisions, this nonvisible range may be as high as 20 percent To adjust for this
pos-sibility, the starting top-left pixel that is used as the viewport should allow for this
po-tential difference When viewports are used, you are going to want to account for this
possibility so that your 3D graphics do not appear to be off center if truncation
oc-curs To fix this, add this version of theTitleSafeRegion()method to obtain the
bounding margins for the top viewport These margins in turn will be used to
deter-mine the starting top-left pixel for each viewport in this demonstration:
Rectangle TitleSafeRegion(){
int windowWidth = Window.ClientBounds.Width;
int windowHeight = Window.ClientBounds.Height;
#if Xbox
// some televisions only show 80% of the window
Vector2 start = new Vector2(); // starting pixel X & Y
const float UNSAFEAREA = 0.2f; // 80% not visible on
// Xbox 360 start.X = windowWidth * UNSAFEAREA/2.0f;
start.Y = windowHeight * UNSAFEAREA/2.0f;
// ensure viewport drawn in safe region on all sides
return new Rectangle(
// PC show the entire region
return new Rectangle(0, 0, windowWidth, windowHeight/2);
}
The next method needed in your game class is the CurrentViewport()method to
set your viewport In a multiplayer game, theDraw()method must trigger rendering
of the entire scene for each viewport Before drawing each viewport, you must set the
top-left pixel where the viewport begins, the height and width properties for each
viewport, and the clip minimum and maximum If the clip minimum and maximum
values are not set between 0 and 1, your 3D models will not render properly:
Viewport CurrentViewport(int player){
Viewport viewport = new Viewport();
Rectangle safeRegion = TitleSafeRegion();
Vector2 startPixel = new Vector2((float)safeRegion.Left,
(float)safeRegion.Top);
Trang 13// get starting top left pixel for viewport
if (player == 1) // 2nd player - bottom startPixel.Y += (float)safeRegion.Height;
// assign viewport properties
viewport.X = (int)startPixel.X; // top left pixel X viewport.Y = (int)startPixel.Y; // top left pixel Y viewport.Width = safeRegion.Width; // pixel width
viewport.Height = safeRegion.Height; // pixel height
viewport.MinDepth = 0.0f; // depth is between viewport.MaxDepth = 1.0f; // 0 & 1 so models
// appear properly return viewport;
}
When multiple viewports are used, each one is rendered separately In effect, thesame scene is drawn more than once With careful planning, the same methods can beused for drawing your primitive objects and models Note also that when drawingwith multiple viewports, the viewport is set first before the viewport is cleared Thedrawing is performed afterward
Replace the existingDraw()method with this revision to draw all objects in eachviewport according to the view and perspective of each player:
protected override void Draw(GameTime gameTime){
for (int player = 0; player < NUMPLAYERS; player++) {
// set the viewport before clearing screen graphics.GraphicsDevice.Viewport = CurrentViewport(player);
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// draw objects DrawGround(player);
Trang 14For this example, you can use the alien models in the Models folder in the book’s
download To do this, obtain the alien0.fbx, alien1.fbx, and spaceA.bmp files from
the Models folder Create a Models folder in your project and reference the fbx files
from the Solution Explorer
To load these models and to control their transformations, declarations for the
model objects and their bone-transformation matrices are required at the top of the
game class:
Model alien0Model; Model alien1Model;
Matrix[] alien0Matrix; Matrix[] alien1Matrix;
The code used to load these two models and their accompanying transformation
matrices is contained in the InitializeAliens()method To initialize the models, add
this method to your game class:
To load the aliens when the program begins, add the call statement
InitializeAliens()to theInitialize()method:
InitializeAliens();
For this two-player game, one alien and its spaceship are controlled by each
player Each alien’s spaceship moves with the player’s camera To rotate the alien
about the Y axis—so it always points in the direction it is traveling—use the
follow-ing method to calculate the angle of direction based on the camera’sLookdirection:
float RotationAngle(Vector3 view, Vector3 position){
Vector3 look = view - position;
return (float)Math.Atan2((double)look.X, (double)look.Z);
}
To save on code, the same method is used to draw both aliens When these items
are rendered in a viewport, this method is called once for each model This process is
Trang 15repeated for each player For this example,alien0’s position and angle of tion is based on the first player’s camera.Alien1’s position and orientation is based
orienta-on the secorienta-ond player’s camera The view is adjusted for each player The rest of theroutine is identical to the routines you have already used in this book for drawingmodels
void DrawAliens(int player, Model model, int modelNum){
foreach (ModelMesh mesh in model.Meshes){
// 1: declare matrices Matrix world, scale, rotationY, translation, translationOrbit;
// 2: initialize matrices scale = Matrix.CreateScale(0.5f, 0.5f, 0.5f);
translation = Matrix.CreateTranslation(Vector3.Zero);
rotationY = Matrix.CreateRotationY(MathHelper.Pi);
translationOrbit = Matrix.CreateTranslation(0.0f, 0.0f, 1.0f); translation = Matrix.CreateTranslation( // one alien
cam[modelNum].position.X, // is located cam[modelNum].position.Y - 0.6f, // at each cam[modelNum].position.Z); // camera float angleY = RotationAngle(cam[modelNum].view,
cam[modelNum].position);
rotationY = Matrix.CreateRotationY(angleY);
// 3: build cumulative world matrix using I.S.R.O.T sequence // identity, scale, rotate, orbit(translate & rotate), translate world = scale * translationOrbit * rotationY * translation;
foreach (BasicEffect effect in mesh.Effects){
// 4: pass wvp to shader effect.View = cam[player].viewMatrix;
switch (modelNum){
case ALIEN0:
effect.World = alien0Matrix[mesh.ParentBone.Index] * world; break; case ALIEN1:
effect.World = alien1Matrix[mesh.ParentBone.Index] * world; break; }
effect.Projection = cam[player].projectionMatrix;
// 4b: set lighting effect.EnableDefaultLighting();