Recently I was fortunate enough to be brought aboard a student-driven game design project by Jake Ross and some other students who are members of Texas A&M’s Visualization Lab (you should really check them out, they send a lot of animators to studios like Pixar and Dreamworks). Together, with a core team of about 8, we spent a year building an iPad game in our free time and named it Titan Ph.D. I built the AI (artificial intelligence), and this is the first of a 3-post series on how I did it.
A Little About the Game
Our game features Noah, a biologist with an animal philanthropist streak (so a bio-philo-zoo-thropist?). Noah is travelling to the world Xion because the planet’s integrity has been compromised (by human mining operations) and the whole thing is about to explode! Perhaps a bit overly dramatic, but you, Noah, have come to Xion to save its creatures. Being a biologist, you can’t stand to see this kind of fauna go extinct. So you chartered a huge ship to bring all the creatures aboard for preservation and future study, and conveniently named it the Ark.
The only thing is, these creatures have no idea that you’re trying to save them. In fact, they’ve come to dislike humans in general, so they won’t be too receptive of your heroism. In fact, they’ll try to kill you. Armed with your Bio-Rifle, you must stun the creatures, and then get close enough to them so you can beam them aboard the Ark.
Making the AI
While I may not be a very good at art, animation, or sound, I am at least a decent programmer! (Or clever enough to fool the other team members that I am). They let me take on the role of AI Programmer, which was very exciting.
If you take a peek at the in-game screenshot from above, you’ll notice that there’s at least 9 enemies on-screen. In fact, our game needed to handle about 30 enemies on-screen simultaneously, and on iPad hardware. On top of rendering the models, doing shading and lighting calculations, sound, physics, and special effects, AI doesn’t usually get a lot of room to work. I was fortunate enough to have a bit of leeway on this game because AI is the crux of the gameplay, but I still wanted to keep things simple and quick.
I’m separating this topic into three parts, to be covered here and in two more blog posts. First, how to efficiently handle a pool of objects, like enemies (this post). Next post will be about finite state machines and behavior trees, and then finally I’ll talk a bit about implementation details using Unity’s powerful scripting engine in the last post.
Pools of Objects
What is an object pool? The name is quite intuitive; it’s a notion of a container full of things that you pull from when you want to use one, and when you’re done you put it back for re-use later. To use an example from the Unity forums, let’s say your character has a gun. Guns shoot bullets. Each time you shoot a bullet, you want to show that bullet travel along its path. After a bullet has hit something, it goes away.
A really inefficient solution to this problem is to simply create a new “bullet” object each time the player pulls the trigger, and then destroy it when it has completed its trajectory. A much more efficient solution is to create, say, 1000 bullet objects at game start, hide them offscreen somewhere, and then teleport them in as they’re needed. When the bullet finishes its trajectory, *Poof!* it’s deactivated and teleported back into the bullet pool.
Having no experience in object pools myself, I turned to the Unity forums, which had a few good posts on the matter.
Spawning and Teleporting Enemies
Our gameplay draws inspiration from survival-type games like the (in)famous I MAED A GAM3 W1TH ZOMBIES 1N IT!!!1. I wanted the player to be initially unchallenged by only a few basic enemies, but eventually be overwhelmed by a horde as time progressed. For that, I needed an enemy pool, and an intelligent spawning strategy.
The AI Manager:
Like The Director in Valve and Turtle Rock Studios’ Left 4 Dead series, I wanted enemy spawning decision to be handled intelligently and on the fly. The AI Manager was the solution to that.
The AI Manager has only few very simple tasks:
- Create all our creature pools
- Spawn enemies at appropriate times and in appropriate locations
- Return creatures to the pool when they’ve been teleported or killed
While we discouraged the player from killing the creatures on the planet by subtracting points, it did happen. I suppose players today have been overexposed to games focused on killing things.
Creating the creature pools was pretty easy; creatures are objects with predefined (tuned) attributes like speed, attack damage, attack range, and health. That means all the AI manager had to do was create a bunch of instances of them and plop them into a list.
Spawning the creatures was a little more difficult. We want to spawn the creatures off-screen, on the terrain, and not intersecting with other creatures or objects. Finding an area off-screen was not too difficult. Instead of the more difficult calculation of the portion of the terrain not viewable in the camera frustum, we settled on a variable minimum distance from the player’s current position, so long as the camera didn’t zoom way out (it didn’t), we were fine.
Spawning the creatures at least a minimum radius away from the player solves the issue of spawning them off-screen, but we still had to worry about them being on the terrain. Our world wasn’t a totally flat plane; it had small hills here and there. If I spawned an enemy on the same plane as the player, it could either be a little above the ground (not a problem with physics that will bring it down), or a little below the ground (a much bigger problem, as the physics will cause the creature to fall to its doom).
To solve this issue I did the following:
- Define some point above the highest point on the terrain. Say, 60m above the origin.
- At the planar point where I want to spawn the creature, project that point vertically 60m so it’s guaranteed to be above all the ground.
- Cast a ray downward from the point and until it intersects the terrain. At the intersection point is where we want to spawn our creature.
Thankfully the Unity engine has support in place for this technique in its Physics.Raycast function.
So now we’ve taken care of spawning our creature off camera and on top of the terrain, so all that’s left is making sure it’s not in the middle of a tree or another creature or something. How can we tell if we’re intersecting a creature or not? Well, Unity has a nice physics system in place, so let’s let it tell us!
I could move the entire creature object to my desired spawn position, test for collision, and then move it back to the pool upon failure, but there’s a few problems with that. The physics engine in Unity has a different timestep than the rendering engine; a slower timestep. Moving the creature model in place, even briefly, runs the risk of having an unsightly visual artifact as we test for collision. Additionally, there’s no reason to test collision on the whole creature; it’s faster to test collisions against spheres.
A better solution, then, is to define an invisible sphere that could contain our creature, and use that instead. For each differently-sized creature, I created a differently sized sphere, which I called a spawn tester. Now I can invisibly place a sphere that can collide with other objects (but not vice versa! We don’t want the player running into mystical invisible teleporting ball), and I can know for certain whether an area is clear for spawning.
After a creature is teleported to the Ark, we shouldn’t be able to see it anymore. When this happens, a creature tells the AI Manager that it has been teleported. The AI Manager waits a second or two for the animation and sound to play, disables the creature, and then literally teleports it to our offscreen holding area. The deactivated enemy is added back into the creature pool, where it is now available to be reset and reactivated later.
This is a common thing to do in games, but it has an interesting philosophical implication; you are constantly killing the same enemies over and over again! It’s like they’re stuck in a cruel universe where they don’t ever really die, they only go to some weird catatonic limbo until a maleficent force reanimates them. In our game at least, most of the creatures aren’t being killed, merely stunned.
So that wraps up Part 1 of this series, I hope it was informative and exciting!
Dev Team (in alphabetical order)
- Stephen Aldriedge
- Cameron Coker
- Rachel Cunningham
- Jack Eggebrecht
- Me (Andy G)
- Jay Jackson
- Chiang Leng
- Sterling Morris
- John Pettingill
- Chris Potter
- Jake Ross
- Brian Smith
- Sterling Smith
- Jacob Zimmer