This week, we’ve got a good base for the TriangleShooter game. It shoots, scores, persists high score, has audio, it really gives you something to work off of. To make it easier to work from, this time around, we’re going to tweak a few things and refactor the source to be more readable and modifiable, which will result in a code base you can extend on your own to improve or re-imagine the game. There are still a few things I want to do to the game, as well, and this will get us in a good place to begin doing so. So let’s get started. You can prepare by grabbing the source code from last week over at the SkyDrive share.

Now before we get to the refactoring, there is one thing that I need to change. The player had been starting at (0,0), the top left of the play space, which was okay most of the time, but could get you killed if an enemy decided to spawn there. Since the enemies spawn around the edges, I want to start by moving the starting location to somewhere away from the edge, to give you a fighting chance at doing better in the game. To do this, we’ll update the initialization of the player in the NewGame function to the following:

Player NewGame Initialization
  1. player = new Player() { Avatar = txPlayer, Position = new Vector2(100f, 240f), Rotation = 0f };

Now we’re ready to start refactoring. The big thing we’re going to do here is move related blocks of code out into methods that we’ll call. If you look through the Update method, there are some blocks of code that can easily be pulled out to distinct methods. We’ll end up getting the following methods:

Update Methods
  1. UpdatePlayer();
  2.  
  3. UpdateEnemies();
  4.  
  5. SpawnEnemies(gameTime);
  6.  
  7. ShootBullets(gameTime);
  8.  
  9. UpdateBullets();

The source is a simple copy/paste out of Update, giving us the following method bodies:

UpdatePlayer
  1. private void UpdatePlayer()
  2. {
  3.     foreach (TouchLocation tl in TouchPanel.GetState())
  4.     {
  5.         if (tl.State == TouchLocationState.Pressed)
  6.         {
  7.             if (movementId == 0)
  8.             {
  9.                 movementId = tl.Id;
  10.             }
  11.         }
  12.  
  13.         if (tl.Id == movementId)
  14.         {
  15.             Vector2 direction = tl.Position - player.Position;
  16.  
  17.             if (direction.LengthSquared() > 100)
  18.             {
  19.                 direction.Normalize();
  20.  
  21.                 player.Rotation = (float)(Math.Atan2(direction.Y, direction.X));
  22.  
  23.                 player.Position += direction * 10;
  24.             }
  25.         }
  26.  
  27.         if (tl.State == TouchLocationState.Released)
  28.         {
  29.             if (tl.Id == movementId)
  30.             {
  31.                 movementId = 0;
  32.             }
  33.         }
  34.     }
  35. }
UpdateEnemies
  1. private void UpdateEnemies()
  2. {
  3.     foreach (Enemy enemy in enemies)
  4.     {
  5.         enemy.Rotation += ((float)Math.PI / 30f) % ((float)Math.PI * 2);
  6.  
  7.         Vector2 enemyDirection = player.Position - enemy.Position;
  8.  
  9.         if (enemyDirection.LengthSquared() > enemy.Speed * enemy.Speed)
  10.         {
  11.             enemyDirection.Normalize();
  12.  
  13.             enemy.Position += enemyDirection * enemy.Speed;
  14.         }
  15.  
  16.         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))
  17.         {
  18.             triangleColor = Color.Red;
  19.             isPlayerDead = true;
  20.             SaveToFile();
  21.         }
  22.     }
  23. }
SpawnEnemies
  1. private void SpawnEnemies(GameTime gameTime)
  2. {
  3.     timeToSpawn -= gameTime.ElapsedGameTime;
  4.  
  5.     if (timeToSpawn < TimeSpan.Zero)
  6.     {
  7.         Vector2 spawnPosition;
  8.         switch (random.Next(4))
  9.         {
  10.             case 0:
  11.                 spawnPosition = new Vector2(0, random.Next(480));
  12.                 break;
  13.             case 1:
  14.                 spawnPosition = new Vector2(800, random.Next(480));
  15.                 break;
  16.             case 2:
  17.                 spawnPosition = new Vector2(random.Next(800), 0);
  18.                 break;
  19.             case 3:
  20.                 spawnPosition = new Vector2(random.Next(800), 480);
  21.                 break;
  22.             default:
  23.                 spawnPosition = Vector2.Zero;
  24.                 break;
  25.         }
  26.  
  27.         switch (random.Next(4))
  28.         {
  29.             case 0:
  30.                 enemies.Add(new Enemy() { Position = spawnPosition, Rotation = 0f, Avatar = txEnemy3, Speed = 3f });
  31.                 break;
  32.             case 1:
  33.                 enemies.Add(new Enemy() { Position = spawnPosition, Rotation = 0f, Avatar = txEnemy4, Speed = 3.5f });
  34.                 break;
  35.             case 2:
  36.                 enemies.Add(new Enemy() { Position = spawnPosition, Rotation = 0f, Avatar = txEnemy5, Speed = 4f });
  37.                 break;
  38.             case 3:
  39.                 enemies.Add(new Enemy() { Position = spawnPosition, Rotation = 0f, Avatar = txEnemy6, Speed = 4.5f });
  40.                 break;
  41.             default:
  42.                 break;
  43.         }
  44.  
  45.         timeToSpawn = new TimeSpan(0, 0, 0, 0, random.Next(500) + 250);
  46.     }
  47. }
ShootBullets
  1. private void ShootBullets(GameTime gameTime)
  2. {
  3.     timeToShoot -= gameTime.ElapsedGameTime;
  4.  
  5.     if (timeToShoot <= TimeSpan.Zero)
  6.     {
  7.         timeToShoot = new TimeSpan(0, 0, 0, 0, ShootDelay);
  8.         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 });
  9.         fire.Play(.5f, 0f, 0f);
  10.     }            
  11. }

UpdateBullets
  1. private void UpdateBullets()
  2. {
  3.     foreach (Bullet b in bullets.ToList())
  4.     {
  5.         b.Position = new Vector2(b.Position.X + b.Speed * (float)Math.Cos(b.Rotation), b.Position.Y + b.Speed * (float)Math.Sin(b.Rotation));
  6.  
  7.         if (!graphics.GraphicsDevice.Viewport.Bounds.Contains(new Point((int)b.Position.X, (int)b.Position.Y)))
  8.         {
  9.             bullets.Remove(b);
  10.         }
  11.         else
  12.         {
  13.             foreach (Enemy enemy in enemies.ToList())
  14.             {
  15.                 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))
  16.                 {
  17.                     bullets.Remove(b);
  18.                     enemies.Remove(enemy);
  19.                     enemyHit.Play();
  20.                     score++;
  21.                     if (score > highScore)
  22.                     {
  23.                         highScore = score;
  24.                     }
  25.                     break;
  26.                 }
  27.             }
  28.         }
  29.     }
  30. }

This makes things easier to update because we won’t be mixing code that doesn’t belong in other blocks. For example, next week, I’ll be adding in code to support the accelerometer, so I can just change up the UpdatePlayer method, and nothing else needs changed!

Download the latest version of the source code.