// Project: XnaBreakout, File: BreakoutGame.cs
// Namespace: XnaBreakout, Class: BreakoutGame
// Path: C:\code\XnaBook\XnaBreakout, Author: Abi
// Code lines: 16, Size of file: 298 Bytes
// Creation date: 21.11.2006 03:56
// Last modified: 26.11.2006 13:21
// Generated with Commenter by abi.exDream.com
#region Using directives
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using XnaBreakout.Helpers;
#endregion
namespace XnaBreakout
{
///
/// This is the main type for your game
///
partial class BreakoutGame : Microsoft.Xna.Framework.Game
{
#region Constants
///
/// Rectangles for our graphics, tested with the unit tests below.
///
static readonly Rectangle
GamePaddleRect = new Rectangle(39, 1, 93, 23),
GameBallRect = new Rectangle(1, 1, 36, 36),
GameBlockRect = new Rectangle(136, 1, 62, 27),
GameYouWonRect = new Rectangle(2, 39, 92, 21),
GameYouLostRect = new Rectangle(105, 39, 94, 21);
///
/// Ball speed multiplicator, this is how much screen space the ball will
/// travel each second.
///
const float BallSpeedMultiplicator = 0.85f;//1;//0.75f;//0.5f;1
///
/// How many block columns and rows are displayed?
///
const int NumOfColumns = 14,
NumOfRows = 12;
#endregion
#region Variables
GraphicsDeviceManager graphics;
ContentManager content;
Texture2D backgroundTexture, gameTexture;
AudioEngine audioEngine;
WaveBank waveBank;
SoundBank soundBank;
///
/// Resolution of our game.
///
int width, height;
///
/// Current paddle positions, 0 means left, 1 means right.
///
float paddlePosition = 0.5f;
///
/// Current ball position.
///
Vector2 ballPosition = new Vector2(0.5f, 0.95f-0.035f);
///
/// Ball speed vector.
///
Vector2 ballSpeedVector = new Vector2(0, 0);
///
/// Paddle, ball, block, etc. as sprite helpers.
///
SpriteHelper paddle, ball, block, youWon, youLost, background;
///
/// Level we are in and the current score.
/// Just updated in the windows title because we don't have
/// font support yet.
///
int level = 0, score = -1;
///
/// Wait until user presses space or A to start a level.
///
bool pressSpaceToStart = true, lostGame = false;
///
/// All blocks of the current play field. If they are
/// all cleared, we advance to the next level.
///
bool[,] blocks = new bool[NumOfColumns, NumOfRows];
///
/// Block positions for each block we have, initialized in Initialize().
///
Vector2[,] blockPositions = new Vector2[NumOfColumns, NumOfRows];
///
/// Bounding boxes for each of the blocks, also precalculated and checked
/// each frame if the ball collides with one of the blocks.
///
BoundingBox[,] blockBoxes = new BoundingBox[NumOfColumns, NumOfRows];
#endregion
#region Constructor
///
/// Create the breakout game
///
public BreakoutGame()
{
graphics = new GraphicsDeviceManager(this);
content = new ContentManager(Services);
} // BreakoutGame()
///
/// Initialize our game and load all graphics.
///
protected override void Initialize()
{
// Remember resolution
width = graphics.GraphicsDevice.Viewport.Width;
height = graphics.GraphicsDevice.Viewport.Height;
// Init all blocks, set positions and bounding boxes
for (int y = 0; y < NumOfRows; y++)
for (int x = 0; x < NumOfColumns; x++)
{
blockPositions[x, y] = new Vector2(
0.05f + 0.9f * x / (float)(NumOfColumns - 1),
0.066f + 0.5f * y / (float)(NumOfRows - 1));
Vector3 pos = new Vector3(blockPositions[x, y], 0);
Vector3 blockSize = new Vector3(
GameBlockRect.X/1024.0f, GameBlockRect.Y/768, 0);
blockBoxes[x, y] = new BoundingBox(
pos - blockSize/2,
pos + blockSize/2);
} // for for
// Start with level 1
StartLevel();
base.Initialize();
} // Initialize()
///
/// Load all graphics content (just our background texture).
/// Use this method to make sure a device reset event is handled correctly.
///
/// Load everything?
protected override void LoadGraphicsContent(bool loadAllContent)
{
if (loadAllContent)
{
// Load all our content
backgroundTexture = content.Load("SpaceBackground");
gameTexture = content.Load("BreakoutGame");
audioEngine = new AudioEngine("BreakoutSound.xgs");
waveBank = new WaveBank(audioEngine, "Wave Bank.xwb");
if (waveBank != null)
soundBank = new SoundBank(audioEngine, "Sound Bank.xsb");
// Create all sprites
paddle = new SpriteHelper(gameTexture, GamePaddleRect);
ball = new SpriteHelper(gameTexture, GameBallRect);
block = new SpriteHelper(gameTexture, GameBlockRect);
youWon = new SpriteHelper(gameTexture, GameYouWonRect);
youLost = new SpriteHelper(gameTexture, GameYouLostRect);
background = new SpriteHelper(backgroundTexture, null);
} // if
base.LoadGraphicsContent(loadAllContent);
} // LoadGraphicsContent(loadAllContent)
///
/// Unload graphic content if the device gets lost.
///
/// Unload everything?
protected override void UnloadGraphicsContent(bool unloadAllContent)
{
if (unloadAllContent == true)
{
content.Unload();
SpriteHelper.Dispose();
} // if
base.UnloadGraphicsContent(unloadAllContent);
} // UnloadGraphicsContent(loadAllContent)
#endregion
#region Start level
void StartLevel()
{
// Randomize levels, but make it more harder each level
for (int y = 0; y < NumOfRows; y++)
for (int x = 0; x < NumOfColumns; x++)
blocks[x, y] = RandomHelper.GetRandomInt(10) < level+1;
// Use the lower blocks only for later levels
if (level < 6)
for (int x = 0; x < NumOfColumns; x++)
blocks[x, NumOfRows - 1] = false;
if (level < 4)
for (int x = 0; x < NumOfColumns; x++)
blocks[x, NumOfRows - 2] = false;
if (level < 2)
for (int x = 0; x < NumOfColumns; x++)
blocks[x, NumOfRows - 3] = false;
// Halt game
ballSpeedVector = Vector2.Zero;
// Wait until user presses space or A to start a level.
pressSpaceToStart = true;
// Update title
Window.Title =
"XnaBreakout - Level " + (level+1) + " - Score " + Math.Max(0, score);
} // StartLevel
#endregion
#region Start and stop ball
///
/// Start new ball at the beginning of each game and when a ball is lost.
///
public void StartNewBall()
{
// Randomize direction, but always go up
ballSpeedVector =
new Vector2(RandomHelper.GetRandomFloat(-1, +1), -1);
ballSpeedVector.Normalize();
// Make sure game is started now
if (score < 0)
score = 0;
// If we lost the game and restarted, reset score!
if (lostGame)
{
// Game over, reset to level 0
level = 0;
score = 0;
lostGame = false;
StartLevel();
} // if
// Clear message
pressSpaceToStart = false;
} // StartNewBall()
///
/// Stop ball for menu and when game is over.
///
public void StopBall()
{
ballSpeedVector = new Vector2(0, 0);
pressSpaceToStart = true;
} // StopBall
#endregion
#region Update
GamePadState gamePad;
KeyboardState keyboard;
///
/// Update game input.
///
protected override void Update(GameTime gameTime)
{
// The time since Update was called last
float elapsed =
(float)gameTime.ElapsedGameTime.TotalSeconds;
// Get keyboard and gamepad states
keyboard = Keyboard.GetState();
gamePad = GamePad.GetState(PlayerIndex.One);
// Escape and back exit the game
if (keyboard.IsKeyDown(Keys.Escape) ||
gamePad.Buttons.Back == ButtonState.Pressed)
this.Exit();
// Move half way across the screen each second
float moveFactorPerSecond = 0.75f *
(float)gameTime.ElapsedRealTime.TotalMilliseconds / 1000.0f;
// Move left and right if we press the cursor or gamepad keys.
if (gamePad.DPad.Right == ButtonState.Pressed ||
gamePad.ThumbSticks.Left.X > 0.5f ||
keyboard.IsKeyDown(Keys.Right))
paddlePosition += moveFactorPerSecond;
if (gamePad.DPad.Left == ButtonState.Pressed ||
gamePad.ThumbSticks.Left.X < -0.5f ||
keyboard.IsKeyDown(Keys.Left))
paddlePosition -= moveFactorPerSecond;
// Make sure paddle stay between 0 and 1 (offset 0.05f for paddle width)
if (paddlePosition < 0.05f)
paddlePosition = 0.05f;
if (paddlePosition > 1 - 0.05f)
paddlePosition = 1 - 0.05f;
// Game not started yet? Then put ball on paddle.
if (pressSpaceToStart)
{
ballPosition = new Vector2(paddlePosition, 0.95f - 0.035f);
// Handle space
if (keyboard.IsKeyDown(Keys.Space) ||
gamePad.Buttons.A == ButtonState.Pressed)
{
StartNewBall();
} // if
} // if
else
{
// Check collisions
CheckBallCollisions(moveFactorPerSecond);
// Update ball position and bounce off the borders
ballPosition += ballSpeedVector *
moveFactorPerSecond * BallSpeedMultiplicator;
// Ball lost?
if (ballPosition.Y > 0.985f)
{
// Play sound
soundBank.PlayCue("PongBallLost");
// Show lost message, reset is done above in StartNewBall!
lostGame = true;
pressSpaceToStart = true;
} // if
// Check if all blocks are killed and if we won this level
bool allBlocksKilled = true;
for (int y = 0; y < NumOfRows; y++)
for (int x = 0; x < NumOfColumns; x++)
if (blocks[x, y])
{
allBlocksKilled = false;
break;
} // for for if
// We won, start next level
if (allBlocksKilled == true)
{
// Play sound
soundBank.PlayCue("BreakoutVictory");
lostGame = false;
level++;
StartLevel();
} // if
} // else
base.Update(gameTime);
} // Update(gameTime)
#endregion
#region Check ball collisions
///
/// Check ball collisions
///
void CheckBallCollisions(float moveFactorPerSecond)
{
// Check top, left and right screen borders
float MinYPos = 0.0235f;
if (ballPosition.Y < MinYPos)
{
ballSpeedVector.Y = -ballSpeedVector.Y;
// Move ball back into screen space
if (ballPosition.X < MinYPos)
ballPosition.X = MinYPos;
// Play hit sound
soundBank.PlayCue("PongBallHit");
} // if
float MinXPos = 0.018f;
if (ballPosition.X < MinXPos ||
ballPosition.X > 1 - MinXPos)
{
ballSpeedVector.X = -ballSpeedVector.X;
// Move ball back into screen space
if (ballPosition.X < MinXPos)
ballPosition.X = MinXPos;
if (ballPosition.X > 1 - MinXPos)
ballPosition.X = 1 - MinXPos;
// Play hit sound
soundBank.PlayCue("PongBallHit");
} // if
// Check for collisions with the paddles
// Construct bounding boxes to use the intersection helper method.
Vector2 ballSize = new Vector2(24 / 1024.0f, 24 / 768.0f);
BoundingBox ballBox = new BoundingBox(
new Vector3(ballPosition.X - ballSize.X / 2, ballPosition.Y - ballSize.Y / 2, 0),
new Vector3(ballPosition.X + ballSize.X / 2, ballPosition.Y + ballSize.Y / 2, 0));
Vector2 paddleSize = new Vector2(
GamePaddleRect.Width / 1024.0f, GamePaddleRect.Height / 768.0f);
BoundingBox paddleBox = new BoundingBox(
new Vector3(paddlePosition - paddleSize.X / 2, 0.95f-paddleSize.Y * 0.7f, 0),
new Vector3(paddlePosition + paddleSize.X / 2, 0.95f, 0));
// Ball hit paddle?
if (ballBox.Intersects(paddleBox))
{
// Bounce off in the direction vector from the paddle
ballSpeedVector.X += (ballPosition.X - paddlePosition) / (MinXPos * 3);
// Max to -1 and +1
if (ballSpeedVector.X < -1)
ballSpeedVector.X = -1;
if (ballSpeedVector.X > 1)
ballSpeedVector.X = 1;
// Bounce of the paddle
ballSpeedVector.Y = -1;// -ballSpeedVector.Y;
// Move away from the paddle
ballPosition.Y -= moveFactorPerSecond * BallSpeedMultiplicator;
// Normalize vector
ballSpeedVector.Normalize();
// Play sound
soundBank.PlayCue("PongBallHit");
} // if
// Ball hits any block?
for (int y = 0; y < NumOfRows; y++)
for (int x = 0; x < NumOfColumns; x++)
if (blocks[x, y])
{
// Collision check
if (ballBox.Intersects(blockBoxes[x, y]))
{
// Kill block
blocks[x, y] = false;
// Add score
score++;
// Update title
Window.Title =
"XnaBreakout - Level " + (level + 1) + " - Score " + score;
// Play sound
soundBank.PlayCue("BreakoutBlockKill");
// Bounce ball back, but first find out which side we hit.
// Start with left/right borders.
if (Math.Abs(blockBoxes[x, y].Max.X - ballBox.Min.X) <
moveFactorPerSecond)
{
ballSpeedVector.X = Math.Abs(ballSpeedVector.X);
// Also move back a little
ballPosition.X += (ballSpeedVector.X < 0 ? -1 : 1) *
moveFactorPerSecond;
} // else
else if (Math.Abs(blockBoxes[x, y].Min.X - ballBox.Max.X) <
moveFactorPerSecond)
{
ballSpeedVector.X = -Math.Abs(ballSpeedVector.X);
// Also move back a little
ballPosition.X += (ballSpeedVector.X < 0 ? -1 : 1) *
moveFactorPerSecond;
} // else
// Now check top/bottom borders
else if (Math.Abs(blockBoxes[x, y].Max.Y - ballBox.Min.Y) <
moveFactorPerSecond)
{
ballSpeedVector.Y = Math.Abs(ballSpeedVector.Y);
// Also move back a little
ballPosition.Y += (ballSpeedVector.Y < 0 ? -1 : 1) *
moveFactorPerSecond;
} // if
else if (Math.Abs(blockBoxes[x, y].Min.Y - ballBox.Max.Y) <
moveFactorPerSecond)
{
ballSpeedVector.Y = -Math.Abs(ballSpeedVector.Y);
// Also move back a little
ballPosition.Y += (ballSpeedVector.Y < 0 ? -1 : 1) *
moveFactorPerSecond;
} // else
else
ballSpeedVector *= -1;
// Go outa here, only handle 1 block at a time
break;
} // if
} // for for if
} // CheckBallCollisions()
#endregion
#region Draw
///
/// Draws all sprites and game components.
///
protected override void Draw(GameTime gameTime)
{
// Render background
background.Render();
SpriteHelper.DrawSprites(width, height);
// Render all game graphics
paddle.RenderCentered(paddlePosition, 0.95f);
ball.RenderCentered(ballPosition);
// Render all blocks
for (int y = 0; y < NumOfRows; y++)
for (int x = 0; x < NumOfColumns; x++)
if (blocks[x, y])
block.RenderCentered(blockPositions[x, y]);
if (pressSpaceToStart &&
score >= 0)
{
if (lostGame)
youLost.RenderCentered(0.5f, 0.65f, 2);
else
youWon.RenderCentered(0.5f, 0.65f, 2);
} // if
// Draw all sprites on the screen
SpriteHelper.DrawSprites(width, height);
base.Draw(gameTime);
} // Draw(gameTime)
#endregion
#region Start game
public static void StartGame()
{
using (BreakoutGame game = new BreakoutGame())
{
game.Run();
} // using
} // StartGame()
#endregion
#region Unit tests
#if DEBUG
#region Test delegate
///
/// Test delegate helper
///
delegate void TestDelegate();
#endregion
#region TestPongGame class
///
/// Helper class to test the BreakoutGame
///
class TestBreakoutGame : BreakoutGame
{
TestDelegate testLoop;
public TestBreakoutGame(TestDelegate setTestLoop)
{
testLoop = setTestLoop;
} // TestPongGame(setTestLoop)
protected override void Draw(GameTime gameTime)
{
base.Draw(gameTime);
testLoop();
} // Draw(gameTime)
} // class TestBreakoutGame
#endregion
#region StartTest
static TestBreakoutGame testGame;
static void StartTest(TestDelegate testLoop)
{
testGame = new TestBreakoutGame(testLoop);
testGame.Run();
testGame.Dispose();
} // RunTest(testLoop)
#endregion
#region TestSounds
public static void TestSounds()
{
StartTest(
delegate
{
string soundToPlay = "";
if (testGame.keyboard.IsKeyDown(Keys.Space))
soundToPlay = "PongBallHit";
if (testGame.keyboard.IsKeyDown(Keys.LeftControl))
soundToPlay = "PongBallLost";
if (testGame.keyboard.IsKeyDown(Keys.LeftAlt))
soundToPlay = "BreakoutVictory";
if (testGame.keyboard.IsKeyDown(Keys.LeftShift))
soundToPlay = "BreakoutBlockKill";
if (String.IsNullOrEmpty(soundToPlay) == false)
{
testGame.soundBank.PlayCue(soundToPlay);
System.Threading.Thread.Sleep(500);
} // if
});
} // TestSounds()
#endregion
#region TestGameSprites
public static void TestGameSprites()
{
StartTest(
delegate
{
if (testGame.keyboard.IsKeyDown(Keys.LeftControl))
testGame.youLost.RenderCentered(0.5f, 0.65f, 2);
else
testGame.youWon.RenderCentered(0.5f, 0.65f, 2);
});
} // TestGameSprites()
#endregion
#region TestBallCollisions
public static void TestBallCollisions()
{
StartTest(
delegate
{
testGame.Window.Title = "XnaBreakout - Press 1-5 to start collision tests";
// Start specific collision scene based on the user input.
if (testGame.keyboard.IsKeyDown(Keys.D1))
{
// First test, just collide with screen border
testGame.ballPosition = new Vector2(0.9f, 0.5f);
testGame.ballSpeedVector = new Vector2(1, 1);
testGame.ballSpeedVector.Normalize();
testGame.pressSpaceToStart = false;
} // if
else if (testGame.keyboard.IsKeyDown(Keys.D2))
{
// Second test, straight on collision with paddle
testGame.ballPosition = new Vector2(0.6f, 0.8f);
testGame.ballSpeedVector = new Vector2(1, 1);
testGame.ballSpeedVector.Normalize();
testGame.paddlePosition = 0.7f;
testGame.pressSpaceToStart = false;
} // if
else if (testGame.keyboard.IsKeyDown(Keys.D3))
{
// Advanced test to check if we hit the edge of paddle
testGame.ballPosition = new Vector2(0.9f, 0.4f);
testGame.ballSpeedVector = new Vector2(1, -0.5f);
testGame.ballSpeedVector.Normalize();
testGame.paddlePosition = 0.29f;
testGame.pressSpaceToStart = false;
} // if
else if (testGame.keyboard.IsKeyDown(Keys.D4))
{
// Advanced test to check if we hit the edge of paddle
testGame.ballPosition = new Vector2(0.9f, 0.4f);
testGame.ballSpeedVector = new Vector2(1, -0.5f);
testGame.ballSpeedVector.Normalize();
testGame.paddlePosition = 0.42f;
testGame.pressSpaceToStart = false;
} // if
else if (testGame.keyboard.IsKeyDown(Keys.D4))
{
// And finally test collision with blocks of current level
testGame.StartLevel();
testGame.pressSpaceToStart = false;
testGame.ballPosition = new Vector2(0.9f, 0.4f);
testGame.ballSpeedVector = new Vector2(1, -0.5f);
testGame.ballSpeedVector.Normalize();
} // if
});
} // TestBallCollisions()
#endregion
#endif
#endregion
} // class BreakoutGame
} // namespace XnaBreakout