Right level of Finite State Machine abstraction?

Started by
3 comments, last by lisab3373 4 years, 5 months ago

Hello everyone,

I'm working on a tactical, turn based dungeon crawler and want to start to implement enemy AI. I have opted for FSMs as they seem a straightforward and intuitive framework. The basics of FSMs are clear. I checked a couple of books on AI design, but while informative, they always consider rather simple systems / proof of concepts. I have a somewhat bigger system in mind, so I wonder about the right level of abstraction for a bigger FSM system.

There are a couple of different enemy types I have in mind, for example: a slime, a spider, a bat and a skeleton, and I want them to be varied in behaviour. They will all use a number of similar states, such as Sleep, MoveTo, Flee, Fight, etc. so this seems a good opportunity to write a generic sleep class that can be reused for all of them. All of them wake up when damaged, so that behaviour is shared, but I also want the transition out of this state to be enemy dependent: a slime also wakes up when it smells blood, a spider when something disturbs its web, bats are sensitive to sound, and skeleton senses close enemies even when sleeping.

I can solve this problem by using a transition table that is enemy specific instead of including the transitions in the state itself. This solves the problem for the Sleep state, since there is no logic needed there (the enemy doesn't do anything in this state).

But what about more complicated states? For example: selecting a point of interest and moving to it. This would again be enemy dependent. The different units are interested in different things, and also their movement might be different. Lets say spiders and bats are skittish, and will try to avoid hostile units, while slimes and skeletons do not care. So while initially it seems like they all share the MoveTo state, they rely on different logic. Do I work these differences in logic into a reusable MoveTo state? Do I make (at least) two variants of the MoveTo state (one that avoids other units one that does not)?

This leads me to the question: to what level should I try to write reusable code that can be shared? The on extreme is to write a complete set of states for every enemy type, e.i. SlimeSleep, BatSleep, etc. which will contain all the relevant code. However, this will entail frequently repeated code. Furthermore, if I happen to change something regarding the game's logic. I will have to change these repeating code snippets everywhere in parallel, which seems prone to bugs.

On the other hand, if I want to reuse a few State classes for all enemies, I will have to separate from the State, both the State Transitions and the State Logic, which begs the question; what is left of the state, except for a name?

So, what is a good way to slot reusable bits of logic and transitions into the states? Thoughts?

Advertisement

One possible direction is not to hardcode everything, but add parameters that affect behavior. The enemy type is some data value in the state or state machine. How skittish the AI is, is a parameter between 1 and 0, etc. Depending on a die and a parameter value, you can pick another edge.

In that way, you can have different responses for different enemies, yet have more state reuse.

Also I would recommend not to be too much focussed on reuse. You seem to be new at this, the idea that you can write highly reusable code at this point is perhaps a bit very optimistic. Try to avoid "do not repeat yourself", by refactoring as needed is probably already a good start. First do the trick a few times to get a feeling for the general pattern.



Remember that

  • states != behavior selection
  • location/target selection != behavior selection
  • most decision-making architectures end up in a "state"... not just an FSM

Dave Mark - President and Lead Designer of Intrinsic Algorithm LLC
Professional consultant on game AI, mathematical modeling, simulation modeling
Co-founder and 10 year advisor of the GDC AI Summit
Author of the book, Behavioral Mathematics for Game AI
Blogs I write:
IA News - What's happening at IA | IA on AI - AI news and notes | Post-Play'em - Observations on AI of games I play

"Reducing the world to mathematical equations!"

check out using the strategy design pattern with a fsm

whenever you have a complicated if-statement that might change or be different for different classes, the strategy pattern is a possible choice.

for example, if a skeleton wakes up and sees a knight, it might attack, but if it sees a priest it might run away, while a dragon might attack a priest and run away from a knight and it can be much more complicated than that

the strategy pattern allows you to insert different behaviors for different monsters that have the same state

This topic is closed to new replies.

Advertisement