Patterns

So far, we've talked about the different states a bot has, such as attacking and dodging. In a game, a creature generally performs different movement patterns when in a particular state. For example, when dodging, a bot might zigzag left and right. You'll use the PathFinder interface from the previous chapter to write some patterns. This interface has methods that return an Iterator of locations in a path to follow. You'll actually create an abstract AIPattern class, in Listing 13.9, that implements this interface. The AIPattern class has a couple of convenience methods to help when making patterns.

Listing 13.9 Convenience Methods in AIPattern
/**
 Calculates the floor for the location specified. If
 the floor cannot be determined, the specified default
 value is used.
*/
protected void calcFloorHeight(Vector3D v, float defaultY) {
 BSPTree.Leaf leaf = bspTree.getLeaf(v.x, v.z);
 if (leaf == null || leaf.floorHeight == Float.MIN_VALUE) {
 v.y = defaultY;
 }
 else {
 v.y = leaf.floorHeight;
 }
}
/**
 Gets the location between the player and the bot
 that is the specified distance away from the player.
*/
protected Vector3D getLocationFromPlayer(GameObject bot,
 GameObject player, float desiredDistSq)
{
 // get actual distance (squared)
 float distSq = bot.getLocation().
 getDistanceSq(player.getLocation());
 // if within 5 units, we're close enough
 if (Math.abs(desiredDistSq - distSq) < 25) {
 return new Vector3D(bot.getLocation());
 }
 // calculate vector to player from the bot
 Vector3D goal = new Vector3D(bot.getLocation());
 goal.subtract(player.getLocation());
 // find the goal distance from the player
 goal.multiply((float)Math.sqrt(desiredDistSq / distSq));
 goal.add(player.getLocation());
 calcFloorHeight(goal, bot.getFloorHeight());
 return goal;
}


The first convenience method, calcFloorHeight(), gets the height of the floor for any location. If the location is out of bounds, the default height is used. The second convenience method helps make several patterns easier to write. It returns the location between the bot and the player that is a specific distance away from the player. A lot of times, a bot wants to attack or dodge from a certain distance.

Dodging

The first pattern we discuss is dodging. Dodging can be very complicated or very simple, depending on what you want to accomplish. Check out Screenshot for some sample dodge patterns.

Screenshot Some sample dodge patterns.

Java graphics 13fig06.gif


The first two dodge patterns, zigzag and random, just move the bot to specific locations relative to the player, which is what most patterns do. The third pattern, hiding, is a bit more complicated. This pattern finds a spot in the environment where the player can't see the bot, so the pattern finding needs to know a bit about the environment for this to work. We leave this one as an exercise for the reader (are you liking these exercises yet?). Let's create the first two dodge patterns as an example. The zigzag pattern is shown here in Listing 13.10.

Listing 13.10 DodgePatternZigZag.java
public class DodgePatternZigZag extends AIPattern {
 private float dodgeDist;
 public DodgePatternZigZag(BSPTree tree, float dodgeDist) {
 super(tree);
 this.dodgeDist = dodgeDist;
 }
 public Iterator find(GameObject bot, GameObject player) {
 // create the vector to the dodge location
 Vector3D zig = new Vector3D(bot.getLocation());
 zig.subtract(player.getLocation());
 zig.normalize();
 zig.multiply(dodgeDist);
 zig.rotateY((float)Math.PI/2);
 // 50% chance - dodge one way or the other
 if (MoreMath.chance(.5f)) {
 zig.multiply(-1);
 }
 // convert vector to absolute location
 zig.add(bot.getLocation());
 calcFloorHeight(zig, bot.getFloorHeight());
 Vector3D zag = new Vector3D(bot.getLocation());
 calcFloorHeight(zag, bot.getFloorHeight());
 List path = new ArrayList();
 path.add(zig);
 path.add(zag);
 return path.iterator();
 }
}


This zigzag pattern makes a pattern that moves the bot a certain distance (dodgeDist) at a tangent to the player and then back to the bot's starting point. There's a random chance the bot moves either left or right. Easy enough, right? Patterns are pretty simple. The next dodge pattern, random, moves the bot to a random location within a half-circle of the player. By limiting to a half-circle, you can ensure the bot doesn't have to cross the player to get to the dodge location. The code is in Listing 13.11.

Listing 13.11 DodgePatternRandom.java
public class DodgePatternRandom extends AIPattern {
 private float radiusSq;
 public DodgePatternRandom(BSPTree tree, float radius) {
 super(tree);
 this.radiusSq = radius * radius;
 }
 public Iterator find(GameObject bot, GameObject player) {
 Vector3D goal = getLocationFromPlayer(bot, player,
 radiusSq);
 // pick a random location on this half-circle
 // (-90 to 90 degrees from current location)
 float maxAngle = (float)Math.PI/2;
 float angle = MoreMath.random(-maxAngle, maxAngle);
 goal.subtract(player.getLocation());
 goal.rotateY(angle);
 goal.add(player.getLocation());
 calcFloorHeight(goal, bot.getFloorHeight());
 return Collections.singleton(goal).iterator();
 }
}


This just involves some more vector math for this pattern. Note that these patterns don't do any environment-based checks to see whether there is room for a pattern. For example, a bot might be able to zigzag one way but not the other. An ideal pattern might check the environment and adjust its pattern accordingly. Of course, these dodge patterns implemented here are just a beginning—you can come up with plenty more dodge patterns. Another idea for dodging is to keep track of incoming projectiles and move perpendicular to the projectile's path, or to do other movements such as ducking or jumping.

Attacking

Now you'll implement some attack patterns. Your attack patterns will vary depending on how your bots attack. For example, some might fire projectiles, some might try to ram the player, and others might simply try to make themselves look bigger to scare you away. Screenshot shows some attack patterns you'll implement for the bots in this chapter.

Screenshot Some sample attack patterns.

Java graphics 13fig07.gif


The rush pattern is probably the easiest pattern we could come up with. Here the bot just moves so that it's within a certain distance of the player. You can just use the getLocationFromPlayer() method you created in AIPattern, as shown in Listing 13.12.

Listing 13.12 AttackPatternRush.java
public class AttackPatternRush extends AIPattern {
 private float desiredDistSq;
 public AttackPatternRush(BSPTree tree, float desiredDist) {
 super(tree);
 this.desiredDistSq = desiredDist * desiredDist;
 }
 public Iterator find(GameObject bot, GameObject player) {
 Vector3D goal = getLocationFromPlayer(bot, player,
 desiredDistSq);
 if (goal.equals(bot.getLocation())) {
 return null;
 }
 else {
 return Collections.singleton(goal).iterator();
 }
 }
}


Note that this pattern returns null if the bot is already at the rush location. Strafing around a player might seem difficult at first, but if you can pull it off, it will really help boost the apparent intelligence of your bots. Strafing makes the bot circle around the player. This makes it easy for the bot to fire projectiles but harder for the player to fire at the bot, so it's a great offensive tactic. Instead of actually moving in a circle, just have the bot move in an octagon around the player. This reduces the path to eight locations. The code is in Listing 13.13.

Listing 13.13 AttackPatternStrafe.java
public class AttackPatternStrafe extends AIPattern {
 private float radiusSq;
 public AttackPatternStrafe(BSPTree tree, float radius) {
 super(tree);
 this.radiusSq = radius * radius;
 }
 public Iterator find(GameObject bot, GameObject player) {
 List path = new ArrayList();
 // find first location within desired radius
 Vector3D firstGoal = getLocationFromPlayer(bot, player,
 radiusSq);
 if (!firstGoal.equals(bot.getLocation())) {
 path.add(firstGoal);
 }
 // make a counter-clockwise circle around the player
 // (it's actually an octagon).
 int numPoints = 8;
 float angle = (float)(2 * Math.PI / numPoints);
 if (MoreMath.chance(0.5f)) {
 angle = -angle;
 }
 float lastY = bot.getFloorHeight();
 for (int i=1; i<numPoints; i++) {
 Vector3D goal = new Vector3D(firstGoal);
 goal.subtract(player.getLocation());
 goal.rotateY(angle * i);
 goal.add(player.getLocation());
 calcFloorHeight(goal, lastY);
 lastY = goal.y;
 path.add(goal);
 }
 // add last location (back to start)
 path.add(firstGoal);
 return path.iterator();
 }
}


In this pattern, with a vector from the player to the desired radius, you can rotate this vector around the y-axis eight times to get the eight different points you want. Again, you could come up with a million other patterns besides these two. There's the sneak attack as in the previous figure. (Reader exercise? You bet!) There are also other attacks, such as ramming, doing kamikaze attacks, waiting and ambushing, or jumping on the player. Of course, in a game, a bot doesn't always have to attack. Plenty of baddies, such as spiders or little animals, can be completely ambivalent to the player, just wandering around minding their own business. Games such as Nintendo's Mario series have plenty of creatures like these. Be sure to add some creativity and variety to the different types of patterns.

Running Away

A bot might run away from the player for lots of reasons. Perhaps the player has a really big, scary weapon, or the bot is so low on health that it just wants to get away. A lot of the time, you might want to just make a nervous bot that runs away often, or runs away for short periods as a defensive tactic. All you need for a run–away pattern is a spot to run to. Listing 13.14 shows a panic pattern that just makes the bot run directly away from the player, even if there is a wall in the way.

Listing 13.14 RunAwayPattern.java
public class RunAwayPattern extends AIPattern {
 public RunAwayPattern(BSPTree tree) {
 super(tree);
 }
 public Iterator find(GameObject bot, GameObject player) {
 // dumb move: run in the *opposite* direction of the
 // player (will cause bots to run into walls!)
 Vector3D goal = new Vector3D(player.getLocation());
 goal.subtract(bot.getLocation());
 // opposite direction
 goal.multiply(-1);
 // far, far away
 goal.multiply(100000);
 calcFloorHeight(goal, bot.getFloorHeight());
 // return an iterator
 return Collections.singleton(goal).iterator();
 }
}


Another solution to running away might be to pick a location on the map and do a path-finding search to get a path to that location. The point could be the location where the bot originated, a predetermined "safe" location, or a "meet-up" location for a group of bots. Or, in a heavily lit game, a bot could try to hide in a shadow. Again, you can come up with lots of different patterns.

Aiming

Although it's not really a movement pattern, often a bot needs to "aim" a weapon at the player, with various degrees of success: You'll probably want to give some bots better aiming than others. Screenshot shows a few aim pattern ideas.

Screenshot Some sample aim patterns.

Java graphics 13fig08.gif


You'll just implement one aim pattern that can aim with a specified accuracy from 0 to 1 (see Listing 13.15). If the accuracy is 0, the aim can be off up to 10°. If the accuracy is 1, the aim is dead on with the player, not taking the player's velocity into account.

Listing 13.15 AimPattern.java
public class AimPattern extends AIPattern {
 protected float accuracy;
 public AimPattern(BSPTree tree) {
 super(tree);
 }
 /**
 Sets the accuracy of the aim from 0 (worst) to 1 (best).
 */
 public void setAccuracy(float p) {
 this.accuracy = p;
 }
 public Iterator find(GameObject bot, GameObject player) {
 Vector3D goal = new Vector3D(player.getLocation());
 goal.y += player.getBounds().getTopHeight() / 2;
 goal.subtract(bot.getLocation());
 // Rotate up to 10 degrees off y-axis
 // (This could use an up/down random offset as well.)
 if (accuracy < 1) {
 float maxAngle = 10 * (1-accuracy);
 float angle = MoreMath.random(-maxAngle, maxAngle);
 goal.rotateY((float)Math.toRadians(angle));
 }
 goal.normalize();
 // return an iterator
 return Collections.singleton(goal).iterator();
 }
}


Note that this aim pattern returns a normalized vector direction rather than an absolute location because you're aiming in a direction. That's it for patterns—these are all the patterns you'll use in this chapter. Patterns are simple to write, so be sure to try out some of your own. Now, though, let's talk about actually firing a projectile after you're done aiming.

Firing

When aiming, another thing to consider is the amount of time it requires a bot to aim. You don't want a bot to simply fire its weapon for every frame; instead, you want it to wait for a bit, as if it's actually aiming, so that shots are fired only after the bot has spent time aiming the shot. In the code, we make the amount of time spent aiming proportional to the accuracy of the shot. Spending two seconds or more gives perfect aim, and anything less results in a slightly inaccurate aim. You add the code in the AIBot class in Listing 13.16.

Listing 13.16 Aiming and Firing in AIBot
AimPattern aimPathFinder;
long aimTime;
public void update(GameObject player, long elapsedTime) {
 ...
 if (aiState == BATTLE_STATE_ATTACK &&
 elapsedTimeInState >= aimTime &&
 aimPathFinder != null)
 {
 elapsedTimeInState-=aimTime;
 // longer aim time == more accuracy
 float p = Math.min(1, aimTime / 2000f);
 aimPathFinder.setAccuracy(p);
 Vector3D direction = (Vector3D)
 aimPathFinder.find(this, player).next();
 fireProjectile(direction);
 }
}
/**
 Fires a projectile in the specified direction. The
 direction vector should be normalized.
*/
public void fireProjectile(Vector3D direction) {
 Projectile blast = new Projectile(
 (PolygonGroup)blastModel.clone(),
 direction, this, 3, 6);
 float dist = 2 * (getBounds().getRadius() +
 blast.getBounds().getRadius());
 blast.getLocation().setTo(
 getX() + direction.x*dist,
 getY() + getBounds().getTopHeight()/2,
 getZ() + direction.z*dist);
 // "spawns" the new game object
 addSpawn(blast);
}


One thing you're doing in the fireProjectile() method is creating a brand-new game object that represents the projectile. But what do you do with the object? You don't want it; the game object manager does. So, mark the object as a spawn and allow the game object manager to pick it up. Let's talk about object spawning next.

Screenshot


   
Comments