Developing Game AI (part 2): Basic Decision Making
This is a follow-up to [the last post I wrote about developing AI]. This post will cover the basics of game entities actually handling decision making, and what that actually means. In future posts, we can go over some of the more specific stuff, like how AI forms the backbone of many animation systems or how pathfinding actually works. But for today, weāll talk about the basics in evaluating a choice and deciding a course of action.
Generally, AI is a looping routine that checks a list of conditions every time step, then makes a decision and does something for that time step until the next time it has to ask itself what to do next. A time step is a unit of time where the AI has to make a decision and carry it out. It could be a matter of seconds or milliseconds, but it is the individual block of time an AI has to make a decision and act on it. Hereās a simplified example of the Centipede AI from the arcade game of the same name:
If I have been shot, delete myself and add points to the player score. Spawn a blocker at my current position.
If I am touching the player, kill the player.
If I am moving right, and the space to my right is traversible, move right.
If I am moving right and the space to my right is not traversible, move down and set myself to moving left
If I am moving left and the space to my left is traversible, move left
If I am moving left and the space to my left is not traversible, move down and set myself to moving right
If I have reached the bottom row of the screen, the game ends and the player loses
All centipede units start off moving right, and follow this basic algorithm until they are either completely destroyed and the player advances a level, or they reach the bottom of the screen and the player loses the game. This is the basic concept of most programming - looping through a bunch of conditional statements and doing specific things depending on when those conditions change.
However, this condition list can make it hard to model more complex behavior. Conditions tend to demand more granularity and logic in order to make things behave better. For example, maybe you want to check if more than one condition is met. Look at the basic condition for killing the player:
If I am touching the player, kill the player
What if the player has the ability to gain a temporary invincibility shield via a power up or something? Then you need to check TWO conditions
If I am touching the player AND the player is not invincible, kill the player
If you add in a health system, you need to start checking more even conditions than that - how much health does the player have, is the player shielded, is the AI actually attacking, etc. Each of these differences in conditions being met can have a different result. But note here that we want to test multiple combinations of conditions and have different results depending on the set of results. One common way to handle this is to start grouping things together into whatās called a State Machine. Most of the time, we call them Finite State Machines, because there is a finite number of states that will be represented. Hereās an example for Centipede. In this, we have two states: moving left and moving right. We then identify the way to transition between these states on specific inputs. Here, the only input we care about is whether weāve collided with something. If there is no collision with a block, the Centipedeās state does not change. However, if there is a collision with a block, the Centipedeās state changes from moving left to moving right, or vice versa.
There are really only two possible inputs here - if the centipede moves and there is a collision or there isnāt a collision. No collision results in remaining in the same state. Colliding with an object forces a state change - the centipede must change direction and start moving that way. It will then remain in that new state until another collision happens.Ā
We can also nest these state machines inside each other. For example, the moving behavior is encompassed inside another state machine that handles whether the centipede is alive or dead. Like so:
Only living centipedes will move left or move right. Dead ones donāt do either of those, and thus donāt care whether they have collided with blocks or not. This representation is logically equivalent to checkingĀ āIf Alive AND Moving Right AND Collides with blockā but makes more intuitive sense than trying to keep track of all of the different conditions strung together.
Notice that the state transition for this state machine is only one way. Once youāre dead, you donāt get to go back to being alive anymore. But suppose we wanted to make it so that the centipede can resurrect parts of itself... for example, if there are berries spawned in the map that, when touched by the Centipede, restore any dead portions. If that were the case, then thereās a potential new transition from dead back to alive again.
Each of these transitions happens on some kind of input. That input could be time-based (X seconds have passed), reaction based (the player attacked me), environment based (I have moved into an area that damages me), or one of many other possible inputs.Ā This kind of logic is the core of AI. When a Dark Souls boss is deciding what to do next, it is generally in some kind of state machine. Maybe you did this, so it will do that. Maybe it will follow X attack with Y. These inputs donāt necessarily have to be deterministic either - we could give choose a new state based on a random roll. We could even make the state transition triggered by random chance in addition to other specific criteria (e.g. this thing will happen with a 20% chance to occur only if the player didnāt use fire spells during this battle). These decision-making state machines can be super simple, or incredibly complex depending on the number of inputs and the number of layers you have built up.
Next time, weāll go a little bit into how AI state machines interact with animations in order to make characters move smoothly.
PS. Those of you with good memories may remember [a post that I wrote about distilling down programming problems into terms of input and output]. State Machines are a natural extension of that basic principle being applied.
Got a burning question you want answered?
Short questions: Ask a Game Dev on Twitter
Long questions: Ask a Game Dev on Tumblr
Frequent questions: The FAQ