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