Finite State Machines with Unity3D – Part 2

Implementing a Finite Machine System with Unity3D is not very complicated. You just have to figure how to manage a collection of state classes, execute some key methods inside these classes and provide a clean way for the State Machine to be instantiated.

My implementation can be found in this GitHub repository. Once you download or clone it you’ll find these main classes: FlexFSM.cs and FlexState.cs. Also there is the the complete code for the following “miner” example.

The “miner” example found in Mat Buckland’s book Programming Game AI by Example characterizes a far west miner doing his everyday duties: mining, sleeping, drinking and depositing the mined gold in the bank.

Let’s take a look at the miner state diagram to understand his behavior.

You can clearly see the conditions that trigger state changes (transitions).

The first step is to create a script that is going to contain the state machine instantiation, this script must be attached to a gameobject since it derives from monobehaviour. So let’s call it Miner.cs and, since the visible action is only going to be via logging to the Unity3D console, attach it to any gameobject.

Here is the code for Miner.cs.

Miner.cs

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

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

usingSystem.Collections.Generic;

usingUnityEngine;

usingSystem.Collections;

usingMinerStates;

publicclassMiner:MonoBehaviour

{

publicstringcurrentState;

publicFlexFSM fsm;

publicMinerData minerData;

voidStart()

{

InitializeMinerData();

MakeFSM();

}

voidInitializeMinerData()

{

minerData=newMinerData();

minerData.fatigue=0;

minerData.goldInPockets=0;

minerData.thirst=0;

}

voidMakeFSM()

{

fsm=newFlexFSM(gameObject);

Mining mining=newMining(this);

Drinking drinking=newDrinking(this);

Sleeping sleeping=newSleeping(this);

MakingBankDeposit makingBankDeposit=newMakingBankDeposit(this);

fsm.AddState(StateID.Mining,mining);

fsm.AddState(StateID.Drinking,drinking);

fsm.AddState(StateID.Sleeping,sleeping);

fsm.AddState(StateID.MakingBankDeposit,makingBankDeposit);

fsm.ChangeState(StateID.Mining);

fsm.Activate();

}

voidUpdate()

{

if(fsm!=null&&fsm.IsActive())

{

fsm.UpdateFSM();

currentState=fsm.GetCurrentStateName();

}

}

publicvoidLogAction(stringmsg)

{

Debug.Log(System.DateTime.Now.ToString("HH:mm:ss")+" - "+msg);

}

publicboolIsFatigued()

{

if(minerData.fatigue>=500)returntrue;

returnfalse;

}

publicboolHasPocketsFull()

{

if(minerData.goldInPockets>=200)returntrue;

returnfalse;

}

publicboolIsThirsty()

{

if(minerData.thirst>=600)returntrue;

returnfalse;

}

}

The required code to declare the FSM are the top public FlexFSM fsm;, and the MakeFSM and Update methods.

As you can see the actions involved are the following:

Instantiate the FSM (in MakeFSM())

Instantiate states (in MakeFSM())

Add those states to the FSM and link each one to a defined enum (in MakeFSM()). The reason behind using enums is to use them to specify the target state in a state change instead of supplying an object state reference.

Set the first state by calling the FSM ChangeState method (in MakeFSM())

Since it’s an example ive put all the states in the same file. The first thing you will see is the StateID enum and next are the states classes. Each class has the following override methods that will get executed for the current active state in the following order:

OnEnter : Gets executed once when the state gets active. For state setup.

Reason : Gets executed in each FSM Update. It’s used to check for conditions and perform transitions.

Act : Gets executed in each FSM Update. Act’s as the state update.

OnExit : Gets executed once when there’s a state change.

A couple of notes:

There’s also a OnEvent method that can be executed upon a FSM notification, but we will talk about this later.

All these method’s are virtual, so you don’t have to actually override them all if you don’t need to.

Reason being the transition performer is only a matter of convention, you are free to call ChangeState wherever you want.

Each method get’s GameObject owner as a parameter. This is the GameObject that the state machine script component is attached to.

In the next article we will talk about a strategy that may come handy when dealing with a high number of states; hierarchical state machines.