Posts
  • File → New Project

    Persisting the High Score Between Games (TriangleShooter)

    • 0 Comments

    Last week, we added scoring, and keeping track of the high score over a set of games. The problem is, when you exit the game for any reason, you lose your high score. That’s because everything is unloaded from memory, so when you start your game over, it has nothing to start from. This week, we’ll save your high score to a file, so we can load it back up when you restart your game. To begin, we’ll load the code from last week over at the SkyDrive Share.

    When working with files, we’ll need to add a few libraries. On the Windows Phone, applications have access to a special type of storage called IsolatedStorage, which is a file store that is accessible only by your application. Additionally, we will be storing our high score in an XML format, and accessing it with LINQ. To work with these, we’ll the following three include statements:

    Using Statements
    1. using System.IO;
    2. using System.IO.IsolatedStorage;
    3. using System.Xml.Linq;

    Once we’ve got these, we’ll just need to set up two simple methods. We simply need to save to the file, and load from the file. We’ll then just need to update the game to call the load method when it starts, and the save method at the end of each game.

    SaveToFile
    1. public void SaveToFile()
    2. {
    3.     string fileName = "SavedState.xml";
    4.  
    5.     using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
    6.     {
    7.         using (IsolatedStorageFileStream writeStream = new IsolatedStorageFileStream(fileName, FileMode.Create, store))
    8.         {
    9.             using (StreamWriter writer = new StreamWriter(writeStream))
    10.             {
    11.                 XDocument doc = new XDocument(
    12.                     new XDeclaration("1.0", "utf-8", "yes"),
    13.                     new XElement("State",
    14.                         new XElement("HighScore", highScore.ToString())
    15.                         )
    16.                      );
    17.  
    18.                 writer.Write(doc.ToString());
    19.             }
    20.         }
    21.     }
    22. }

    At the beginning of the SaveToFile method, we choose a file name to save the game state. You then open the Isolated Storage and open a file stream into it. We use FileMode.Create because we just want to overwrite a file if it’s there, and make a new one if it isn’t. The StreamWriter lets us write directly into the file we created in Isolated Storage. We then create a basic XML file that contains the high score inside of a State element.

    LoadFromFile
    1. public void LoadFromFile()
    2. {
    3.     string fileName = "SavedState.xml";
    4.  
    5.     try
    6.     {
    7.         using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
    8.         {
    9.             using (IsolatedStorageFileStream readStream = new IsolatedStorageFileStream(fileName, FileMode.Open, store))
    10.             {
    11.                 using (StreamReader reader = new StreamReader(readStream))
    12.                 {
    13.                     string stateXml = reader.ReadToEnd();
    14.  
    15.                     XDocument doc = XDocument.Parse(stateXml);
    16.  
    17.                     var q = from c in doc.Descendants("State")
    18.                             select (string)c.Element("HighScore");
    19.                     
    20.                     int i = 0;
    21.  
    22.                     foreach (string s in q)
    23.                     {
    24.                         if (int.TryParse(s, out i))
    25.                         {
    26.                             highScore = i;
    27.                         }
    28.                     }
    29.                 }
    30.             }
    31.         }
    32.     }
    33.     catch (IsolatedStorageException)
    34.     {
    35.     }
    36. }

    To load from the file, we use the same file name we defined when we saved our state. We then open the Isolated Storage using a StreamReader, and load its contents into a XDocument. This lets us use LINQ to pull in the elements that we saved. We walk through the elements named HighScore, and parse its contents into the highScore variable. The whole thing is wrapped in a try catch block because the file might not be there, or maybe there is something wrong with the file. We don’t want that to crash the game, so we just let it go.

    Now we need to use these methods. First of all, we just need to load in the file in the Initalize method.

    Initialize
    1. protected override void Initialize()
    2. {
    3.     random = new Random();
    4.  
    5.     highScore = 0;
    6.  
    7.     LoadFromFile();
    8.  
    9.     base.Initialize();
    10. }

    And finally, we need to save it. You could put it in every time the high score is beaten, but file I/O is expensive, so this could cause performance problems. Since we are really storing game high scores, why not drop it into the update method when the player dies.

    Code Snippet
    1. if (new Rectangle((int)enemy.Position.X - enemy.Avatar.Width / 2, (int)enemy.Position.Y - enemy.Avatar.Height / 2, enemy.Avatar.Width, enemy.Avatar.Height).Contains((int)player.Position.X, (int)player.Position.Y))
    2. {
    3.     triangleColor = Color.Red;
    4.     isPlayerDead = true;
    5.     SaveToFile();
    6. }

    And there you go, your high score is saved across application launches. Remember that if you restart the emulator, your isolated storage will be blown away, so you’ll lose your high score. This will also happen in you uninstall the game and then redeploy.

    Next week, we’ll be adding sound effects, so stay tuned.

    Download the latest version of the source code.

  • File → New Project

    Scoring (TriangleShooter)

    • 0 Comments

    Time to add scoring! To add a competitive edge to the game, let’s add in a running total of how many enemies you’ve shot. We’ll also keep track of the high score across games so you can try to beat your last score. To do this, we’ll make use of a Sprite Font. A Sprite Font is a font specially prepared by taking a set of characters and converting them to textures. So let’s get started. To begin, we’ll want to grab the code from last week over at the SkyDrive share.

    The first thing that we’ll want to do is create a new Sprite Font. Right-click on the TriangleShooterContent project, and choose Sprite Font. Under name, type “Font.spritefont”, and then click Add. It will open in the editor, in all it’s XML glory. As you can see by looking at the file, it describes a specific version of a font. You define the Font’s name, size, spacing, whether to use kerning or not, the style, a default character, and the portion of the font to include. The document is well commented, and is easy enough to understand. The one thing that might catch you up is the CharacterRegions. Because games require a lot of efficiency, you want to minimize any overhead possible. This includes characters from a font you wouldn’t use. This is especially important when you want to use non-Latin characters, such as Chinese or Japanese, because they won’t be included in the default Character Region. If you’re going to be using a lot of non-Latin characters, I suggest you take a look at the Localization Sample on AppHub.

    We’ll change two things. First, change the FontName field to Kootenay. Second, change the Size field to 32. This will give us a readable font for the phone.

    Next, we’ll need to set up our variables. We’ll be counting the number of enemies you shoot, so well use an integer named score, and an integer named highScore to keep track of the record. We’ll also need a variable to hold our font.

    Variable Declaration
    1. int score;
    2. int highScore;
    3.  
    4. SpriteFont font;

    We’ll initialize the highScore in the Initialize method

    Initialize
    1. highScore = 0;

    And the score in the NewGame method.

    NewGame
    1. score = 0;

    Then load up the font in the LoadContent method

    LoadContent
    1. font = Content.Load<SpriteFont>("Font");

    Now we’re ready to start keeping score. Inside of the Update method, where we do the collision for bullets, we add a few lines to a successful collision. We increment the score, then check if the score is greater than the high score. If that’s the case, we update the high score to equal the current score. All together, the updated bullet collision detection part of the Update method looks like the following

    Bullet Collision
    1. foreach (Bullet b in bullets.ToList())
    2. {
    3.     b.Position = new Vector2(b.Position.X + b.Speed * (float)Math.Cos(b.Rotation), b.Position.Y + b.Speed * (float)Math.Sin(b.Rotation));
    4.  
    5.     if (!graphics.GraphicsDevice.Viewport.Bounds.Contains(new Point((int)b.Position.X, (int)b.Position.Y)))
    6.     {
    7.         bullets.Remove(b);
    8.     }
    9.     else
    10.     {
    11.         foreach (Enemy enemy in enemies.ToList())
    12.         {
    13.             if (new Rectangle((int)enemy.Position.X - enemy.Avatar.Width / 2, (int)enemy.Position.Y - enemy.Avatar.Height / 2, enemy.Avatar.Width, enemy.Avatar.Height).Contains((int)b.Position.X, (int)b.Position.Y))
    14.             {
    15.                 bullets.Remove(b);
    16.                 enemies.Remove(enemy);
    17.                 score++;
    18.                 if (score > highScore)
    19.                 {
    20.                     highScore = score;
    21.                 }
    22.                 break;
    23.             }
    24.         }
    25.     }
    26. }

    Finally, we add a couple of lines to the Draw method. We use the spriteBatch.DrawString method. The High score is left aligned, so we just tell it to draw at Vector2.Zero, the top left position. For the Score, we want to right align, which means we have to set the offset based on the width of the string. To do this, we use the font.MeasureString function, and subtract it from the width of the Viewport. The lines look like this:

    Draw
    1. spriteBatch.DrawString(font, score.ToString(), new Vector2(graphics.GraphicsDevice.Viewport.Width, 0), Color.White, 0f, new Vector2(font.MeasureString(score.ToString()).X, 0), 1f, SpriteEffects.None, 1f);
    2.  
    3. spriteBatch.DrawString(font, "High: " + highScore.ToString(), Vector2.Zero, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 1f);

    With those changes, we now have scoring, and the updated game looks like this:

    image

    When you die, the score resets, but the high score remains. If you reset the game, though, you��ll lose your high score. That’s why next week we’ll be looking at persistent storage, and save the high score to a file we can load when the game starts.

    Download the latest version of the source code.

  • File → New Project

    Refactoring and Death Behavior (TriangleShooter)

    • 0 Comments

    This time around, we’ll be doing a bit of refactoring to make the code easier to deal with, and adding in some death behavior. To begin with, we’ll need to start with the code from last week, so go ahead and pick that up from the SkyDrive share.

    To begin with, let’s update the Initialize and LoadContent methods, and move some of the setup into a new method called NewGame. This will allow us to reset the game back to it’s initial state more easily, such as when you get killed by running into an enemy.

    Initialize
    1. protected override void Initialize()
    2. {
    3.     random = new Random();
    4.  
    5.     base.Initialize();
    6. }

    LoadContent
    1. protected override void LoadContent()
    2. {
    3.     // Create a new SpriteBatch, which can be used to draw textures.
    4.     spriteBatch = new SpriteBatch(GraphicsDevice);
    5.  
    6.     txPlayer = Content.Load<Texture2D>("Triangle");
    7.     txBullet = Content.Load<Texture2D>("Bullet");
    8.     txEnemy3 = Content.Load<Texture2D>("Enemy-3");
    9.     txEnemy4 = Content.Load<Texture2D>("Enemy-4");
    10.     txEnemy5 = Content.Load<Texture2D>("Enemy-5");
    11.     txEnemy6 = Content.Load<Texture2D>("Enemy-6");
    12.  
    13.     NewGame();
    14. }

    NewGame
    1. private void NewGame()
    2. {
    3.     triangleColor = Color.White;
    4.  
    5.     timeToSpawn = new TimeSpan(0, 0, 0, 0, 1000);
    6.     timeToShoot = new TimeSpan(0, 0, 0, 0, ShootDelay);
    7.  
    8.     player = new Player() { Avatar = txPlayer, Position = Vector2.Zero, Rotation = 0f };
    9.     bullets = new List<Bullet>();
    10.  
    11.     enemies = new List<Enemy>();
    12.     enemies.Add(new Enemy() { Avatar = txEnemy3, Position = new Vector2(600f, 200f), Rotation = 0f, Speed = 3f });
    13.     enemies.Add(new Enemy() { Avatar = txEnemy4, Position = new Vector2(600f, 300f), Rotation = 0f, Speed = 3.5f });
    14.     enemies.Add(new Enemy() { Avatar = txEnemy5, Position = new Vector2(600f, 400f), Rotation = 0f, Speed = 4f });
    15. }

    If we update the collision detection, we can call NewGame now if the collision happens, which means we can reset the game with ease. Also, while I was setting this up, I found a bug in the code based around the origin of the drawing and the rectangle we were using for collision. The updated code in the Update method looks like the following:

    Updated Collision Detection
    1. if (new Rectangle((int)enemy.Position.X - enemy.Avatar.Width / 2, (int)enemy.Position.Y - enemy.Avatar.Height / 2, enemy.Avatar.Width, enemy.Avatar.Height).Contains((int)player.Position.X, (int)player.Position.Y))
    2. {
    3.     triangleColor = Color.Red;
    4.     NewGame();
    5. }

    Now, when you die, the game resets. It might make more sense to freeze the game, and wait for the player to choose to continue before resetting the game. To do that, let’s create a new variable to say whether the player is alive or not. Let’s call it isPlayerDead

    isPlayerDead
    1. bool isPlayerDead;

    And initialize it to false in the NewGame method

    Initializing isPlayerDead
    1. isPlayerDead = false;

    Then check if the player is dead in the Update method

    Update
    1. if (isPlayerDead)
    2. {
    3.     if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
    4.     {
    5.         NewGame();
    6.     }
    7. }
    8. else
    9. {
    10.     // Everything else in Update
    11. }

    This means that the Collision detection from above needs to be updated once more to set the boolean rather than calling NewGame directly.

    Collision Detection
    1. if (new Rectangle((int)enemy.Position.X - enemy.Avatar.Width / 2, (int)enemy.Position.Y - enemy.Avatar.Height / 2, enemy.Avatar.Width, enemy.Avatar.Height).Contains((int)player.Position.X, (int)player.Position.Y))
    2. {
    3.     triangleColor = Color.Red;
    4.     isPlayerDead = true;
    5. }

    And we’ve now got a game that actually ends when you die, and let’s you start a new game. Sounds like we’re ready for scoring. We’ll add that in next week. See you then.

    Download the latest version of the source code.

  • File → New Project

    Adding Shooting (TriangleShooter)

    • 0 Comments

    Let’s make TriangleShooter a better game. This time around, we’ll be putting the shooter in TriangleShooter, by adding in the ability for the player to shoot. We’ll be starting with last week’s code, so you can go over and grab that from my SkyDrive share.

    I tried a few different ways of making the player shoot in my development of the game, and the best one I have found so far is to have you always laying out a steady stream of shots. Towards the end of the series, I’ll be doing some play mechanics tweaking, and we can investigate some of the other ways, but that’s how I’m going to do things in this article.

    If you’re going to shoot, you need a bullet. You can get my highly stylized, square bullet from the SkyDrive share. To begin with, download that, then add it into the TriangleShooterContent project by dragging it and dropping it in there. We’ll also need a class to handle the bullet, which is going to look familiar. Create a new class in the TriangleShooter project named Bullet.cs, and set the content to the following:

    Bullet.cs
    1. using Microsoft.Xna.Framework;
    2. using Microsoft.Xna.Framework.Graphics;
    3.  
    4. namespace TriangleShooter
    5. {
    6.     class Bullet
    7.     {
    8.         public Texture2D Avatar { get; set; }
    9.         public Vector2 Position { get; set; }
    10.         public float Rotation { get; set; }
    11.         public float Speed { get; set; }
    12.     }
    13. }

    We’ll set up the variables at the top of the game class to hold the texture, the list of Bullets, and to monitor the passing time to shoot, along with a constant to tweak the timer a bit.

    Variables for Bullets
    1. const int ShootDelay = 250;
    2. TimeSpan timeToShoot;
    3.  
    4. List<Bullet> bullets;
    5. Texture2D txBullet;

    In the Initialize method, we set up the list for the bullets, and set the time to shoot from the constant value we set up.

    Initialize
    1. bullets = new List<Bullet>();
    2.  
    3. timeToShoot = new TimeSpan(0, 0, 0, 0, ShootDelay);

    And add some code into the LoadContent method to load in the texture.

    LoadContent
    1. txBullet = Content.Load<Texture2D>("Bullet");

    And now we’ve got everything we need. We only have the Update and Draw to go. The Update is more interesting, so let’s start there.

    Update
    1. timeToShoot -= gameTime.ElapsedGameTime;
    2.  
    3. if (timeToShoot <= TimeSpan.Zero)
    4. {
    5.     timeToShoot = new TimeSpan(0, 0, 0, 0, ShootDelay);
    6.     bullets.Add(new Bullet() { Avatar = txBullet, Position = new Vector2(player.Position.X + player.Avatar.Width * (float)Math.Cos(player.Rotation), player.Position.Y + player.Avatar.Height * (float)Math.Sin(player.Rotation)), Rotation = player.Rotation, Speed = 15f });
    7.  
    8. }
    9.  
    10. foreach (Bullet b in bullets.ToList())
    11. {
    12.     b.Position = new Vector2(b.Position.X + b.Speed * (float)Math.Cos(b.Rotation), b.Position.Y + b.Speed * (float)Math.Sin(b.Rotation));
    13.  
    14.     if (!graphics.GraphicsDevice.Viewport.Bounds.Contains(new Point((int)b.Position.X, (int)b.Position.Y)))
    15.     {
    16.         bullets.Remove(b);
    17.     }
    18.     else
    19.     {
    20.         foreach (Enemy enemy in enemies.ToList())
    21.         {
    22.             if (new Rectangle((int)enemy.Position.X - enemy.Avatar.Width / 2, (int)enemy.Position.Y - enemy.Avatar.Height / 2, enemy.Avatar.Width, enemy.Avatar.Height).Contains((int)b.Position.X, (int)b.Position.Y))
    23.             {
    24.                 bullets.Remove(b);
    25.                 enemies.Remove(enemy);
    26.                 break;
    27.             }
    28.         }
    29.     }
    30. }

    The first part of the code is similar to what we do to spawn enemies. We subtract the elapsed time from the remaining time, and if the time is less than zero, we spawn a new bullet, setting it’s position to the front of the player and the rotation to the players rotation. We then update all existing bullets. Because we may need to remove them from the master list, we make a copy in the foreach by using the .ToList() function. We move them in the direction based on their rotation, and see if they have left the bounds of the screen. If they have, we remove them from the update list. To optimize things here, we should do something like create a list of decommissioned bullets, and reuse them rather than creating new ones each time, but we won’t do that this time around. If they are within the bounds of the screen, we check collision with each of the enemies, again using the .ToList() function because we might need to remove items. If they collide, we remove both the bullet, and the enemy that collided with it.

    This is all we need to do, but we do need to draw the bullets. Luckily, this is a simple process, just add a few lines into Draw.

    Draw
    1. foreach (Bullet b in bullets)
    2. {
    3.     spriteBatch.Draw(b.Avatar, b.Position, null, Color.White, b.Rotation, new Vector2(b.Avatar.Width / 2, b.Avatar.Height / 2), 1f, SpriteEffects.None, 0f);
    4. }

    And now we have shooting!

    image

    Next week, we’ll do some refactoring to make the code easier to work with, and see if we can add in some death behavior.

    Download the latest version of the source code.

  • File → New Project

    Spawning More Enemies (TriangleShooter)

    • 0 Comments

    Time for my favorite part of Monday – Another article improving my TriangleShooter game! Last week, I promised you that I would spawn some more enemies, and that I would add in some other enemy types to make it more visually appealing. The great news is, both of those things are pretty easy. We’ll get started with the code base from last week, so you can grab that from my SkyDrive share.

    We can start by adding in some additional enemy types. To begin with, we’ll drag and drop the rest of the Enemies from the Enemies file from SkyDrive into the Content project with the first enemy. We’ll then create some variables to hold the textures.

    Textures for Enemy
    1. Texture2D txEnemy3;
    2. Texture2D txEnemy4;
    3. Texture2D txEnemy5;
    4. Texture2D txEnemy6;

    We’ll then load them from the content project in the LoadContent method.

    Loading the Enemy Textures
    1. txEnemy3 = Content.Load<Texture2D>("Enemy-3");
    2. txEnemy4 = Content.Load<Texture2D>("Enemy-4");
    3. txEnemy5 = Content.Load<Texture2D>("Enemy-5");
    4. txEnemy6 = Content.Load<Texture2D>("Enemy-6");

    We can try this out by changing the lines further down in LoadContent to make use of the new textures.

    Creating Diverse Enemies
    1. enemies.Add(new Enemy() { Avatar = txEnemy3, Position = new Vector2(600f, 200f), Rotation = 0f, Speed = 3f });
    2. enemies.Add(new Enemy() { Avatar = txEnemy4, Position = new Vector2(600f, 300f), Rotation = 0f, Speed = 3.5f });
    3. enemies.Add(new Enemy() { Avatar = txEnemy5, Position = new Vector2(600f, 400f), Rotation = 0f, Speed = 4f });

    image

    Next up, we need to start spawning some more enemies. What we’re looking to do here is to bring in new enemies every so often. The faster you spawn the enemies, the more difficult the game would be, so you can play with the amount of time between spawns. In order to track them, though, we’ll need a variable to track how long we have left until the next spawn. Since we’ve got some different enemy types, we may as well add in some randomization, so we’ll create a variable to handle randomness as well. As always, we’ll drop the variable up at the top of the game file.

    Spawn Timer and Randomization
    1. TimeSpan timeToSpawn;
    2.  
    3. Random random;

    And then we initialize them down in Initalize()

    Initializing Spawn and Random
    1. timeToSpawn = new TimeSpan(0, 0, 0, 0, 1000);
    2.  
    3. random = new Random();

    This sets up timeToSpawn as one second. We’ll count down until this reaches zero, then spawn another enemy. Luckily, there’s a easy way to get elapsed game time in the Update Method with the gameTime argument. We’ll subtract the elapsed game time from the timeToSpawn variable, then check to see if it is less than or equal to zero. If it is, we create a new enemy and add it to the enemies list. To add variety, we can pick a random edge, and spawn the enemy somewhere along it so that it doesn’t just always come from the same spot. Finally, we’ll reset the timeToSpawn so that we can spawn another. All of this will go in the Update method after we check the enemy collision.

    Spawning More Enemies
    1. timeToSpawn -= gameTime.ElapsedGameTime;
    2.  
    3. if (timeToSpawn < TimeSpan.Zero)
    4. {
    5.     Vector2 spawnPosition;
    6.     switch (random.Next(4))
    7.     {
    8.         case 0:
    9.             spawnPosition = new Vector2(0, random.Next(480));
    10.             break;
    11.         case 1:
    12.             spawnPosition = new Vector2(800, random.Next(480));
    13.             break;
    14.         case 2:
    15.             spawnPosition = new Vector2(random.Next(800), 0);
    16.             break;
    17.         case 3:
    18.             spawnPosition = new Vector2(random.Next(800), 480);
    19.             break;
    20.         default:
    21.             spawnPosition = Vector2.Zero;
    22.             break;
    23.     }
    24.  
    25.     switch (random.Next(4))
    26.     {
    27.         case 0:
    28.             enemies.Add(new Enemy() { Position = spawnPosition, Rotation = 0f, Avatar = txEnemy3, Speed = 3f });
    29.             break;
    30.         case 1:
    31.             enemies.Add(new Enemy() { Position = spawnPosition, Rotation = 0f, Avatar = txEnemy4, Speed = 3.5f });
    32.             break;
    33.         case 2:
    34.             enemies.Add(new Enemy() { Position = spawnPosition, Rotation = 0f, Avatar = txEnemy5, Speed = 4f });
    35.             break;
    36.         case 3:
    37.             enemies.Add(new Enemy() { Position = spawnPosition, Rotation = 0f, Avatar = txEnemy6, Speed = 4.5f });
    38.             break;
    39.         default:
    40.             break;
    41.     }
    42.  
    43.     timeToSpawn = new TimeSpan(0, 0, 0, 0, random.Next(500) + 250);
    44. }

    image

    There’s just so many of them!

    Next week, we’ll add in shooting! Until this point, we were just a triangle moving around, but now we’ll finally be a triangle that shoots!

    Download the latest version of the source code.

Page 3 of 8 (37 items) 12345»