Finite State Machines with Unity3D – Part 4

State machines are meant to have a predefined flow upon already known transitions, that’s why we label them as deterministic. Sometimes we may stumble upon a special state that is going to be a possible state change target for every state, these can be named as global or blip states.

For example, let’s think about a grunt in a game like Warcraft. The grunt is a semi-autonomous agent that will go back and forth from a resource to a destination (you may also picture the miner example from the previous post with his mine – bank route). Sometimes an especial situation will disrupt the current task, like our grunt being attacked. In this case the grunt could flee no matter what the current state is and resume the previous task when the enemy is out of range. It would be convenient to just tell the FSM “hey, change me to this other state and when I tell you bring me back to my previous one”.

We can do this by calling these two FlexFSM methods:

PushBlipState(Enum stateID)

RevertBlipState()

Talking with FlexFSM

Sometimes we will need states receiving information from other state machines or entities. One option to make things clean is to use an observer pattern based system, such as the one found in the Unity3D wiki. Another option could be to implement a FlexFSM method that upon call would notify the current state for a given event and pass along some useful information.

The latter is implemented in FlexFSM by maintaining a collection of enums (as event identifications) that will be associated to states. The following describes how it works:

An Enum with eventID’s must be declared.

Events will be explicitly related to classes using the FlexState.AddEvent(Enum eventID) method.

A call to FlexFSM.NotifyEvent will trigger the notification.

Notifications will be received by the state’s OnEvent override method if available.

Note that we are dealing with a non-type-safe way to provide arguments so you will have to cast accordingly inside the OnEvent method. Also to make things safer and deal with more than one event type per state always check the incoming eventID inside OnEvent.

To be sure we are still in the same page here is a quick and dirty example.

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

publicenumGameEvents

{

SkipIntro

}

publicclassGameFSM:MonoBehaviour

{

publicFlexFSM fsm;

voidStart()

{

MakeFSM();

}

voidMakeFSM()

{

fsm=newFlexFSM(gameObject);

GameStates.Intro intro=newGameStates.Intro();

Intro.AddEvent(GameEvents.SkipIntro);

fsm.AddState(StateID.Intro,intro);

//Code

}

publicvoidSkipIntro()

{

fsm.NotifyEvent(GameEvents.SkipIntro,null);

}

//Code

}

publicclassIntro:FlexState

{

//Code

publicoverridevoidOnEvent(EnumeventId,List<object>args)

{

if(eventId.Equals(GameEvents.SkipIntro))

{

//Code

}

}

//Code

}

Bonus tip: Debug labels

You can easily attach text labels to FlexFSM driven entities showing their state by querying the currentState variable. The declaration and update of this variable is clearly seen in the miner example.

This is easily done by creating a TextMesh prefab and making it a child of the FlexFSM gameobject. Tune the position of the TextMesh, refresh his text property to match the currentState string and you are done.

Where to go from here

Very complex behaviors have two common traits: high number of states and transitions. This is usually the problem in the AI department where, even with the aid of hierarchical state machines, things may turn in a pile of unmaintainable spaghetti.

An alternative for state machines can be found in behavior trees. These organize overall logic into nodes that can be actions or can control the execution flow of these actions.