How to Code a Simple State Machine

Have you ever written a method with two paths of execution? It probably had an if statement based on a member variable, right? But then your requirements changed, and you added another condition to handle a new state. And I bet they change again, over and over, until your code became a mess of hard-to-read flow logic and conditional statements.

If that’s you, then you should learn about State Machines. State Machines are an implementation of the State Pattern, a behavioral design pattern that enables an object to control its internal behavior at runtime. In this Unity tutorial, I’ll walk you through the implementation of a simple State Machine that handles the logic of a turn-based battle system.

Thanks for that Tutorial. It opened my eyes to where I was going wrong with my own state machine setup.

I’ve not watched the whole thing, just clicked through it. Some points may be covered.

Polymorphism doesn’t play well with unity serialization system.

Serialization is necessary if you want unity to be able to immediately utilize changes you did in the code while the game is running in editor. I think it was called hot-plugging or something. Your “protected State” variable will be nulled and you’ll have to restart the level.

If you wish to keep serialization working then your State needs to be converted into a component meaning it will become a derivatives of MonoBehavior, and “state” woudl need to be marked with [SerializeField]. In this scenario you’ll gain benefit of unity component system, meaning each state will be able to gain access to StateMachine via gameobject’s GetComponent function. Additionally, internal condition of each State will be visible in inspector, and you’ll see - in inspector - which State currently is being used. It may be worth keeping in mind that this sceanrio will have reduced number of memory allocation, meaning GC works.

If you DON’T want to keep serialization functioning, then you don’t need State class at all, and instead can simply utilize System.Func or an equivalent delegate (public delegate IEnumerator State;, IIRC). The delegate will eat methods and classes, and in this case all possible behaviors can be part of the original state machine, meaning they’ll have easier access to shared data. Wha’ts more you can even use lambdas there, meaning flexibility becomes very high.

You can go further than that. By default a single state StateMachine will not support nested state machines, because there’s only one “State state” variable. You can fix this, by ditching “State” variables and… switching to coroutines. Coroutines automatically handle nested call, and are spread across multiple frames automatically.