A Finite State Machine (FSM) in a more technical term is a mathematical modal of computation where an “abstract”
machine is able to be in only one of a finite number of states at any given time.
Let’s take a look at a few real world examples.
A light switch can either be on or off, but never both at the same time.
You can either be asleep or awake, but never both at the same time.
A computer screen is either on, off, or sleeping, but never two or more at the same time.
In game programming, a simple example of a finite state machine is the main character.
In many games, the main playable character are usually programmed in a way that represents a finite state
machine.
For example, you may have noticed that a main playable character can do certain things such as the
following:
Idle
Run
Jump
Fall
In this case our character can run, jump, fall, and stand idle still, but it can only do one thing
at a time.
In GDScript, this player state may look something like this:
#hero simple state machine exampleextendsNode2Denum PLAYER_STATE {IDLE, RUN, JUMP, FALL}
export(PLAYER_STATE) var currentState = PLAYER_STATE.IDLE
func _physics_process(_delta):
match currentState:
PLAYER_STATE.IDLE:
print("Inside of Idle State")
PLAYER_STATE.RUN:
print("Inside of Run State")
PLAYER_STATE.JUMP:
print("Inside of Jump State")
PLAYER_STATE.FALL:
print("Inside of Fall State")
Adding Simple Finite State Machine to our Pong Game
We can use the FSM in our game by adding different “menus” for the player.
We can have the player on the main screen state, and when they press a button (spacebar) they can then move onto
another state such as the serving screen state. From the serving screen state they can then move onto the play screen state
in which the game can start, and the player can “play” the game.
When the player wins or losses a point, the player will be taken from the play screen state to the serve
state where they can then go ahead and serve the ball again.
Once the player wins or losses, they will finally be taken back into the main screen state where the player
will be told of whether they won or lost.
Brute Force Coding the Finite State Machine
Brute force coding is writing out the first solution that comes into your head, not doing any form of
refactoring. Refactoring will be done towards the end of this series. For now lets just focus on getting
this done.
First let’s start by creating our variables that will hold our states.
Enums and Booleans are the perfect data type to hold states if the programming language provides them.
Lucky for us Godot GDScript has both.
Let’s add two states, one for the game screen (Game State) and one for the ball that we will use in the future.
Next let’s add a switch statement case inside our _physics_process method.
func _physics_process(delta: float) -> void:
match currentGameState:
GAME_STATE.MENU:
print("inside of the menu state")
GAME_STATE.SERVE:
print("inside of the serve state")
GAME_STATE.PLAY:
print("inside of the play state")
The physics process virtual method is the perfect place to store the states of our “screens” as our game will
constantly run the _physcis_process loop when the game starts.
From there let’s add some player input to test our states and on top of that let’s change the string that is shown
at the top of the screen.
There is a problem here. If you were to run this, the moment you tap on the space bar you will immediately jump between states.
To mitigate this issue, let’s add some sort of logic that will make sure there is a delay between when presses will register.
First lets add the variables to make sure there is a delay between registering key presses to the top of the screen.
You’ll come to a point in your programming journey where your simple state machine is no longer “simple”.
You’ll have two choices, continue to use the current simple state machine as found on this article or evolve your code to use the
state pattern. The state pattern was built to combat the limitations of using switch statements for changing states.
In this series for beginners I will stick to the finite state machine (FSM).
Hello, Godot Tutorials is not sponsored by or affiliated with Godot Game Engine, let's go over what a finite state machine is.
A finite state machine is a mathematical abstraction where an abstract machine can be in exactly one of a finite number of states at any given time. In programming, we can add state machines onto objects and change its state based on inputs. A quick example. So in this case we have two states and a awake state and and asleep state. In this case, we are awake because art is tired. Function returns back false.
However, what happens if is tired becomes true. Well, we change from the awake state into the asleep state. In this case we have another function called is rested and as long as is rested is equivalent to false, we will be in the asleep state. However, once R is rested, function returns back, true, we will then switch to the awake state. And so in this case you can see our finite state machine can change between two states, the awake state and the asleep state.
In game programming. It is very common that game objects have multiple states for different categories. Just keep in mind that they must be independent of each other. For example, we have a character state machine for player movement and in this case we start in the idle state. However, at some point our idle state can change into the run state and our run states can change into the jump state.
And at some point or jump state needs to turn into the falling state because what goes up must come down.
And lastly, as we are done falling, we change back into the Idle state.
Now, this looks like a basic state machine for movement, however, we can add onto this, for example, or idle state can change immediately into the jump state and vice versa, or jump state can turn into the idle state. For example, if we jump onto a ledge, we will not fall.
In addition, if we are running, we can also stop running and return back into the Idle state.
Now, let's look at another example where we have additional states that are independent of our character movement state. In this case, we have a character state for health and we have two choices being awake or asleep. We start in the awake state and at some point we can, in fact turn into the asleep state and vice versa from the asleep state, we can transform ourselves into the awake state.
Now, keep in mind that even though our character has two different states, it manages the health state and the movement state. The health state is independent of the character movement state. This means that health does not directly affect the state of movement and vice versa. The states of our character movement does not change or directly affect the states of our characters. Health, state, machine.
Let's go ahead and write some code. So first, let's start off by creating our states in this case, I'm going to go ahead and use it to store our state's.
To do that, we use the key word, followed by the name we want to give our enemy, in this case, I'm going to give it the name game state, all capitalized, by the way, because these are considered constants.
And on top of that, I'm going to create three different states in menu state, a serve state and a police state from the menu state. We will then transition into the serve state and from the serve state. We will then transition into the play state.
Lastly, from the place state, we will then transition to the serve state and so there will be a loop between these serve and play state. I'm also going to create a serve state. This is to make sure we know which direction the ball will take in a later episode. In this case, we're just going to use a simple variable called IS Player Serve and assign that the value true because we want the player to serve first. We can change this value to false when it's time for the right to serve. However, we will not be doing that in this episode.
I'm going to go ahead and change this value back to true. All we did was create enum values for game states, so let's go ahead and actually assign that to a variable. In this case, let's go ahead and create a variable called Current Game State, and we will assign it a value from our home game state.
I assign the value menu because we want the player to start in the menu state.
Heading over to the physics process, a virtual method.
This is where we will actually change our code based on the state we are in. I'm going to use a match statement, however, feel free to use an if statement if you want. Regardless, we need to test the value inside the variable current game state. And in this case, because I'm using a match statement, I can just list out each individual enum.
To reiterate, the current game, state variable is the expression we are testing against and each individual enum is what we are testing the variable current game state with. Now let's actually do something with our game state for this example. Let's go ahead and change our string value that we present to the player on the screen based on our game state. On first glance, this looks pretty good.
However, when we run it, it may appear that our string values are centered. However, let me go ahead and change this. Add more sentences onto our string and if we run our game, you can see that our string is not actually centered. It only gave the illusion that it was centered based on how small the text was. This problem actually stems in the ready virtual method. As you can see here, we are calculating our width of our font and the string position before we actually change the string value.
The solution is quite simple. All we have to do is take that logic outside the already virtual method and apply it after we have changed the string value inside of our match statement. Now, when we run our code, we can see that our string is now centered when the game changes states. In this case, we are not really changing states. So let's go ahead and add some player input listeners and change states when, for example, the player presses the spacebar.
To do this or we have to do is use an if statement and using the global class input followed by the is key prest method, it takes in a scan code, which is just an integer value. Lucky for us, Godot does provide global enums for scan codes. In this case, we want to test whether or not the player has press the space bar when the space bar has been pressed. We will need to change the current game state variable.
So in this case, since we are inside the menu game state, when the space bar has been pressed, we will change it to the surface state. In this case, let's go ahead and copy everything because the code will be the same no matter what state we are in, since we are just changing string values. Also, one thing to note is that we need to call the drome method every time we want to update values inside or draw methods.
In this case, I use the update method in order to call everything inside the draw method. Now, as you can see here, every time we press the spacebar, we change games states. However, if I were to hold down the spacebar button, notice what's happening here, it just spams out. So let's go ahead and fix that. At the top of our file, let's go ahead and create a variable that we can compound our Delta time in order to make sure that there is a delay when changing game states.
In this case, we're going to create a variable called Delta Key Press and assign it a float value of zero.
At the top of our physics process, virtual method will go ahead and compound the Delta value onto our variable called Delta, keep make sure to remove the underscore in the Delta variable, as we will now be using the Delta argument provided to us by the physics process, virtual method to compound the time value onto our variable called Delta Key Press.
From there, we will make sure to add an extra logic check instead of our IF statements. So in this case, if our player has press the space bar and if more than zero point three seconds have elapsed since the last space bar, key press, go ahead and change the game state and we will add this logic into every if statement. We do have to do one more thing, and that is every time our player has press the space bar, we need to reset our Delta Key press value back to the float value of zero.
Now, if I were to run this game, you can see that if I hold the spacebar, there is a little delay between changing our states based on key presses from the player. Now, there is a little bit of a problem here, as you can see, I hardcoded literally float values into our game logic, hard coding, literal values is considered magic numbers. And what that means is that we don't understand what exactly our numbers are referring to.
We don't understand what the purpose of zero point zero is and what the purpose of zero point three is. To fix this or we have to do is extract these literal values and assign them to variables or constant variables. So let's go ahead and do that. In this case, I'm going to head back to the top of the file and create two additional constant variables to hold our never changing values. The first constant will be called Reset Delta Key, and it will hold the float value of zero.
The second constant variable will be called MAXXI time, and it will hold our total delay time, which will be zero point three. And then go ahead and assign the value of reset Delta Key onto our variable called Delta Key Press. Removing our magic numbers is as easy as replacing our literal values with our constant variables. Having our code structured this way gives us an additional benefit of testing out new values.
In this case, I set the delay to two seconds and as you can see here, holding down the space bar has quite a delay after testing that two seconds is too long, I'm going to go ahead and change that value back to zero point three. And as you can see here, the benefit of using variables or in this case, constant variables over magic numbers is that we have a single point of truth, a single place where we can edit code and see the changes propagate throughout our entire code base.
Now, I want you to notice something here we are basically duplicating the highlighted code in multiple places, we don't necessarily have to do this. And so what I'm going to do is a technique called function extraction and function extraction. We go ahead and we take out certain lines of code if they follow or fulfill a specific goal.
In this case, the goal of the highlighted code was to change the string value that was displayed for the player. So let's go ahead and create a function called change string with one parameter that takes any value which we will name new string value. What we do is we take our variable called string value and replace it with the new string value passed inside our parameter. On top of that, we need to calculate the width of the font and the string position for centering our text on the screen.
So I'm going to go ahead copy paste that into our change string function. Lastly, we need to call the update method. This is so we force a call to the draw virtual method so we can see this string changes appear on our game screen. Lastly, all we need to do is delete those duplicated code lines and replace it with our new function and inside the function we pass in the string value we wish to show on the game screen when the game state changes.
When we run the game, when we press the spacebar, you can see that the changes are, in fact, propagating to the game screen. Let us recap the refactoring techniques done in this episode, a magic number is a literal float or integer in code that provides no context. Magic numbers provide context only to those that have worked on the code and is heart for new people to the project to grasp the usefulness of the hard coded values to fix the issues that magic numbers provide is to extract them into a constant variable.
Next is the function extraction refactoring technique, a function extraction is just taking pieces of code and adding it into its own self-described function. Here we have a function called Change Health, and we print some statistics out onto the console. And instead of leaving that inside the function called change how we remove it and create a new function called display stats that we now can not only use inside of our function called change health, but we can use it inside of other functions and places inside our code as well.
If we can extract a function, then we can also insert a function. And so in this case, we have a technique called function insertion or inline function. Function insertion is taking code out of a function and adding it inside where it is needed. In this case, we have a function called Display New Health and all it does is print to the screen our new home. And in most cases this would be fine. However, in our example, we only need this in one place. In this case, it is better to insert or one line of code where it is needed rather than having it inside of its very own function.
That's all I have for you in this episode. Thank you so much for joining me. Thank you for clicking the like button and thank you for clicking the subscribe button. I look forward to seeing you in the next episode. Have an amazing day.
When should I upgrade the FSM to the state pattern?
Ultimately this will be up to you. A good indicator is when you feel overwhelmed when adding new states
to your Finite State Machine.
Are there resources in which I can learn more about the state pattern?
I have those in the Resource Tab.
My favorite is the Refactoring Guru website.