A simplistic aproach to the Finite State Machines in Godot
Tags: gamedev, godot, architecture, tutorial
Published: 2022-06-28 04:17:51

## Table of contents
- [What's a Finite State Machine?](#what-is-a-finite-state-machine?)
- [The Structure](#the-structure)
  - [The State Class](#the-state-class)
  - [The FSM](#the-fsm)
- [Practical Examples](#practical-example)
  - [A simple player controller](#a-simple-player-controller)

---
## What is a Finite State Machine?
When coding how an entity will behave in your game, you would typically write all the behaviours in one big script file.

```gdscript
func _process(_delta):
  if !in_air:
    if(Input.is_action_pressed("move_left")):
      move(Vector2(5,0))
    if !moving:
       pass # Do even more cool stuff
# Etc...
  
```
The biggest issue with this script is that, when time time comes to implement another feature into your entity's behaviour, it wil become much harder for you to do it and the code will look even more messier than before. But worry not because the ***Finite State Machine*** came to the rescue!

<!-- From what you can see, after some time your code will become bloated and more and more harder to read. The solution? Using a (FSM) -->

To simply put, the FSM is a type of "system" that controls the states of an entity and keeps tracks of all the states available to it. We'll see in the [Practical Example](#practical-example) how do we implement one

Now, To clean up the code, we'll break up the code from above into multiple *states*, coresponding to their specific actions like: idle, moving, jumping. 

## The Structure
### The State Class
At its core, a state has three main functions
```gdscript
func on_state_enter():
  pass


func on_state_update():
  pass

func on_state_exit():
  pass
```

Both the enter and the exit methods are called when the states are changed. We can now implement the behaviour of our state as well as checking when a certain condition is met in order to switch to another state.

```gdscript
func _on_state_update():
  if moving:
    move(Position)
  else:
    change_state("Idle")
```

### The FSM
In our system, the state machine will only take care of the states. At it's core, it will only contain one important function that we will frequently use across our states.

```gdscript
func change_state(state):
  _current_state.on_state_exit()
  _current_state = state
  _current_state.on_state_enter()
```

In the next part of this tutorial, we will see how to implement this system so that we can make it functional.

## Practical Example
Enough with the theory. It's time to put everything into practice. We've discuss earlier about some principles of the FSM, but how do we exacly implement it? Firstly, let's implement our ``State Class``. 

```gdscript
# State.gd
class_name State
extends Node

var fsm = get_parent()
var parent = fsm.get_parent()

func on_state_enter():
  pass


func on_state_update():
  pass


func on_state_exit():
  pass
```

This will be our base class for our states. Later on, we will create other classes for our states and we will derive all the features. Now, let's have a look at our Finite State Machine Class.

Like I've mentioned before, our state class will only have one essential function in order to be functional (That being the ``change_state()`` function). In a way, this is partially true, but it's not enough to be functional. Let's have a look at the code.

```gdscript
# FiniteStateMachine.gd
class_name FiniteStateMachine
extends Node

var _current_state = null

func change_state(state):
  _current_state.on_state_exit()
  _current_state = state
  _current_state.on_state_enter()

```

But wait! Something doesn't really seem quite right, does it? Our game knows how to change the states, but how does it know what kind of stated the FSM has? Let's fix this!	

![FSM Node Hierarchy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rnajee4mvtqg3gb0k2bw.png)
 

What needs to be done now is to append the states to the FSM as children, and when calling the ``change_state`` function, we will use the name of the State's node as a parameter. The method will get the child node with the parameter's value and it will store it in the ``_current_state`` variable. As simple as that...

```gdscript
# FiniteStateMachine.gd
class_name FiniteStateMachine
extends Node

var _current_state = null

func change_state(state: String):
  _current_state.on_state_exit()
  _current_state = get_node(state)
  _current_state.on_state_enter()

```

<!-- We can append the states of the FSM as childs and when the game changes the state of our state machine with a name as an parameter, we will store the node of a state with that specific name in mind, in the ``_current_state`` variable. --> 

One more thing that we forgot to implement is the update function. In our states, we want to check the rules and change the states. The only piece that's missing here is the infinite loop that calls our ``on_state_update()`` function.  In addition, our _current_state is null by default. A good practice would be to initialize when the entity is ready.

```gdscript
# FiniteStateMachine.gd
class_name FiniteStateMachine
extends Node

var _current_state = null

func _ready():
  _current_state = get_child(0)
  _current_state.on_state_enter()
  

func change_state(state: String):
  _current_state.on_state_exit()
  _current_state = get_node(state)
  _current_state.on_state_enter()


func _process(_delta):
  _current_state.on_state_update()

```
Aaand that's it!
In the following example, we will try to control our player using the state machine.

--- 
### A simple player controller
Let's consider the following scenario: We have created our player but it does nothing. Neither does it move, neither does any other cool stuff. What are we going to do to define it's behaviour? 

![The player](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w9q95edjb6321lvk0908.png)

Before we move on, let's consider the fact that we already have a Finite State Machine implemented in the first place.

In this example, our player will only be able to move, but feel free to add as many states as you need. Let's implement the following states, shall we?

[![State Graph](https://mermaid.ink/img/pako:eNo1jjEKwzAQBL8itrY_oCKVU7hIlZRqDt_ZFrEko5wCwfjvkU1SLAzLLOyGIbHAYsq0zubRuWjMkZ4XMW17Mbf09nE68ehcRIMgOZDnOttOHzpLEAdbkSk_HVzcq1dWJpUre00ZdqTlJQ2oaLp_4gCruchf6jzVC-Fn7V9DrzIG)](https://mermaid.live/edit#pako:eNo1jjEKwzAQBL8itrY_oCKVU7hIlZRqDt_ZFrEko5wCwfjvkU1SLAzLLOyGIbHAYsq0zubRuWjMkZ4XMW17Mbf09nE68ehcRIMgOZDnOttOHzpLEAdbkSk_HVzcq1dWJpUre00ZdqTlJQ2oaLp_4gCruchf6jzVC-Fn7V9DrzIG)


Let's start with our player script. We will want to check when the player moves.

```gdscript
# player.gd
extends KinematicBody2D

var is_moving = false

func _process(_delta):
	if Input.is_action_pressed("move_right") or Input.is_action_pressed("move_left"):
		is_moving = true
	else:
		is_moving = false
```

Now that we can check if the player is moving or not, we can proceed to the ***creation of the states***. Firsly, let's implement the idle state. When a state will become active, we will want to print the current state in the console and then check the rules for the transition.

```gdscript
# idle.gd
extends State


func on_state_enter():
  print("Entering state: ", self.name)


func on_state_update():
	if parent.is_moving:
		fsm.change_state("Moving")


func on_state_exit():
  print("Exiting state: ", self.name)
  pass
```

While in the idle state we are not doing anything special, in the moving state we'll have to define the movement of the player.

```gdscript
# moving.gd
extends State


func on_state_enter():
  print("Entering state: ", self.name)


func on_state_update():
	if !parent.is_moving:
		fsm.change_state("Idle")

	if Input.is_action_pressed("move_left"):
		parent.move_and_collide(Vector2(5,0))
	elif Input.is_action_pressed("move_right"):
		parent.move_and_collide(Vector2(-5,0))



func on_state_exit():
  print("Exiting state: ", self.name)
  pass

```
---
![Final Player Node Hierarchy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ium9n14n1jpziowuayuq.png)
 
 
Now, the only thing left to do is to attach your FMS and states to your player node and it should work like new!

![FSM Demo](https://media.giphy.com/media/Z5od9XNe2T7msqtD8q/giphy.gif)
 
Congrats for making it so far! Now your player is able to move like never before, with a FSM on the backend! 🎉

You can find the final project [here](https://github.com/sebiTCR/Tutorials/tree/master/Godot/FSM)