7. Updated GameplayScreen Class
When you click "new game" in the main menu, the GameplayScreen is the screen that loads and the GameplayScreen class is where all of the game action occurs. The following sections cover how the GameplayScreen class manages the game objects, collision detection, and scoring.
7.1. GamePlay Screen Initialization
Now we are ready to modify the GameplayScreen class to manage the game objects. We declare the object instances needed:
//Game objects
GameStatusBoard statusBoard;
List<AlienGameObject> enemies;
int maxEnemies = 5;
UserGameObject heroShip;
int maxMissiles = 3;
//Indicates to draw game over frame;
bool drawGameOverFrame = false ;
The maxMissiles and maxEnemies
variable are not a constant because we may want to change it dynamically
during the game as part of the game play. Otherwise, one UserGameObject named heroShip and a List of AlienGameObjects are the other key components of the game. Another potential modification would be to increase the number of AlienGameObjects in the game as the score gets higher to make it more interesting. Otherwise a player will get bored if nothing changes.
Next we load and initialize assets in LoadContent():
public override void LoadContent()
{
if (content == null)
content = new ContentManager(ScreenManager.Game.Services, "Content");
gameFont = content.Load<SpriteFont>("gamefont");
alienShooterSpriteSheet = content.Load<SpriteSheet>("Sprites/AlienShooterSpriteSheet");
backgroundTexture = content.Load<Texture2D>("Textures/background");
backgroundPosition = new Vector2(0, 34);
//Get a pointer to the entire screen Rectangle
screenRect = ScreenManager.GraphicsDevice.Viewport.Bounds;
//Initialize Enemies collection
enemies = new List<AlienGameObject>();
for (int i = 0; i < maxEnemies; i++)
{
enemies.Add(new AlienGameObject(alienShooterSpriteSheet, "spaceship", screenRect));
}
//Initialize Player Object
heroShip = new UserGameObject(alienShooterSpriteSheet, "heroship", screenRect, maxMissiles);
heroShip.Position = new Vector2(screenRect.Width / 2, 720);
heroShip.LoadContent(content);
//Initialize Status Board
statusBoard = new GameStatusBoard(gameFont);
statusBoard.LoadContent(content);
// A real game would probably have more content than this sample, so
// it would take longer to load. We simulate that by delaying for a
// while, giving you a chance to admire the beautiful loading screen.
Thread.Sleep(1000);
// once the load has finished, we use ResetElapsedTime to tell the game's
// timing mechanism that we have just finished a very long frame, and that
// it should not try to catch up.
ScreenManager.Game.ResetElapsedTime();
}
By breaking out our game assets into objects it
greatly unclutters the code in GameplayScreen class. As an example,
initializing the enemies object is a matter of passing in the texture information for the animations:
//Initialize Enemies collection
enemies = new List<AlienGameObject>();
for (int i = 0; i < maxEnemies; i++)
{
enemies.Add(new AlienGameObject(alienShooterSpriteSheet, "spaceship", screenRect));
}
7.2. GameplayScreen Update and Draw Methods
Addingsupport for the heroShip and the AlienGameObject to the GameplayScreen.Update method is pretty straightforward now that we have nice objects that manage work for us:
public override void Update(GameTime gameTime, bool otherScreenHasFocus,
bool coveredByOtherScreen)
{
if (IsActive)
{
if (!statusBoard.GameOver)
{
CheckForCollisions();
heroShip.Update(gameTime);
statusBoard.Update(gameTime);
for (int i = 0; i < maxEnemies; i++)
{
enemies[i].Update(gameTime);
}
}
}
base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
}
If StatusBoard.GameOver is not true, i.e.
the hero ship has lives available, the game action continues. Otherwise,
the code is straightforward, calling Update for each object. Notice the call to the CheckForCollisions method. We cover collision detection in the next sub section.
Adding support for user input is just as easy by adding a call to heroShip.HandleIpnut(input) just after the else in the GameplayScreen.HandleInput method. For the GameplayScreen.Draw method the Draw method is called for each object. If StatusBoard.GameOver is true, the Draw method is not called for the enemies any further and the game is over.
public override void Draw(GameTime gameTime)
{
ScreenManager.GraphicsDevice.Clear(ClearOptions.Target,
Color.SlateBlue, 0, 0);
// Our player and enemy are both actually just text strings.
SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
spriteBatch.Begin();
//Draw Background
spriteBatch.Draw(backgroundTexture, backgroundPosition, Color.White);
//Draw Status Board
statusBoard.Draw(gameTime, spriteBatch);
//Draw Hero Ship
heroShip.Draw(gameTime, spriteBatch);
//Draw enemies
if (!statusBoard.GameOver)
{
for (int i = 0; i < maxEnemies; i++)
{
enemies[i].Draw(gameTime, spriteBatch);
}
}
spriteBatch.End();
// If the game is transitioning on or off, fade it out to black.
if (TransitionPosition > 0)
ScreenManager.FadeBackBufferToBlack(1f - TransitionAlpha);
}
8. Collision Detection and Memory Management
In the GameplayScreen.Update method, there is a call to the CheckForCollisions
method. This method detects collisions between inflight missiles and
enemy alien ships (a score) as well as collisions between enemy alien
ships and the hero ship (lose a life). Here is the code for the CheckForCollisions method:
private void CheckForCollisions()
{
//Checking for two major collisions
//1 - Has an in flight missile intersected an alien spaceship - score 5 pts
for (int i = 0; i < heroShip.MaxNumberofMissiles; i++)
if (heroShip.Missiles[i].Alive)
for (int j = 0; j < maxEnemies; j++)
if ((enemies[j].Alive) &&
(enemies[j].BoundingRect.Intersects(heroShip.Missiles[i].BoundingRect)))
{
statusBoard.Score += 5;
enemies[j].ResetGameObject();
heroShip.Missiles[i].ResetGameObject();
}
//2 - Has an alien spaceship intersected the hero ship - deduct a life
for (int j = 0; j < maxEnemies; j++)
if ((enemies[j].Alive) && (enemies[j].Position.Y > 600) &&
(enemies[j].BoundingRect.Intersects(heroShip.BoundingRect)))
{
statusBoard.Lives -= 1;
for (int i = 0; i < maxEnemies; i++)
enemies[i].ResetGameObject();
for (int i = 0; i < heroShip.MaxNumberofMissiles; i++)
heroShip.Missiles[i].ResetGameObject();
}
}
For detecting a hit by a missile by an alien ship,
each missile's bounding box must be compared with each enemy alien
ship's bounding box. Same goes for detecting a collision between an
enemy ship and the hero ship to lose a life. Each one must be compared
every frame for the most part. This means that every time two objects
are compared, which could be every frame, two new bounding boxes must be
constructed. Remember the bounding box includes a position for the
bounding box as well as height and width. Here is the code to return a
bounding box in the GameObject base class as a refresher:
public virtual Rectangle BoundingRect
{
get
{
return new Rectangle((int)Position.X, (int)Position.Y,
SpriteAnimationSpriteSheet.SourceRectangle(SpriteName + 0).Width,
SpriteAnimationSpriteSheet.SourceRectangle(SpriteName + 0).Height);
}
}
In a mobile application you want to try to minimize
memory allocations to reduce garbage collection activity. In a mobile
game, it is especially important to watch memory allocations. As an
example, when an enemy is destroyed by a hit, the AlienGameObject for that enemy has its Alive
property set to false. We could instead set the object to null and then
instantiate a new object but that just wastes CPU cycles on garbage
collection.
Another way to minimize CPU cycles is to only do work if needed. Notice in the CheckforCollisions method that the if statements are structured to only perform work and get a BoundingRect
when needed. As an example, an enemy alien ship can only intersect the
hero ship after it has fallen about two/thirds of the way down the
screen so a check is made to only perform the collision calculations if
the alien ship is below 600 pixels on the screen.
if ((enemies[j].Alive) && (enemies[j].Position.Y > 600) &&
(enemies[j].BoundingRect.Intersects(heroShip.BoundingRect)))
Part of game development is always looking
for ways to do things smartly. For myself, it is one of the most
enjoyable parts of the effort.