Adding Multiple Enemies with AI (TriangleShooter)

Adding Multiple Enemies with AI (TriangleShooter)

  • Comments 1

Another week, another article bringing us one step further in the creation of TriangleShooter. Last week, we did a bit of refactoring and broke Player and Enemy out into classes. At the time, this didn’t change anything in how the application worked, but this week, we’ll take advantage of it by adding in some basic AI, and then showing how easily you can scale out to more enemies. We’re going to be starting with the project we created worked on last week, so you can grab that from the SkyDrive Share.

The first thing we can do is make the enemy seem a bit more impressive. He was just sitting out there while we flew all around him just making fun of his inability to move. Well, let’s change that. We’ll begin by some basic animation. We’re not going into sprite frames or anything like that, a rotation should do. In the update method, we’ll add a line to update the enemy’s rotation. Let’s drop it in right before the collision detection. Rotation is handled as a float, in radians. That means that when the rotation is equal to 2*pi, it will be a single rotation. Since rotation is cyclical, and bounded between 0 and 2*pi, we can make use of the modulus operator to make sure that it doesn’t eventually exceed the boundary of what a float can handle.

Rotating the Enemy
  1. enemy.Rotation += ((float)Math.PI / 30f) % ((float)Math.PI * 2);

We’ll need to update the Draw function as well, to make use of the newly updated rotation. We’ll use the same format as we did for the player. The main difference will be that the origin of rotation for the enemy will be the center.

Drawing the Rotated Enemy
  1. spriteBatch.Draw(enemy.Avatar, enemy.Position, null, Color.White, enemy.Rotation, new Vector2(enemy.Avatar.Width, enemy.Avatar.Height) / 2, 1f, SpriteEffects.None, 0f);

If you run the application now, it works much like it did at the end of the last article, but now the enemy rotates. That simple change makes the visuals of the game quite a bit more interesting.  It also gives us a great view into how easy it is going to be to add in some additional AI.

image

Let’s jump right into that. We’ve already seen how easy it is to make the player move towards a touch point. Let’s do the same thing with the enemy, and set the destination to the player. That’s the basis for follow AI. Add the following lines to the Update method right after the line that updates the rotation.

Follow AI
  1. Vector2 enemyDirection = player.Position - enemy.Position;
  2.  
  3. if (enemyDirection.LengthSquared() > enemy.Speed * enemy.Speed)
  4. {
  5.     enemyDirection.Normalize();
  6.  
  7.     enemy.Position += enemyDirection * enemy.Speed;
  8. }

That’s it. We run the application, and now the enemy follows you around the screen. Programming is really easy, when you think about it.

image

The next step is to add more enemies. This is actually a lot easier than you might think. First, let’s look at how we are currently creating instances of the Enemy class.

Creating an Enemy
  1. enemy = new Enemy();
  2. enemy.Avatar = txEnemy6;
  3. enemy.Position = new Vector2(600f, 200f);
  4. enemy.Rotation = 0f;

As you can see, it takes four lines of code, and we aren’t even setting speed at this point. There are a couple of ways to condense this down, including creating a constructor, and using Object Initialization. In this case, let’s go ahead and use Object Initialization, which means that we will use the default constructor, then assign the values directly after in braces. I prefer this method in this case because when I read through the code, it is more explicit. You may prefer to use a constructor, which works just fine by me. Using Object Initialization, the previous code changes to the following:

Using Object Initialization
  1. enemy = new Enemy() { Avatar = txEnemy6, Position = new Vector2(600f, 200f), Rotation = 0f, Speed =  3f};

You’ll notice that I added Speed in there while I was at it.

With this setup, it’s much easier to instantiate a full new Enemy with just a single line of code.

We had been using a variable called enemy defined throughout the class. To allow for more enemies, we’ll make use of a generic collection called List. Replace the line towards the top of game1 defining enemy

Single Enemy
  1. Enemy enemy;

with a List of Enemy

List of Enemy
  1. List<Enemy> enemies;

and initialize it in the Initialize method.

Initializing enemies
  1. enemies = new List<Enemy>();

in the LoadContent method, replace the line

enemy initialization
  1. enemy = new Enemy() { Avatar = txEnemy6, Position = new Vector2(600f, 200f), Rotation = 0f, Speed = 3f };

with the following few lines. This will create three separate enemies, each with a different speed.

Adding enemies
  1. enemies.Add(new Enemy() { Avatar = txEnemy6, Position = new Vector2(600f, 200f), Rotation = 0f, Speed = 3f });
  2. enemies.Add(new Enemy() { Avatar = txEnemy6, Position = new Vector2(600f, 300f), Rotation = 0f, Speed = 3.5f });
  3. enemies.Add(new Enemy() { Avatar = txEnemy6, Position = new Vector2(600f, 400f), Rotation = 0f, Speed = 4f });

the Update method needs to add a foreach loop around the area we’re using enemy. It will look like this

Updating each Enemy
  1. foreach (Enemy enemy in enemies)
  2. {
  3.     enemy.Rotation += ((float)Math.PI / 30f) % ((float)Math.PI * 2);
  4.  
  5.     Vector2 enemyDirection = player.Position - enemy.Position;
  6.  
  7.     if (enemyDirection.LengthSquared() > enemy.Speed * enemy.Speed)
  8.     {
  9.         enemyDirection.Normalize();
  10.  
  11.         enemy.Position += enemyDirection * enemy.Speed;
  12.     }
  13.  
  14.     if (new Rectangle((int)enemy.Position.X, (int)enemy.Position.Y, txEnemy6.Width, txEnemy6.Height).Contains((int)player.Position.X, (int)player.Position.Y))
  15.     {
  16.         triangleColor = Color.Red;
  17.     }
  18. }

and a foreach around the enemy in Draw

Drawing each Enemy
  1. foreach (Enemy enemy in enemies)
  2. {
  3.     spriteBatch.Draw(enemy.Avatar, enemy.Position, null, Color.White, enemy.Rotation, new Vector2(enemy.Avatar.Width, enemy.Avatar.Height) / 2, 1f, SpriteEffects.None, 0f);
  4. }

And again, that’s all. A couple more simple changes, and we’ve added support for as many enemies as we want.

image

Next week, we’ll add in some spawning code, and add in the rest of the enemy graphics to add in some variety.

Download the latest version of the source code.

  • in the first part of this tutorial, I couldn't get the enemy to follow the player without setting a value to enemy.Speed, yet in the next paragraph you make it sound like you could get it to work without setting enemy.Speed? I don't understand this.

Page 1 of 1 (1 items)
Leave a Comment
  • Please add 8 and 7 and type the answer here:
  • Post