Alex Profile picture
Feb 23 22 tweets 6 min read
A few people have asked me about final state machines.

Is an FMS just a bunch of IF statements? Why did I switch? HOW did I switch?

Here are some thoughts. Bring coffee.

First, what was my problem and why did I use an FSM to fix it? #gamedev #architecture #tutorial #howto
2/ In the beginning...

Update() on my original player controller started as 2 ideas:

1) Always apply forward movement
2) If the screen is touched and I'm on the ground then Jump()
3/ This was great because it was fast and easy to create. I didn't have a lot of "management" code to deal with, which helped me build my prototype quickly and get a feel for what kind of game it wanted to be.
4/ Then I added smashing enemies:

If velocity.y < 0 and I'm on an enemy, then enemy.Smash()

And so on until it became a messy 200+ lines, that supported jumping over small gaps, gripping a ledge, auto-rolling over enemies and 1-tile tall walls.
The code started to look like...

if(isTouchingWall && !isTouchingGround && velocity.y < 0 && !recentlyClimbedWall)

... and there were a lot of redundant checks. if(onGround) at the top and the bottom of the method.

Bugs everywhere. Image
6/ The order of the checks was important (handle edge gripping THEN vaulting THEN smashing) and many situations resulted in buggy actions. Like, "Why am I drifting INTO the edge when I'm gripping an edge? Okay, fix that by freezing velocity.x... now why is vaulting is broken?"
7/ I'm sure you've fallen into this trap. It becomes a huge unmanageable mess.

FSMs can help untangle this mess.
8/ Should you use an FSM?

An FSM isn't a feature of your app... it doesn't make your game any more fun. It's the tasty stuff inside the mess that will make people will play your game. In my case, this is the vaulting, the smashing, the wall jumps, etc.
9/ FSMs are an architectural tool to manage your code. It's a strategy for breaking your tasty code into smaller, self-contained and easier to build and debug chunks. If you only have two IFs, then FSMs are a more complex solution that will probably be more work than it's worth.
10/ When you have a big mess of tangled code, or if you want to add a cool new feature but you don't because the idea of changing that giant method is just too much trouble, then FSMs might help.
11/ As a general rule for beginners, you don't need an FSM until you have tasty code that needs a better structure. If this is your first day of game dev, don't start by building an FSM. Write your tasty code, and then MOVE your tasty code into the FSM, if you need to later.
12/ In my case, I have code for gripping edges, and an animation for vaulting a ledge, and the physics for jumping and falling. The problem is that this code was put into one method and that was hard to manage. The FSM allows me to separate the code that I already wrote.
13/ How do you use an FSM?

There are loads of solutions here. The Unity Animator is an FSM and I've seen articles that describe how to use this feature to manage the states of classes. I'm sure there are assets that you can buy in the Unity store.
14/ I rolled my own solution because I've done this before, I can build the features I need now and ignore the rest, and I don't have to learn a new tool.

Here's what you need:

1) A controller
2) A base class for states
3) Concrete states

Only one state is active at a time.
My controller is a MonoBehaviour. It has a pointer to the current state, and all configuration properties that I've decided to make standard between several states, such as the default gravity strength, or the layermask for enemies and the ground. Image
16/ Controller.Update() simply calls state._Update() and the same for FixedUpdate(). It has a method for changing state TryToEnterState(nextState), which... tries to enter the next state =)

It also has helper methods for common checks, such as IsTouchingGround(). Image
17/ My base state, PlayerState, is the parent class of all states that the player can be in. It's small. It provides a pointer to the Controller, the rigid body and the transform. Each state writes its own logic for what happens during update, entering or exiting the state. Image
18/ Here's a simple state, WallSlide. The state can only be entered if the player is falling AND touching a wall AND not touching the ground; the same check that would have been used in the single-big-method solution, but much more focused now and not muddied by other states. Image
19/ When entering WallSlide, I reset gravity, clear the drag and disable all X movement. I'll probably also want to snap the position.x to align to the wall's edge. Image
20/ In Update, I test the state of the object. Walking is always allowed, so I test to see if the player is still wall sliding: IsTouchingGround() || !IsTouchingWall().

I also test if the player touched the screen; if so, I'll try to enter the Jump state and then reset the touch Image
21/ The rules for entering each state and what happens in the state are the tasty code that I'd already written in the single-method solution, the FSM helps me isolate each state from the other, which results in separate, smaller, maintainable classes, one for each action.THE END
Was this helpful to anyone? Should I do posts like this more often or go into more detail?

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with Alex

Alex Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Don't want to be a Premium member but still want to support us?

Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal

Or Donate anonymously using crypto!

Ethereum

0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy

Bitcoin

3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us on Twitter!

:(