In this chapter, you'll create your first game and explore some of the techniques discussed in the previous chapter. Your first game will be both simple and fun. It will run on a PC or on an Xbox 360 console.
But as trivial as the game might seem, it still must be well planned. Many projects fail because of too little effort in this phase, which leads to projects without a defined end, or projects that are finished but do not achieve their goals. Planning involves discovering the questions that must be answered before starting a game project. This book intends to teach making games the right way, so let's start right.
You're an intergalactic explorer, and you're stuck in an endless asteroid field! How long will you resist this rock rain? This is the main theme of your game, a frenetic challenge where you need to dodge a lot of asteroids that pass rapidly across the screen. It's like an Asteroids clone.
This is a simple and old game concept. Players need to avoid getting hit by meteors, and the longer they remain without a collision, the more points they get. Additionally, the quantity of meteors increases as time goes by, making the challenge harder and harder. To satisfy your curiosity, Figure 3-1 shows an example of a screen in your first game.
Right now, you'll clarify the game constraints and rules before you program anything. In the case of Rock Rain, they're simple:
Values such as the starting quantity of meteors and how long it should take before another meteor is added to the screen were not specified, because they're game parameters, rather than rules.
From a game programmer's point of view, things like spaceships, meteors, and scores are objects in your game. You should also detail these objects before you start programming anything.
Each object in the game has its own characteristics and behavior: the rocks fall, the player controls the spaceship, the score grows with the meteor count, and so on. The correct definition of the behavior and the state control of the game's objects are the most challenging tasks in game programming. That's why your game should be well thought out before you start to build anything.
You also need to consider the audio portions for the game. For Rock Rain, you'll have only three sound effects: music that plays while the game is active, a sound that plays when a new meteor is added to the game, and an explosion sound that plays when the player collides with a meteor. And as another feature, when a collision occurs, you'll make the player's Xbox 360 gamepad shake, to give an impact effect.
As you might have guessed, your first game is created as an XNA Windows Game project, which you've explored in the previous chapters. So, start by opening Visual Studio and creating a new Windows Game project called RockRain
. The Solution Explorer window will look like Figure 3-2.
As explained in Chapter 1, the Content folder is a special item in XNA projects. In this folder, you'll place all the game's assets, such as images, sounds, and so on—everything that should be loaded through the Content Pipeline.
You can download all the files used in this project from the book's details page at the Apress web site (http://www.apress.com).
Start by putting a background in your game. For a space game, nothing is better than an image of a galaxy! Add the file SpaceBackground.dds
to the Content
folder.
You should load this texture so that it fits the whole screen of the game. First, define the texture in your code. Add this attribute to your Game1
class:
// Background texture
private Texture2D backgroundTexture;
As you saw in the previous chapter, you'll load this texture and initialize the spriteBatch
object in the LoadContent
method:
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// Load all textures
backgroundTexture = content.Load<Texture2D>(" SpaceBackground");
}
You need to load the texture using the spriteBatch
object. Declare it in the Game1
class:
private SpriteBatch spriteBatch = null;
Now you can draw the background. Add the following code in the Draw
method of the Game1
class:
// Draw background texture in a separate pass.
spriteBatch.Begin();
spriteBatch.Draw(backgroundTexture,new Rectangle(0, 0,
graphics.GraphicsDevice.DisplayMode.Width,
graphics.GraphicsDevice.DisplayMode.Height),
Color.LightGray);
spriteBatch.End();
Run the game by pressing F5. If everything is correct, the result will look like Figure 3-3.
The player is represented in the game as a small spaceship that can be controlled using an Xbox 360 gamepad or a PC keyboard. The image of this spaceship is in the RockRain.png
file. Add it to the project inside the Content
folder. This texture contains the image of the player's spaceship and also the meteors that the player must avoid (see Figure 3-4).
As you did for the background, first declare the texture in the Game1
class:
private Texture2D meteorTexture;
Then load it in the LoadContent
method immediately after loading the background texture:
meteorTexture = content.Load<Texture2D>(" RockRain");
Note The graphics in this chapter and the next were built using SpriteLIB GPL, available from http://www.flyingyogi.com/fun/spritelib.html. SpriteLib GPL is a collection of static and animated grap hic objects (also commonly known as sprites).
Now you'll create a class that represents the player in the game. Add a new GameComponent
to the project, name the file Ship.cs
(as in Figure 3-5), and click OK. The new file added to the project contains a class that derives from GameComponent
. This game component will be visible in the game; therefore, it must be drawn. Derive from DrawableGameComponent
so that you have a Draw
method you can use to draw in the game.
This game component copies the texture region that contains the picture of the spaceship in the specified position. To accomplish that, it needs the texture where this picture is, the coordinates of the picture in this texture, and the coordinates on the screen where the picture must be rendered.
The component needs to move according to the Xbox 360 gamepad or PC keyboard controls. Also, it must remain within the screen boundaries; that is, the spaceship cannot disappear by leaving the defined borders of the game's window.
See that you have two steps of a DrawableGameComponent
well defined:
Draw
method, you copy the spaceship picture to the screen.Update
method, you update the screen according to the Xbox 360 gamepad or keyboard state.This class code follows:
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#endregion
namespace RockRain
{
/// <summary>
/// This is a game component that implements the player ship.
/// </summary>
public class Ship : Microsoft.Xna.Framework.DrawableGameComponent
{
protected Texture2D texture;
protected Rectangle spriteRectangle;
protected Vector2 position;
// Width and height of sprite in texture
protected const int SHIPWIDTH = 30;
protected const int SHIPHEIGHT = 30;
// Screen area
protected Rectangle screenBounds;
public Ship(Game game, ref Texture2D theTexture)
: base(game)
{
texture = theTexture;
position = new Vector2();
// Create the source rectangle.
// This represents where the sprite picture is in the surface
spriteRectangle = new Rectangle(31, 83, SHIPWIDTH, SHIPHEIGHT);
#if XBOX360
// On the 360, we need to be careful about the TV's "safe" area.
screenBounds = new Rectangle(
(int)(Game.Window.ClientBounds.Width * 0.03f),
(int)(Game.Window.ClientBounds.Height * 0.03f),
Game.Window.ClientBounds.Width -
(int)(Game.Window.ClientBounds.Width * 0.03f),
Game.Window.ClientBounds.Height -
(int)(Game.Window.ClientBounds.Height * 0.03f));#else
screenBounds = new Rectangle(0,0,
Game.Window.ClientBounds.Width,
Game.Window.ClientBounds.Height);
#endif
}
/// <summary>
/// Put the ship in your start position in the screen
/// </summary>
public void PutinStartPosition()
{
position.X = screenBounds.Width / 2;
position.Y = screenBounds.Height - SHIPHEIGHT;
}
/// <summary>
/// Update the ship position
/// </summary>
public override void Update(GameTime gameTime)
{
// Move the ship with the Xbox controller
GamePadState gamepadstatus = GamePad.GetState(PlayerIndex.One);
position.Y += (int)((gamepadstatus.ThumbSticks.Left.Y * 3) * −2);
position.X += (int)((gamepadstatus.ThumbSticks.Left.X * 3) * 2);
// Move the ship with the keyboard
KeyboardState keyboard = Keyboard.GetState();
if (keyboard.IsKeyDown(Keys.Up))
{
position.Y -= 3;
}
if (keyboard.IsKeyDown(Keys.Down))
{
position.Y += 3;
}
if (keyboard.IsKeyDown(Keys.Left))
{
position.X -= 3;
}
if (keyboard.IsKeyDown(Keys.Right))
{
position.X += 3;
}
// Keep the ship inside the screen
if (position.X < screenBounds.Left)
{
position.X = screenBounds.Left;
}
if (position.X > screenBounds.Width - SHIPWIDTH)
{
position.X = screenBounds.Width - SHIPWIDTH;
}
if (position.Y < screenBounds.Top)
{
position.Y = screenBounds.Top;
}
if (position.Y > screenBounds.Height - SHIPHEIGHT)
{
position.Y = screenBounds.Height - SHIPHEIGHT;
}
base.Update(gameTime);
}
/// <summary>
/// Draw the ship sprite
/// </summary>
public override void Draw(GameTime gameTime)
{
// Get the current sprite batch
SpriteBatch sBatch =
(SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
// Draw the ship
sBatch.Draw(texture, position, spriteRectangle, Color.White);
base.Draw(gameTime);
}
/// <summary>
/// Get the bound rectangle of ship position in screen
/// </summary>
public Rectangle GetBounds()
{
return new Rectangle((int)position.X, (int)position.Y,
SHIPWIDTH, SHIPHEIGHT);
}
}
}
Note that the Draw
method does not create a SpriteBatch
, as was created when you rendered the background texture. Ideally (following the "batch" concept), you should not keep creating and destroying SpriteBatch
objects, because this jeopardizes the application's performance. You could create a "global" SpriteBatch
and use it in your classes. However, this would create a coupling between your game components and a global attribute of a specific game (which is not desirable in object-oriented programming). XNA has an excellent solution to supply this global object and still allow you to reuse the component's code easily: the game service.
You can think of a game service as a service that is available to anyone who has a reference to a Game
. The idea behind it is that a component should be able to depend on certain types, or services, for its functionality. If that service isn't available, then the component can't operate correctly.
In this case, the Draw
method will look for an active SpriteBatch
directly in the GameServices
collection and use it to draw itself on the screen. Of course, you must add this SpriteBatch
to GameServices
. So, add the following code directly after creating the SpriteBatch
in the LoadContent
method of the Game1
class:
// Add the SpriteBatch service
Services.AddService(typeof(SpriteBatch), spriteBatch);
All the GameComponent
items in your game will use this SpriteBatch
.
Let's talk a little about this class. The Update
method checks the keyboard and Xbox 360 gamepad to update the Position
attribute and change the position of the ship on the screen. In this method, you also check if the ship is inside the screen boundaries. If not, the code puts the ship inside the visible area of the screen.
The GetBound
method just returns the rectangle that has the ship boundaries in the screen. You'll use this rectangle later to do collision tests with meteors. Finally, the PutinStartPosition
puts the ship in its initial position, centered horizontally in the bottom area of the screen. This method is called when you need to put the ship in its starting position; for example, when a new round starts.
Now let's test this GameComponent
. Create a Start
method that will be used to initialize the game objects (only the player for the moment), as follows:
/// <summary>
/// Initialize the game round
/// </summary>
private void Start()
{
// Create (if necessary) and put the player in start position
if (player == null)
{
// Add the player component
player = new Ship(this, ref meteorTexture);
Components.Add(player);
}
player.PutinStartPosition();
}
Observe that the player
attribute contains a reference to the player's GameComponent
. You also need to add this component to the components list of the Game
itself to be able to have XNA call the Draw
and Update
methods of this object in the game.
Finally, declare the player
attribute in the Game1
class:
private Ship player;
Now let's go back to the game's logic as a whole. The game's logic is normally implemented inside the Update
method of the Game
class. In this case, you can start with the following code:
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
gamepadstatus = GamePad.GetState(PlayerIndex.One);
keyboard = Keyboard.GetState();
if ((gamepadstatus.Buttons.Back == ButtonState.Pressed) ||
(keyboard.IsKeyDown(Keys.Escape)))
{
Exit();
}
// Start if not started yet
if (player == null)
{
Start();
}
// Update all other components
base.Update(gameTime);
}
Initially, this code checks if the user pressed the Esc key or the Back button on the Xbox 360 gamepad, which ends the game. Then, if necessary, the code starts the game through the Start
method.
One detail is still missing. The Draw
method of your game draws only the background. You also need to make it draw all the other GameComponent
items of the game, so add the following code immediately after the code that draws the background:
// Start rendering sprites
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
// Draw the game components (sprites included)
base.Draw(gameTime);
// End rendering sprites
spriteBatch.End();
Save and execute the code. Now you can move the spaceship around the screen with the Xbox 360 gamepad or the PC arrow keys. Observe that all the movement logic of the spaceship is being handled by its own component that you created, although XNA automatically calls its Update
method through the base.Update
call of the Game1
class. You'll create meteors following the same principle. The difference is that the player won't move the meteors.
The concepts you used to create a component for the player are the same that you'll use to create the meteors. The only difference is that the meteors' initial position and movement depend on a random factor. The meteors' code follows:
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
#endregion
namespace FirstGame
{
/// <summary>
/// This is a game component that implements the rocks the player must avoid.
/// </summary>
public class Meteor : Microsoft.Xna.Framework.DrawableGameComponent
{
protected Texture2D texture;
protected Rectangle spriteRectangle;
protected Vector2 position;
protected int Yspeed;
protected int Xspeed;
protected Random random;
// Width and height of sprite in texture
protected const int METEORWIDTH = 45;
protected const int METEORHEIGHT = 45;
public Meteor(Game game, ref Texture2D theTexture)
: base(game)
{
texture = theTexture;
position = new Vector2();
// Create the source rectangle.
// This represents where the sprite picture is in the surface
spriteRectangle = new Rectangle(20, 16, METEORWIDTH, METEORHEIGHT);
// Initialize the random number generator and put the meteor in
// its start position
random = new Random(this.GetHashCode());
PutinStartPosition();
}
/// <summary>
/// Initialize meteor position and velocity
/// </summary>
protected void PutinStartPosition()
{
position.X = random.Next(Game.Window.ClientBounds.Width - METEORWIDTH);
position.Y = 0;
Yspeed = 1 + random.Next(9);
Xspeed = random.Next(3) - 1;
}
/// <summary>
/// Allows the game component to draw your content in the game screen
/// </summary>
public override void Draw(GameTime gameTime)
{
// Get the current sprite batch
SpriteBatch sBatch =
(SpriteBatch) Game.Services.GetService(typeof(SpriteBatch));
// Draw the meteor
sBatch.Draw(texture, position, spriteRectangle, Color.White);
base.Draw(gameTime);
}
/// <summary>
/// Allows the game component to update itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
public override void Update(GameTime gameTime)
{
// Check if the meteor is still visible
if ((position.Y >= Game.Window.ClientBounds.Height) ||
(position.X >= Game.Window.ClientBounds.Width) || (position.X <= 0))
{
PutinStartPosition();
}
// Move meteor
position.Y += Yspeed;
position.X += Xspeed;
base.Update(gameTime);
}
/// <summary>
/// Check if the meteor intersects with the specified rectangle
/// </summary>
/// <param name="rect">test rectangle</param>
/// <returns>true, if has a collision</returns>
public bool CheckCollision(Rectangle rect)
{
Rectangle spriterect = new Rectangle((int)position.X, (int)position.Y,
METEORWIDTH, METEORHEIGHT);
return spriterect.Intersects(rect);
}
}
}
The PutinStartPosition
method puts the meteor in a random horizontal position at the top of the screen, and also obtains the vertical and horizontal displacement speed of the meteor, which each call of the class's Update
method refreshes.
A CheckCollision
method verifies if the rectangle that delimits the meteor intersects with a rectangle passed as a parameter. This will be the rectangle that delimits the position of the player's spaceship on the screen.
Now let's put the meteors on the screen. Add the following code in the Start
method of the Game1
class:
// Add the meteors
for (int i = 0; i < STARTMETEORCOUNT; i++)
{
Components.Add(new Meteor(this, ref meteorTexture));
}
The STARTMETEORCOUNT
constant defines the initial number of meteors that will appear in the game. Declare it in the Game1
class as follows:
private const int STARTMETEORCOUNT = 10;
Execute the program by pressing F5. Look at the beauty of the meteor rain. Note that each instance of the Meteor
component behaves in an independent way, just as the Ship
component does.
Your game components are nearly ready. Now you need to make them work together; that is, when the player's spaceship collides with a meteor, the game will restart.
So, just as you have a Start
method that initializes the game components, create a method called DoGameLogic
that executes the game logic itself. Right now, this method only iterates the game component list, to check if a meteor collided with the player's spaceship. If there's a collision, the meteors should be taken out of the game so that they can be re-created in their initial position. This code follows:
/// <summary>
/// Run the game logic
/// </summary>
private void DoGameLogic()
{
// Check collisions
bool hasCollision = false;
Rectangle shipRectangle = player.GetBounds();
foreach (GameComponent gc in Components)
{
if (gc is Meteor)
{
hasCollision = ((Meteor)gc).CheckCollision(shipRectangle);
if (hasCollision)
{
// Remove all previous meteors
RemoveAllMeteors();
// Let's start again
Start();
break;
}
}
}
}
/// <summary>
/// Remove all meteors
/// </summary>
private void RemoveAllMeteors()
{
for (int i = 0; i < Components.Count; i++)
{
if (Components[i] is Meteor)
{
Components.RemoveAt(i);
i--;
}
}
}
You should call the DoGameLogic
method inside the Update
method of the Game1
class, immediately before the line that contains the base.Update(gameTime)
call. This calls your game logic inside the game loop. Execute the program and see that when the spaceship collides with the meteor, the program puts all the objects in their initial position, and continues this loop until the user leaves the application.
Now let's make the player's life a little harder. In your game, a new meteor will be added after some time has passed. As the meteors behave in an independent way, you just need to add a new Meteor
component to the game, and it does all the rest. This is done with the method in the following code. Call this method inside the doGameLoop
method, after the foreach
loop.
/// <summary>
/// Check if it is time for a new rock!
/// </summary>
private void CheckforNewMeteor()
{
// Add a rock each ADDMETEORTIME
if ((System.Environment.TickCount - lastTickCount) > ADDMETEORTIME)
{
lastTickCount = System.Environment.TickCount;
Components.Add(new Meteor(this, ref meteorTexture));
rockCount++;
}
}
The ADDMETEORTIME
constant represents the interval, in milliseconds, at which a new meteor should be added. Declare it in the Game1
class as follows:
private const int ADDMETEORTIME = 5000;
This 5 seconds (or 5,000 milliseconds) setting is a "magic number," and you can change it to alter the game difficulty later.
Two new attributes store the number of meteors added (rockCount
) and the time to calculate the desired interval (lastTickCount
). Declare them as follows:
private const int ADDMETEORTIME = 5000;
private int lastTickCount;
private int rockCount;
You should initialize these attributes in the Start
method, so add the following code to this method:
// Initialize a counter
lastTickCount = System.Environment.TickCount;
// Reset rock count
rockCount = STARTMETEORCOUNT;
So, every 5 seconds, a new meteor is added to the game.
Run the game again, and see how long you can play without hitting a rock.
As you saw in Chapter 2, it is very simple to add music and sound effects to your games. For Rock Rain, you'll use two WAV files for sound effects and a MP3 file for background music Add the following files to the Content
folder:
Explosion.wav
is an explosion sound that plays when the player collides with a meteor.Backmusic.mp3
is the game's background music.Newmeteor.wav
plays when a new meteor is added to the game.Before you do anything else, declare the audio objects in the Game1
class:
// Audio stuff
private SoundEffect explosion;
private SoundEffect newMeteor;
private Song backMusic;
Initialize them in the LoadContent
method of the Game1
class:
// Load audio elements
explosion = Content.Load<SoundEffect>("explosion");
newMeteor = Content.Load<SoundEffect>("newmeteor");
backMusic = Content.Load<Song>("backmusic");
// Play the background music
MediaPlayer.Play(backMusic);
Also, add the following code inside the DoGameLogic
method, so that the explosion sound is played before calling the Start
method again:
explosion.Play();
Then add the following code inside CheckforNewMeteor
, immediately after the line that contains rockCount++
:
newMeteor.Play();
This plays a sound when a new meteor is added to the game.
Run the game again. You'll see how the sound effects make the game even more entertaining.
The Rock Rain scoreboard will show the current number of meteors on the screen. First, you need to create the game source and draw it in the game.
Then add a new SpriteFont
. Call it font
and add the following code to declare an object for it:
private SpriteFont gameFont;
Initialize the object in the LoadGraphicsContent
method, just as you did with the other contents, inside the if
statement, as follows:
// Load game font
gameFont = content.Load<SpriteFont>("Content//font");
The scoreboard is a typical GameComponent
, but to show that you don't need to create a component for it, you'll draw this scoreboard in the Draw
method of the Game1
class. You can draw it immediately after drawing the game sprites. Add the following code in the Draw
method of the Game1
class:
// Draw score
spriteBatch.Begin();
spriteBatch.DrawString(gameFont, "Rocks: " + rockCount.ToString(),
new Vector2(15, 15), Color.YellowGreen);
spriteBatch.End();
Notice that you used a separate spriteBatch
object to draw the scoreboard. This way, the steps to draw the background picture, the sprites, and the scoreboard are also separated in the video card, avoiding any possible "confusion" that the card might experience.
Caution Be careful with the use of third-party fonts in your games. Some fonts, such as TrueType and OpenType, are not royalty-free and have legal restrictions regarding their use. This is especially true of fonts used by Windows. You can find many free fonts on web sites across the Internet.
Your game is almost ready. Now let's add one more effect to the game: the vibration. When players collide with a meteor, in addition to the explosion sound, you'll make the Xbox 360 gamepad vibrate to simulate the impact.
As you saw in the previous chapter, you can start and finish the Xbox 360 gamepad vibration through the SetVibration
method. You're going to create a nonvisual GameComponent
that will help you with this effect.
Add a new GameComponent
to the project with the following code:
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
#endregion
namespace FirstGame
{
/// <summary>
/// This component helps shake your Xbox 360 gamepad
/// </summary>
public class SimpleRumblePad : Microsoft.Xna.Framework.GameComponent
{
private int time;
private int lastTickCount;
public SimpleRumblePad(Game game)
: base(game)
{
}
/// <summary>
/// Allows the game component to update itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
public override void Update(GameTime gameTime)
{
if (time > 0) {
int elapsed = System.Environment.TickCount - lastTickCount;
if (elapsed >= time)
{
time = 0;
GamePad.SetVibration(PlayerIndex.One, 0, 0);
}
}
base.Update(gameTime);
}
/// <summary>
/// Turn off the rumble
/// </summary>
protected override void Dispose(bool disposing)
{
GamePad.SetVibration(PlayerIndex.One, 0, 0);
base.Dispose(disposing);
}
/// <summary>
/// Set the vibration
/// </summary>
/// <param name="Time">Vibration time</param>
/// <param name="LeftMotor">Left Motor Intensity</param>
/// <param name="RightMotor">Right Motor Intensity</param>
public void RumblePad(int Time, float LeftMotor, float RightMotor)
{
lastTickCount = System.Environment.TickCount;
time = Time;
GamePad.SetVibration(PlayerIndex.One, LeftMotor, RightMotor);
}
}
}
In this class, the RumblePad
method receives the amount of time that the controller should vibrate and the vibration motor's intensity as parameters.
As usual, declare it in the Game1
class, as follows:
// Rumble effect
private SimpleRumblePad rumblePad;
Initialize it in the Initialize
method of the Game1
class:
rumblePad = new SimpleRumblePad(this);
Components.Add(rumblePad);
Make the controller vibrate immediately after executing the explosion sound, in the DoGameLogic
method:
// Shake!
rumblePad.RumblePad(500, 1.0f, 1.0f);
Congratulations—you've just finished your first game!
You know that XNA technology allows you to create games for the PC as well as the Xbox 360. If you wish to make a console version of Rock Rain, all you need to do is create a copy of this project for Xbox 360. Just right-click the RockRain
project in the Solution Explorer window and choose Create Copy of Project for Xbox 360, as shown in Figure 3-6. Compile the project, and it's ready to go. You immediately have a game that works on the Xbox 360.
However, not everything is that simple. First, to deploy your game to the Xbox 360, you need an XNA Creators Club subscription, which enables your PC and the correctly registered console to communicate. (Your subscription can be renewed annually or every three months.) Additionally, to deploy the game, your console must be connected to the Xbox LIVE network.
You also need to take into account the difference between TVs (used by the console games) and monitors (used by the PC games). With an ordinary PC monitor, you have access to all areas of the screen. With a TV, you're forced to use what is called the safe area, which is the area visible on the TV screen. In other words, not everything that you put on the screen is visible on an ordinary TV. Older TVs can display less of the space outside the safe area than ones made more recently. Flat-panel, plasma, and liquid crystal display (LCD) screens generally can show most of the "unsafe" area.
This leads you to a problem regarding the margin of the screen. As the player cannot leave the margin of the screen, knowing exactly where the visible margin of the screen is can be a problem. Normally, the game industry works with a 3 to 5 percent physical margin.
So, in your Ship
class, which represents the player's spaceship, add this code in the part where you calculated the size of the screen, in the class constructor:
#if XBOX360
// On the 360, we need to be careful about the TV's "safe" area.
screenBounds = new Rectangle((int)(Game.Window.ClientBounds.Width * 0.03f),
(int)(Game.Window.ClientBounds.Height * 0.03f),
Game.Window.ClientBounds.Width -
(int)(Game.Window.ClientBounds.Width * 0.03f),
Game.Window.ClientBounds.Height -
(int)(Game.Window.ClientBounds.Height * 0.03f));
#else
screenBounds = new Rectangle(0,0,Game.Window.ClientBounds.Width,
Game.Window.ClientBounds.Height);
#endif
Now, when you want to build the Xbox 360 project, compile the code that creates the rectangle that defines the screen margin with a size 3 percent smaller than the rectangle of a PC project, which takes all the monitor space. It's that simple.
In this chapter, you learned the basics of creating 2D games, and you went through a small project planning phase, focusing on the items that the game programmer and designer should have in mind before starting to write any code.
You also learned how to model your game using game components and create the game logic itself, modifying and testing the state of these components inside the game's loop. You saw that you can implement simple sprites using GameComponent
objects and take advantage of all the classes that XNA already offers.
You also saw how you can add sounds and vibration effects to your game, as well as use a conditional compilation to solve the TV safe area issue of video game consoles.
18.191.195.183