Unity’s Animator Controller as a State Machine

The Setup

First of all, for those who are not familiar with the Animator Controller at all, we would recommend you watch, at least this tutorial by Unity themselves, in which some basic concepts are explained. Also, you should read this other tutorial, also by Unity, which deals with StateMachineBehaviours, especially everything related to the methods they use, how StateMachineBehaviours differ from MonoBehaviours, and how they communicate with each other.

Basic layout of the State Machine. You can see the parameters on the left.
if (InputHandle.Enter) {
anim.SetTrigger(“EnterPressed”);
}

The Good

There are quite a few advantages of using the Animator Controller as a State Machine:

  • Having a clear graphic layout of the states gives you a huge advantage. It helps think about the way the game will be structured and, because it’s very easy to edit, it makes it better than simply drawing the states on pen and paper. It helps think and decide on what the transitions will depend on, and it’s also easy to scale: Adding or removing states is not only simple in the Animator Controller, but it doesn’t require refactoring a lot of code (we actually added a couple of states while working on it).
  • Having the State Machine handle transitions makes the code less bug prone: We avoid having the horrible long series of “if / else if” sequences, there is no need for checking what state we’re in, what value a certain variable has, what state our bools are in, etc.
  • But not only that: The Animator Controller shows all the information we need while playing the game: What state we’re in, when the transitions happen, and what values the different parameters are holding at any point in time. That makes it extremely easy to debug the code in case something goes wrong. You don’t need to write to the console to find out whether a function is not getting called because you never went into the state that calls it or for some other reason: you can see it on your screen. You miscalculated a value? You can see it there. This is invaluable for debugging.
  • This may be a minor one, but you can also change the parameters while you’re playing. You’re running out of lives before managing to get to a certain point in the game? You can add more lives right there in the editor, as you’re playing. You want to see what would happen if a certain trigger was set at some point? You just click it.

The Bad

There are a few caveats, though, the most important that we found were:

  • If a trigger falls in the forest…..
    Seems to make a lot of sense now that we know it, but this one caught us off guard. Triggers are going to remain in their set state until they trigger some transition. So if you have, like we showed above, a trigger that is set when the user hits “enter”, the trigger will remain in the “on” state if you’re currently in a state that doesn’t use it at all. This can cause weird behaviour, of course, because eventually you will enter a state that will use it, and it will trigger an undesired behaviour.
    Especially if you have triggers that depend on user input, what we would recommend is to reset all triggers when states are entered. It isn’t the prettiest solution, but it’s safe, particularly because you can never tell when the user will hit a key by mistake.
  • This one is a bit weirder. The automatically created comments for StateMachineBehaviours clearly state that OnStateExit() “is called when a transition ends and the state machine finishes evaluating this state”. This is not so clear in the documentation, to be honest. What this means, in practical terms is that the next state’s OnStateEnter() may be called before the current state’s OnStateExit() does. Which can be a problem if you have some data which you can only retrieve when the transition gets triggered. This happened to us while setting up the Highscores routine. If you remember Breakout (as most classic 80s games), has this 3 initial system for entering your name when you reached a high score. The only time in which we could retrieve the initials is when the user is done inputting them and presses “enter”, which triggers the next state. This makes OnStateExit() the ideal place to retrieve them: The user will hit “enter”, we get the initials from the UI. The problem was they were not being displayed when the next state was entered because of the order in which things happen. Because all StateMachineBehaviours (unlike MonoBehaviours) get instantiated at the same time, when the AnimatorController does, we worked around it by getting a reference to the next Behaviour and calling a function from it that would write the correct initials to the UI.
    Also, not too pretty. The worst part is that we couldn’t find any definite answer as to whether this behaviour is constant or if in some cases OnStateExit() may be called before the next OnStateEnter().

The Ugly

Looking back at the code, there are a lot of things we would have done differently. We were experimenting as we moved along, and the code is not to be taken as an example of brilliant design, just as a show of different things that can be done.

The Verdict

For handling “big picture” state, we think this is pretty much ideal. Managing scenes, UI elements, keeping track of when to move to the next level or when to transition to a game over state, for those things the State Machine is a powerful tool.

Last, but certainly not least

A big shout out to Colton Ogden and the whole team at CS50 for an amazing and fun course. We’re enjoying it a lot, you should all check it out!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store