I thought I'd talk a bit about how I reason around things such as enemy behavior in a game such as this one, and how I go about to translate that behavior into code.
I’ve spent the last couple of weeks working on various kinds of enemies and enemy AI/behavior. Cathedral is a pretty simple game in terms of this since I’m doing it in the style of a classic platformer, so the algorithms don’t have to be too sophisticated. This post will mostly outline how I think about enemy design – hopefully it will either help someone else, or someone might point out a better way to do things than I currently do
![Smiley](https://forums.tigsource.com/Smileys/derek/smiley.gif)
Anyway, one of the enemies I designed in the last couple of days is this reaper character:
![](https://dl.dropboxusercontent.com/u/5202409/dev/tig/2015-04-03-1928-13.gif)
I generally start out by trying to figure out the behavior by just sketching a few ideas paper. I usually have a rough idea of what the sprite should look like, but seldom something completely finished. I tend to start out by doing a quick throwaway sprite/design just to try some ideas out. But to start with, I'm more interested in pure behavior.
So, for instance, in this case, I want the reaper to:
- Pace back and forth in the air, waiting for the player to come close
- If the player is within a certain preset distance, the reaper should start chasing the player
- If the player is REALLY close (another preset distance), then the reaper will start to prepare for a strike, basically to give the player a short amount of time to react and get out of the way.
- After the preparation phase, the enemy will try to hit the player. If the player should hit the enemy before that, then the attack is interrupted.
- If the reaper hits the player, there’s a short cool down period before it attacks again (so the player is not completely overwhelmed by attacks)
So, what I find interesting to talk about, is how to visualize this and map these actions out to understand how everything should interact, and which invariants I need to uphold in my code. It turns out that this is pretty easily represented as a finite state machine (
http://en.wikipedia.org/wiki/Finite-state_machine).
I have a fixed number of states that the enemy can be in, and I have various rules for transitions between these states.
At this point, I usually separate states and the transitions between them. For instance, my reaper character can be in the following states:
- IDLE – The “pacing back and forth” movement
- CHASING - he enemy is chasing after the player
- PREPARE – The enemy is preparing an attack
- ATTACK – The actual attack
- COOL_DOWN – Cooling down after a successful attack
Having a whiteboard at hand is pretty nice at this point. Just drawing out the states is helpful:
![](https://dl.dropboxusercontent.com/u/5202409/dev/tig/fsm/IMG_20150407_035531.jpg)
The next thing to determine is the allowed transitions. Basically answering questions such as "when the enemy is idling, which state can it actually go to from there?"
Well. From IDLE, we can go to CHASING which is triggered when the player is within a set distance. However, we can also transition from CHASING to IDLE if the player runs away and gets outside of that distance again, so I then continue by filling out the different allowed transitions in my diagram. As an example, for these two states, we get a really simple state machine:
![](https://dl.dropboxusercontent.com/u/5202409/dev/tig/fsm/IMG_20150407_035633.jpg)
After filling out all the transitions, I have the final FSM diagram:
![](https://dl.dropboxusercontent.com/u/5202409/dev/tig/fsm/IMG_20150407_035849.jpg)
... And finally, I like to add a bunch of labels to it to mark the actual rules for the transitions:
![](https://dl.dropboxusercontent.com/u/5202409/dev/tig/fsm/IMG_20150407_040256.jpg)
These things tend to carry over pretty well into code. In C/C++, states just tend to become a private unnamed enum, such as:
enum {
COOL_DOWN,
IDLE,
CHASING,
PREPARE,
ATTACK, } _state;In short, the enemy AI just becomes a switch where, depending on the current state, I check for various transitions and perform them if needed. Basically, I combine these with a bunch of timers which takes a lambda as a callback, giving me expressions such as:
_coolDown.onDone([this]() {
_state = IDLE;
});I’m not going to cover the code itself too much in detail since it’s going to differ depending on language (but feel free to ask questions about it). I was more interested in just sharing the overall design process and how to make sense of what code you actually need to write. So: basically some of the architectural stuff. Hopefully it gave some useful insight to someone
![Smiley](https://forums.tigsource.com/Smileys/derek/smiley.gif)