Chapter 7. Basic Artificial Intelligence

Artificial intelligence, huh? It probably sounds a little bit scary and pretty cool at the same time. We touched on the concept of artificial intelligence in previous chapters, but now let’s take a look at what artificial intelligence really is.

Since the beginning of the computing age, researchers have pondered and debated ways to make machines act more like humans and/or give them some form of artificial intelligence. The biggest problem with the entire line of artificial intelligence science is that there really is no way to define intelligence. What makes somebody or something intelligent? That’s an excellent question, and perhaps one that we will never be able to answer fully. Numerous other questions crop up as well. How do you define typical human behavior? What forms of human behavior constitute intelligence? What forms of human behavior are worthy of replication in machines?

You could argue that the application you have written is “intelligent” because the sprites animate on their own (that is, the user doesn’t have to tell them to continually animate). So, they must be intelligent, right? Others would argue that they are not intelligent, though, because they don’t “do” anything; they just sit there and spin. Even in this example, where it’s clear that the sprites aren’t really intelligent, you can start to see how this area of research is inherently ambiguous.

In this line of science, it’s a blessing and a curse that the idea of creating artificially intelligent beings is so fascinating to humans. It’s a blessing because that’s what drives this science to begin with: researchers and casual observers alike are so interested in the possibilities in this field that more and more money and time are spent on artificial intelligence every year.

At the same time, it’s a curse because that fascination, dating from the early days of civilization, has led to the dramatization of highly advanced artificially intelligent beings in books, movies, and beyond. The expectations in this field are set so high by Hollywood and authors alike that there may never be a way for science to catch up to what is depicted in the latest science fiction.

The Turing Test

Alan Turing, widely regarded as the father of modern computer science, invented one of the most famous methods for determining whether or not a machine truly is intelligent. Turing called this method the Imitation Game, but universally it is known as the Turing Test.

Essentially, a Turing Test begins with a human sitting at a keyboard. Using the keyboard, the user interrogates both a computer and another human. The identities of the other subjects are not disclosed to the interrogator. If the interrogator is unable to determine which one is the computer and which one is the human, the computer used in the test is deemed “intelligent.” Although it seems simplistic, programming something that would be able to fool somebody regardless of the line of questioning is extremely difficult.

How does that apply to what we’re talking about with XNA? Well, even though the Turing Test wasn’t a video game, the same principle is behind the essence of nearly all artificial intelligence as related to video games. When programming a computer-controlled entity in any game, the idea is to make that entity play so much like a human that a real human opponent wouldn’t know the difference.

That’s definitely easier said than done, and we aren’t going to get to that level in this game. However, you can clearly see that if you used a Turing Test as your standard, there’s no way that your current application would cut it.

So, what’s the next step? Let’s program some basic movement for your automated sprites, and then we can look at taking things a step further with some basic artificial intelligence algorithms.

Creating Sprites at Random Intervals

This chapter picks up with the code that you finished writing in Chapter 6. Open that project and use it throughout this chapter.

You have already created a sprite manager that draws and updates all the sprites in your application. However, right now all you have is a handful of skull ball sprites that are created when the application starts. Even worse, those sprites don’t move—they just sit there and animate. That just isn’t going to cut it; you need some action and excitement in this game. In this section, you’ll add some code that will create automated sprites at random intervals and send them flying onto the screen to force the player to move around and work a little to avoid hitting them.

Rather than creating the objects in waves or all at once, you want to create them at random intervals. This adds a bit of variety to the game and also serves to keep the player guessing. The first thing you need to do is create some variables that will help you define how often to create your automated sprites.

First, to handle the random factor in your game, create the following variable at the class level in your Game1 class:

public Random rnd { get; private set;}

Then, initialize the Random object in the constructor of the Game1 class:

rnd = new Random(  );

You now have a Random variable that you’ll use for all random aspects of your game. When using random number generators, it’s important to make sure that you don’t create multiple random number generators inside a tight loop. This is because if you create multiple random number generators within a close enough time frame, there is a chance that they will be created with the same seed. The seed is what the random number generators use to determine which numbers are generated and in which order. As you can probably guess, having multiple random number generators with the same seed would be a bad thing; you could potentially end up with the same list of numbers being generated by each, and then your randomness would be thrown out the window.

One way to avoid this is to have only one random number generator object in your application and reuse that object for all random numbers. Otherwise, just make sure that you create the random number generators in areas of the application that won’t be executed within a short time frame.

System.Random really isn’t the greatest of random number generation tools, but it will have to do for now.

Next, add to the SpriteManager class some class-level variables that will be used to spawn sprites:

int enemySpawnMinMilliseconds = 1000;
int enemySpawnMaxMilliseconds = 2000;
int enemyMinSpeed = 2;
int enemyMaxSpeed = 6;

These two sets of variables represent the minimum number of seconds and the maximum number of seconds to wait to spawn a new enemy, and the minimum and maximum speeds of those enemies. The next step is to use these two variables in your SpriteManager class to spawn enemies at some random interval between these two variables and at random speeds between your two speed threshold values.

Next, you need to get rid of the code that created the AutomatedSprites that didn’t move. Because you’ll now be periodically spawning new enemies, you don’t need those test sprites anymore. The code to create those objects is in the LoadContent method of your SpriteManager class. Once you remove the code that creates the AutomatedSprites, the code in the LoadContent method of your SpriteManager class should look like this:

protected override void LoadContent(  )
{
    spriteBatch = new SpriteBatch(Game.GraphicsDevice);

    player = new UserControlledSprite(
        Game.Content.Load<Texture2D>(@"Images/threerings"),
        Vector2.Zero, new Point(75, 75), 10, new Point(0, 0),
        new Point(6, 8), new Vector2(6, 6));

    base.LoadContent(  );
}

At this point, you’ll want to make the game window a bit larger so you have more room to work with. Add this code at the end of the constructor in your Game1 class:

graphics.PreferredBackBufferHeight = 768;
graphics.PreferredBackBufferWidth = 1024;

Randomly Spawning Sprites

All right, let’s spawn some sprites. You want to make your sprites spawn at somewhat random intervals, and you want them to spawn from the top, left, right, and bottom sides of the screen. For now, you’ll just have them traveling in a straight direction across the screen, but they’ll do so at varying speeds.

You need to let your SpriteManager class know when to spawn the next enemy sprite. Create a class-level variable in your SpriteManager class to store a value indicating the next spawn time:

int nextSpawnTime = 0;

Next, you need to initialize the variable to your next spawn time. Create a separate method that will set the next spawn time to some value between the spawn time thresholds represented by the class-level variables you defined previously in the SpriteManager class:

private void ResetSpawnTime(  )
{
    nextSpawnTime = ((Game1)Game).rnd.Next(
        enemySpawnMinMilliseconds,
        enemySpawnMaxMilliseconds);
}

You’ll then need to call your new ResetSpawnTime method from the Initialize method of your SpriteManager class, so the variable is initialized when the game starts. Add the following line at the end of the Initialize method of the SpriteManager class, just before the call to base.Initialize:

ResetSpawnTime(  );

Now you need to use the GameTime variable in the SpriteManager’s Update method to determine when it’s time to spawn a new enemy. Add this code to the beginning of the Update method:

nextSpawnTime −= gameTime.ElapsedGameTime.Milliseconds;
if (nextSpawnTime < 0)
{
    SpawnEnemy(  );

    // Reset spawn timer
    ResetSpawnTime(  );
}

This code first subtracts the elapsed game time in milliseconds from the NextSpawnTime variable (i.e., it subtracts the amount of time that has passed since the last call to Update). Once the NextSpawnTime variable is less than zero, your spawn timer expires, and it’s time for you to unleash the fury of your new enemy upon the pitiful human player—err…I mean…it’s time to spawn an AutomatedSprite. You spawn a new enemy via the SpawnEnemy method, which you’ll write in just a moment. Then, you reset the NextSpawnTime to determine when a new enemy will spawn again.

The SpawnEnemy method will need to, well, spawn an enemy. You’ll be choosing a random starting position for the enemy, at the left, right, top, or bottom of the screen. You’ll also be choosing a random speed for the enemy based on the speed threshold variables in the Game1 class. To add the enemy sprite to the game, all you need to do is add a new AutomatedSprite to your SpriteList variable. Add the SpawnEnemy to your code as follows:

private void SpawnEnemy(  )
{
    Vector2 speed = Vector2.Zero;
    Vector2 position = Vector2.Zero;

    // Default frame size
    Point frameSize = new Point(75, 75);

    // Randomly choose which side of the screen to place enemy,
    // then randomly create a position along that side of the screen
    // and randomly choose a speed for the enemy
    switch (((Game1)Game).rnd.Next(4))
    {
        case 0: // LEFT to RIGHT
            position = new Vector2(
                -frameSize.X, ((Game1)Game).rnd.Next(0,
                Game.GraphicsDevice.PresentationParameters.BackBufferHeight
                - frameSize.Y));

            speed = new Vector2(((Game1)Game).rnd.Next(
                enemyMinSpeed,
                enemyMaxSpeed), 0);
            break;

        case 1: // RIGHT to LEFT
            position = new
                Vector2(
                Game.GraphicsDevice.PresentationParameters.BackBufferWidth,
                ((Game1)Game).rnd.Next(0,
                Game.GraphicsDevice.PresentationParameters.BackBufferHeight
                - frameSize.Y));

            speed = new Vector2(-((Game1)Game).rnd.Next(
               enemyMinSpeed, enemyMaxSpeed), 0);
            break;

        case 2: // BOTTOM to TOP
            position = new Vector2(((Game1)Game).rnd.Next(0,
                Game.GraphicsDevice.PresentationParameters.BackBufferWidth
                - frameSize.X),
                Game.GraphicsDevice.PresentationParameters.BackBufferHeight);

            speed = new Vector2(0,
               -((Game1)Game).rnd.Next(enemyMinSpeed,
               enemyMaxSpeed));
            break;

        case 3: // TOP to BOTTOM
            position = new Vector2(((Game1)Game).rnd.Next(0,
                Game.GraphicsDevice.PresentationParameters.BackBufferWidth
                - frameSize.X), -frameSize.Y);

            speed = new Vector2(0,
                ((Game1)Game).rnd.Next(enemyMinSpeed,
                enemyMaxSpeed));
            break;
    }

    // Create the sprite
    spriteList.Add(
        new AutomatedSprite(Game.Content.Load<Texture2D>(@"imagesskullball"),        position, new Point(75, 75), 10, new Point(0, 0),
        new Point(6, 8), speed, "skullcollision"));
}

First, this method creates variables for the speed and position of the soon-to-be-added sprite. Next, the speed and position variables are set by randomly choosing in which direction the new sprite will be heading. Then, the sprite is created and added to the list of sprites. The frameSize variable defined at the top of the method is used to determine how far to offset the sprite from all sides of the window.

Compile and run the application at this point, and you’ll find that it’s looking more and more like a real game. The enemy sprites are spawned from each edge of the screen, and they head across the screen in a straight line at varying speeds (see Figure 7-1).

Randomly generated enemies attacking us!
Figure 7-1. Randomly generated enemies attacking us!

OK, quiz time. Let’s see how well you understand what’s going on here, and what problems you might run into. Let the game run for a minute or so without user input. Some objects may hit the user-controlled sprite and disappear, but most of them will fly harmlessly off the edge of the screen. What’s the problem, and how can you fix it?

If you said that you’re not deleting your objects, you’re really picking this up and understanding game concepts—great job! If you’re confused by that, let me explain: when an automated sprite hits the user-controlled sprite, the automated sprite is removed from the list of sprites and destroyed. However, when an automated sprite makes it all the way across the screen, it simply disappears; you aren’t doing anything with that object to destroy it and the player can no longer collide with the object to destroy it, either. The result is that these objects will continue forever outside the field of play, and in every frame, you will continue to update and draw each of them—not to mention running pointless collision checks on them. This problem will grow worse and worse until at some point it affects the performance of your game.

Irrelevant Objects

This brings us to a fundamental element of game development. One thing that is absolutely essential to any game is the definition of what makes an object “irrelevant.” An object is considered irrelevant when it can no longer affect anything in the game.

Irrelevancy is handled differently in each game. Some games allow objects to leave the screen and then ultimately return. Other games destroy objects before they ever leave the screen. An example of the latter is seen in most renditions of the game Asteroids. In most versions of Asteroids, when shooting from one side of the screen to the other, a ship’s bullet actually disappears before it leaves the screen. This is because the shot has a maximum distance that it can travel before it is deleted from the game. Although I’m not a huge fan of that functionality (yeah, I like guns that can shoot as far as I can see), the developers made the call that a bullet wouldn’t be able to reach from one side of the screen to the other. You can argue the merits of that choice, but that’s not the point. The point is that the developers decided what constituted irrelevancy for those bullets, and when they reached that point, they deleted them.

It’s interesting to look at the Asteroids game further, because while its developers decided to remove bullets before they hit the edge of the screen, they did the opposite with the asteroids themselves: the asteroids are recycled immediately when they leave the screen, and they pop into view on another side of the screen. Again, you can argue about whether you like this behavior and whether it’s realistic, but that’s not the point. One of the great things about game development is that you control the world, and you can do whatever you want. The developers of Asteroids made that call, and hey, who can argue with one of the best classic games ever made, right?

Currently, you aren’t doing anything about your irrelevant sprites. Your sprites leave the screen and never have any chance to return (you only have logic for the sprites to move forward, not to turn or double back), and therefore at that point they become irrelevant. Once one of your automated sprites leaves the screen, you need to detect that and get rid of it so that you don’t waste precious processor time updating and drawing objects that will never come into play in the game again.

To do this, you need to add a method in your Sprite base class that will accept a Rectangle representing the window rectangle and return true or false to indicate whether the sprite is out of bounds. Add the following method to your Sprite class:

public bool IsOutOfBounds(Rectangle clientRect)
{
    if (position.X < -frameSize.X ||
        position.X > clientRect.Width ||
        position.Y < -frameSize.Y ||
        position.Y > clientRect.Height)
    {
        return true;
    }

    return false;
}

Next, you’ll need to add to the Update method of your SpriteManager class some code that will loop through the list of AutomatedSprites and call the IsOutOfBounds method on each sprite, deleting those that are out of bounds. You already have code in the Update method of your SpriteManager class that loops through all your AutomatedSprite objects. The current code should look something like this:

// Update all sprites
for (int i = 0; i < spriteList.Count; ++i)
{
    Sprite s = spriteList[i];

    s.Update(gameTime, Game.Window.ClientBounds);

    // Check for collisions
    if (s.collisionRect.Intersects(player.collisionRect))
    {
        // Play collision sound
        if(s.collisionCueName != null)
            ((Game1)Game).PlayCue(s.collisionCueName);

        // Remove collided sprite from the game
        spriteList.RemoveAt(i);
        −−i;
    }
}

Add some code to check whether the sprite is out of bounds. If the sprite is out of bounds, remove it from the game. The preceding loop should now look like this (added lines are in bold):

// Update all sprites
for (int i = 0; i < spriteList.Count; ++i)
{
    Sprite s = spriteList[i];

    s.Update(gameTime, Game.Window.ClientBounds);

    // Check for collisions
    if (s.collisionRect.Intersects(player.collisionRect))
    {
        // Play collision sound
        if(s.collisionCueName != null)
            ((Game1)Game).PlayCue(s.collisionCueName);

        // Remove collided sprite from the game
        spriteList.RemoveAt(i);
        −−i;
    }

    // Remove object if it is out of bounds
    if (s.IsOutOfBounds(Game.Window.ClientBounds))
    {
        spriteList.RemoveAt(i);
        −−i;
    }

}

Now your irrelevant objects will be deleted after they leave the screen. Your game will have to update, draw, and run collision checks only on objects that are on the screen, and this will greatly improve performance, especially as the game progresses.

Creating a Chasing Sprite

As mentioned previously, when it comes to computer-controlled objects, the goal of any game is to make those objects appear intelligent to the point where a user may not be able to tell the difference between an object controlled by a human and an object controlled by a computer. We clearly aren’t even close to that.

The automated sprites you’ve added do nothing more than move forward in a straight line. You’ve done some great work on your SpriteManager, but we haven’t discussed how to do anything to improve the movement of your automated sprites.

Let’s create a couple of different objects that do something a little more intelligent than simply moving in a straight line.

In this section, you’ll create a new sprite type that will chase your user-controlled object around the screen. You’ll do this with the following very simple chase algorithm:

if (player.X < chasingSprite.X)
    chasingSprite.X −= 1;
else if (player.X > chasingSprite.X)
    chasingSprite.X += 1;

if (player.Y < chasingSprite.Y)
    chasingSprite.Y −= 1;
else if (player.Y > chasingSprite.Y)
    chasingSprite.Y += 1;

Essentially, the algorithm compares the position of the player with that of the chasing sprite. If the player’s X coordinate is less than the chasing sprite’s X coordinate, the chasing sprite’s coordinate is decremented. If the player’s X coordinate is greater than the chasing sprite’s X coordinate, the chasing sprite’s X coordinate is incremented. The same is done with the Y coordinate.

To implement the chasing sprite, you’ll want to create a new class that derives from Sprite. But before you do that, you can see from the preceding algorithm that the new class is going to need to know the position of the player object. Looking at your current Sprite class and its derived classes, there is no way to get that information. So, you’ll need to add a public accessor to the Sprite base class that will return the position of the sprite object:

public Vector2 GetPosition
{
    get { return position; }
}

Then, add a method in your SpriteManager class that will return the position of the player object:

public Vector2 GetPlayerPosition(  )
{
    return player.GetPosition;
}

That done, you’ll need to create a new class within your project (right-click on the project in the Solution Explorer and select AddClass…). Name it ChasingSprite.cs, and replace the code that’s generated with the following:

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace AnimatedSprites
{
    class ChasingSprite : Sprite
    {
        // Save a reference to the sprite manager to
        // use to get the player position
        SpriteManager spriteManager;

        public ChasingSprite(Texture2D textureImage, Vector2 position,
            Point frameSize, int collisionOffset, Point currentFrame,
            Point sheetSize, Vector2 speed, string collisionCueName,
            SpriteManager spriteManager)
            : base(textureImage, position, frameSize, collisionOffset,
            currentFrame, sheetSize, speed, collisionCueName)
        {
            this.spriteManager = spriteManager;
        }        public ChasingSprite(Texture2D textureImage, Vector2 position,
            Point frameSize, int collisionOffset, Point currentFrame,
            Point sheetSize, Vector2 speed, int millisecondsPerFrame,
            string collisionCueName, SpriteManager spriteManager)
            : base(textureImage, position, frameSize, collisionOffset,
            currentFrame, sheetSize, speed, millisecondsPerFrame,
            collisionCueName)
        {
            this.spriteManager = spriteManager;
        }

        public override Vector2 direction
        {
            get { return speed; }
        }

        public override void Update(GameTime gameTime, Rectangle clientBounds)
        {
            // Use the player position to move the sprite closer in
            // the X and/or Y directions
            Vector2 player = spriteManager.GetPlayerPosition(  );

            // Because sprite may be moving in the X or Y direction
            // but not both, get the largest of the two numbers and
            // use it as the speed of the object
            float speedVal = Math.Max(
                Math.Abs(speed.X), Math.Abs(speed.Y));

            if (player.X < position.X)
                position.X −= speedVal;
            else if (player.X > position.X)
                position.X += speedVal;

            if (player.Y < position.Y)
                position.Y −= speedVal;
            else if (player.Y > position.Y)
                position.Y += speedVal;

            base.Update(gameTime, clientBounds);
        }
    }
}

There are a couple of things to note about this code. First, the namespace that you’re using is AnimatedSprites. This was what you should have named your project way back in the first couple of chapters in this book. If the namespace is giving you problems, you most likely named your project something else. Look at the namespace in your Game1 class, and use the same namespace that you have listed there in this file.

Next, notice that the constructor is essentially the same as the one in your AutomatedSprite class, with one key exception: here, you’ve added a SpriteManager parameter and set a local SpriteManager variable to keep track of the object passed in via that parameter. During the Update method call, this object is used to retrieve the position of the player via the method you added previously.

The other important thing to understand is what’s going on in the Update method. You’re retrieving the position of the player and then running your chasing algorithm using the largest of the two coordinates specified in the speed member of the Sprite base class (because the sprites will be moving only in the X or the Y direction, not both).

The final thing that you’ll need to change in order to get a functional chasing sprite is the SpriteList.Add call in your SpriteManager’s SpawnEnemy method. You’ll need to change the type of sprite you’re creating to ChasingSprite instead of AutomatedSprite. This will result in creating ChasingSprite objects at random intervals rather than AutomatedSprites, and when you run your application, they should give you a good run for your money. Your SpriteList.Add call, which is at the end of the SpawnEnemy method in the SpriteManager class, should look like this:

spriteList.Add(
    new ChasingSprite (Game.Content.Load<Texture2D>(@"imagesskullball"),
    position, new Point(75, 75), 10, new Point(0, 0),
    new Point(6, 8), speed, "skullcollision", this));

Run the application, and get ready to run for your life. You can try to avoid the objects, but eventually there’ll be too many of them and you’ll hit them. Try using a gamepad or the keyboard rather than the mouse for an even tougher challenge. Your application should, at this point, look something like Figure 7-2.

I can’t get away from those sprites!!!
Figure 7-2. I can’t get away from those sprites!!!

You can easily increase or decrease the difficulty of this algorithm by multiplying the speed member of the base class by some value. Increasing the speed will make your sprites chase the player faster, whereas decreasing the speed will slow them down. As it is, the objects definitely chase the player around the screen, but we’re going to tweak them a little bit for the purposes of this game. Instead of having the objects chase the player indefinitely all over the screen, you’re going to program them to continue on their course across the screen while veering toward the player. This will cause the chasing sprites to continue their course off the screen toward deletion from the game if the player successfully avoids them.

To accomplish this, you’ll first need to figure out which direction a given sprite is moving in (remember that the sprites only move in one direction—up, down, left, or right). If the sprite is moving horizontally, you’ll adjust only the sprite’s vertical movement to chase the player. If the sprite is moving vertically, you’ll adjust only the horizontal movement to chase the player. This will make the sprites continue in their original direction (horizontal or vertical) while chasing the player at the same time.

Replace the Update method of your ChasingSprite class with this one:

public override void Update(GameTime gameTime, Rectangle clientBounds)
{
    // First, move the sprite along its direction vector
    position += speed;

    // Use the player position to move the sprite closer in
    // the X and/or Y directions
    Vector2 player = spriteManager.GetPlayerPosition(  );

    // If player is moving vertically, chase horizontally
    if (speed.X == 0)
    {
        if (player.X < position.X)
            position.X −= Math.Abs(speed.Y);
        else if (player.X > position.X)
            position.X += Math.Abs(speed.Y);
    }

    // If player is moving horizontally, chase vertically
    if (speed.Y == 0)
    {
        if (player.Y < position.Y)
            position.Y −= Math.Abs(speed.X);
        else if (player.Y > position.Y)
            position.Y += Math.Abs(speed.X);
    }

    base.Update(gameTime, clientBounds);
}

This is a slightly modified chasing algorithm that will chase in only one direction. The method starts by adding the speed member to the sprite’s position member. This will move the sprite forward in the direction of the speed vector.

After the position is updated, the player object’s position is retrieved. Recall from when you wrote the code that generates the automated sprites that the code generates a Vector2 for speed that will have a zero value in the X or Y coordinate (i.e., sprites move only vertically or horizontally, not diagonally). Because of this, the algorithm next detects whether the ChasingSprite is moving horizontally or vertically by determining which coordinate in the Speed variable is zero. If the X coordinate is zero, that means that the object is moving vertically, and the algorithm will then adjust only the X coordinate of the ChasingSprite to “chase” the player in only the horizontal direction. The result is that the sprite will continually move up or down across the screen, but while doing so, will sway to the left or the right to chase the player. The algorithm then runs the same checks and calculations for objects moving horizontally.

Compile and run the game now, and you’ll see that the sprites move horizontally or vertically across the screen, but bend slightly to chase the player. As you’ll probably notice when playing, the sprites that move more quickly are definitely more difficult to evade than the slower ones. That’s because your objects are chasing the player at the same speed at which they are cruising across the screen (i.e., you’re using the speed member variable to chase the player by using Math.Abs(speed.X) and Math.Abs(speed.Y)).

Congratulations! Now you’re really getting somewhere. Not only does your game actually look and feel more like a real game, but you’ve just written an artificial intelligence algorithm that makes the sprites respond to the movements of the real human player. Pretty cool!

Creating an Evading Sprite

You now have two types of automated sprites in your application: one that moves across the screen without changing direction and one that moves across the screen but changes direction slightly to chase the player.

In this section, you’ll build one more type of sprite that is similar to the chasing sprite, but this one will actually try to avoid the player. Why would you want to write a sprite that avoids the player? This sprite type will be used for something that the player will want to run into (maybe a power-up, or extra life, or something), so the sprite will tease the player by letting her get close to it but then, when she gets too close, taking off in another direction. This should add a nice different element to the game, as well as making it more challenging.

Let’s get started. Add a new class to your project, and call it EvadingSprite.cs. The code for this sprite will be very similar to the code you just wrote for the ChasingSprite—so similar, in fact, that it will be easier to start with that code than to start from scratch. Remove the code generated for you in the EvadingSprite class and replace it by copying the code in the ChasingSprite class and pasting that code into the EvadingSprite.cs file. You’ll need to change the name of the class from ChasingSprite to EvadingSprite and also change the names of the constructors. Your EvadingSprite.cs file should now look like this:

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace AnimatedSprites
{
    class EvadingSprite : Sprite
    {
        // Save a reference to the sprite manager to
        // use to get the player position
        SpriteManager spriteManager;

        public EvadingSprite(Texture2D textureImage, Vector2 position,
            Point frameSize, int collisionOffset, Point currentFrame,
            Point sheetSize, Vector2 speed, string collisionCueName,
            SpriteManager spriteManager)
            : base(textureImage, position, frameSize, collisionOffset,
            currentFrame, sheetSize, speed, collisionCueName)
        {
            this.spriteManager = spriteManager;
        }

        public EvadingSprite(Texture2D textureImage, Vector2 position,
            Point frameSize, int collisionOffset, Point currentFrame,
            Point sheetSize, Vector2 speed, int millisecondsPerFrame,
            string collisionCueName, SpriteManager spriteManager)
            : base(textureImage, position, frameSize, collisionOffset,
            currentFrame, sheetSize, speed, millisecondsPerFrame,
            collisionCueName)
        {
            this.spriteManager = spriteManager;
        }

        public override Vector2 direction
        {
            get { return speed; }
        }

        public override void Update(GameTime gameTime, Rectangle clientBounds)
        {
            // First, move the sprite along its direction vector
            position += speed;

            // Use the player position to move the sprite closer in
            // the X and/or Y directions
            Vector2 player = spriteManager.GetPlayerPosition(  );

            // If player is moving vertically, chase horizontally
            if (speed.X == 0)
            {                if (player.X < position.X)
                    position.X −= Math.Abs(speed.Y);
                else if (player.X > position.X)
                    position.X += Math.Abs(speed.Y);
            }

            // If player is moving horizontally, chase vertically
            if (speed.Y == 0)
            {
                if (player.Y < position.Y)
                    position.Y −= Math.Abs(speed.X);
                else if (player.Y > position.Y)
                    position.Y += Math.Abs(speed.X);
            }

            base.Update(gameTime, clientBounds);
        }

    }
}

Because this code is exactly the same as the code you used for your ChasingSprite object, creating an object of this type at this point will create an object that will chase the user while moving across the screen. However, you want to program this class to actually run away from the player.

First, you’ll need to tell the SpriteManager to create objects of the type EvadingSprite rather than ChasingSprite. To do this, modify the SpriteList.Add call in the SpriteManager’s SpawnEnemy method:

spriteList.Add(
    new EvadingSprite (Game.Content.Load<Texture2D>(@"imagesskullball"),
    position, new Point(75, 75), 10, new Point(0, 0),
    new Point(6, 8), speed, "skullcollision", this));

Now you’re ready to get to work on the evasion algorithm. The algorithm is really simple. In fact, it’s essentially the opposite of the chasing algorithm: if the X coordinate of the player’s position is less than the X coordinate of the evading sprite’s position, rather than decreasing the value of the X coordinate of the evading sprite’s position to move the sprite closer to the player, you’ll increase the value to move the sprite farther away from the player.

You can do this by swapping the additions and subtractions in your chasing algorithm. Also, because you are now evading the player, you don’t care about continuing in a straight line across the screen, so you can remove the two if statements detecting the direction in which the sprite is traveling. After making these changes, your EvadingSprite’s Update method should look like this:

public override void Update(GameTime gameTime, Rectangle clientBounds)
{
    // First, move the sprite along its direction vector
    position += speed;

    // Use the player position to move the sprite closer in
    // the X and/or Y directions
    Vector2 player = spriteManager.GetPlayerPosition(  );

    // Move away from the player horizontally
    if (player.X < position.X)
        position.X += Math.Abs(speed.Y);
    else if (player.X > position.X)
        position.X −= Math.Abs(speed.Y);

    // Move away from the player vertically
    if (player.Y < position.Y)
        position.Y += Math.Abs(speed.X);
    else if (player.Y > position.Y)
        position.Y −= Math.Abs(speed.X);

    base.Update(gameTime, clientBounds);
}

Compile and run your project at this point, and you’ll see that the objects are nearly impossible to catch. Instead of traveling across the screen, they veer off to one side to avoid even coming close to the player.

Although the sprites are effectively avoiding the player, this really isn’t very fun. You’re losing at your own game, and that’s just lame. Let’s modify the new sprite so that it travels across the screen just like an AutomatedSprite object, but then, when the player gets within a certain range of the object, the evasion algorithm turns on and the sprite turns and runs.

Add a few variables to your EvadingSprite class: one that will be used to detect when to activate the evasion algorithm, one that will determine the speed at which the sprite runs from the player, and one that will keep track of the sprite’s state (possible states are evading and not evading). By default, you want this variable to indicate that your sprite is in a not-evading state:

float evasionSpeedModifier;
int evasionRange;
bool evade = false;

Why use a separate speed for evasion? You don’t have to do this, but the evasion tactic will be somewhat unexpected for the user. All of the other sprites in the game either move forward only or actually chase after the player. Having a sprite turn and book it in a different direction will be a bit of a surprise and therefore will be a little harder for the player to handle. Using a modifier like this will enable you to increase or decrease the speed of the sprite while in evasion mode. You’ll be able to play with the numbers and find a speed that feels right to you as a player/developer.

Next, update your constructors to accept parameters for the evasionSpeedModifier and evasionRange variables. You’ll want to assign the values from those parameters to your member variables in the body of your constructors as well:

public EvadingSprite(Texture2D textureImage, Vector2 position,
    Point frameSize, int collisionOffset, Point currentFrame,
    Point sheetSize, Vector2 speed, string collisionCueName,
    SpriteManager spriteManager, float evasionSpeedModifier,
    int evasionRange)
    : base(textureImage, position, frameSize, collisionOffset,
    currentFrame, sheetSize, speed, collisionCueName)
{
    this.spriteManager = spriteManager;
    this.evasionSpeedModifier = evasionSpeedModifier;
    this.evasionRange = evasionRange;
}

public EvadingSprite(Texture2D textureImage, Vector2 position,
    Point frameSize, int collisionOffset, Point currentFrame,
    Point sheetSize, Vector2 speed, int millisecondsPerFrame,
    string collisionCueName, SpriteManager spriteManager,
    float evasionSpeedModifier, int evasionRange)
    : base(textureImage, position, frameSize, collisionOffset,
    currentFrame, sheetSize, speed, millisecondsPerFrame,
    collisionCueName)
{
    this.spriteManager = spriteManager;
    this.evasionSpeedModifier = evasionSpeedModifier;
    this.evasionRange = evasionRange;
}

Now you’ll need to modify your Update method to add some logic that will make the sprite operate just like an AutomatedSprite would until the distance between the player’s position and the sprite’s position is less than the value in the evasionRange variable. You can use the Vector2.Distance method to determine the distance between two vectors.

Once the sprites are closer than the evasionRange, you need to reverse the direction of the sprite and activate the evasion algorithm. That algorithm should continue to run until the sprite is destroyed.

Your Update method should look something like this:

public override void Update(GameTime gameTime, Rectangle clientBounds)
{
    // First, move the sprite along its direction vector
    position += speed;

    // Use the player position to move the sprite closer in
    // the X and/or Y directions
    Vector2 player = spriteManager.GetPlayerPosition(  );

    if (evade)
    {
        // Move away from the player horizontally
        if (player.X < position.X)
            position.X += Math.Abs(speed.Y);
        else if (player.X > position.X)
            position.X −= Math.Abs(speed.Y);

        // Move away from the player vertically
        if (player.Y < position.Y)
            position.Y += Math.Abs(speed.X);
        else if (player.Y > position.Y)
            position.Y −= Math.Abs(speed.X);
    }
    else
    {
        if (Vector2.Distance(position, player) < evasionRange)
        {
            // Player is within evasion range,
            // reverse direction and modify speed
            speed *= -evasionSpeedModifier;
            evade = true;
        }
    }

    base.Update(gameTime, clientBounds);
}

Finally, you’ll need to change the SpriteList.Add call in the SpriteManager’s SpawnEnemy method once again, adding the two parameters to the constructor for the EvadingSprite object. For starters, pass in .75f as the modifier for the evasion speed and 150 for the evasion range. These values will cause the sprite to begin evading the player when the two are within a range of 150 units, and the sprite will evade at three quarters of its normal speed:

spriteList.Add(
    new EvadingSprite (Game.Content.Load<Texture2D>(@"imagesskullball"),
    position, new Point(75, 75), 10, new Point(0, 0),
    new Point(6, 8), speed, "skullcollision", this, .75f, 150));

Compile and run the game now, and you’ll see that the sprites seem to have a bit more “intelligence.” They detect when the player is near, and they turn and head off in the opposite direction. Also, you’ll find that you probably can catch most of them, but they’re still a bit tricky. Once you add the other sprites floating around that the player actually has to avoid, this evasion technique will be just enough to represent a good challenge.

So that’s it? Is that artificial intelligence?

Well, not quite. We haven’t even scratched the surface of true artificial intelligence algorithms. In fact, many hardcore AI experts would argue that this isn’t artificial intelligence at all—and they might be right. Again, the science is somewhat ambiguous, and who’s to say whether what you’ve done here truly represents intelligence or not?

The point here is that you can definitely go overboard in the name of science sometimes. True artificial intelligence research and algorithms absolutely have a place in the world, but probably not in a 2D sprite-avoidance game. When programming artificially intelligent objects—especially in video games—there is a certain level of “intelligence” that is typically “good enough.” You could spend months or years fine-tuning an algorithm for this game so you could argue that it is truly intelligent, but at what cost, and at what advantage to the player?

Unfortunately, there is no right or wrong answer in relation to the degree or quality of artificial intelligence that should be implemented, and it comes down to a decision that you as the developer must make. Ultimately, it’s up to you to decide when your algorithm needs improvement and at what point it’s good enough for what you’re trying to accomplish.

What You Just Did

You’re now very close to having something worthy of your newly developed XNA prowess. In the next chapter, you’ll fine-tune the game and wrap up your 2D development. In the meantime, let’s reflect on what you just accomplished:

  • You learned some background to artificial intelligence.

  • You created a factory for sprites that creates sprites at random intervals.

  • You learned about irrelevant objects and what to do with them to improve game performance.

  • You created a chasing sprite that follows a player across the screen.

  • You created an evading sprite that runs from a player.

  • You drank from the fount of XNA goodness.

Summary

  • Artificial intelligence means many different things, mainly because the term “intelligence” itself is ambiguous and difficult to define.

  • Alan Turing made great strides in the field of artificial intelligence. Much of his work is directly relevant to what game developers attempt to accomplish.

  • Irrelevant objects are objects that will no longer affect gameplay (e.g., a bullet that’s shot into the sky and doesn’t hit anything). These objects must be removed and deleted in order to not negatively impact performance as they accrue.

  • To implement a chase algorithm, detect the position of the player in relation to the chaser’s current position, and then move the chasing object in the direction of the player.

  • Implementing an evasion algorithm is the opposite of the chase algorithm: detect the position of the player, and move the evading object in the opposite direction.

  • “Artificial intelligence is no match for natural stupidity.” —Anonymous

Test Your Knowledge: Quiz

  1. What is the Turing Test?

  2. Why is artificial intelligence so difficult to perfect?

  3. What constitutes irrelevancy for an object in a video game? What should be done with irrelevant objects, and why?

  4. If you have a player whose position is stored in a Vector2 object called PlayerPos and a chasing object whose position is stored in a Vector2 object called ChasePos, what algorithm will cause your chasing object to chase after your player?

  5. In the beginning, what was created that made a lot of people very angry and has been widely regarded as a bad move?

Test Your Knowledge: Exercise

Take what you’ve learned in this chapter and make yet another type of sprite object, one that moves randomly around the screen. To do this, you’ll want to create a random timer that signifies when the object should change directions. When the timer expires, have the object move in a different direction, and then reset the random timer to a new random time at which the object will again shift its direction.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.107.193