In this video, you will learn how to create
a simple state machine that will give you better control over your code, make it easier
to implement ai into your games, and allow you to automate certain behaviours. Howdy! And welcome to Game Endeavor. State machines are a vital part of coding
complex behaviors in your games such as wall-jumping, climbing ladders, and enemy ai. It allows us to organize our code and limit
its scope based on its current status. I use them daily in my developer job, and
in this tutorial I will be showing you how I quickly implement a reliable state machine. Let’s go ahead and get started. We’re going to be creating an abstract state
machine. Meaning you will never use this script directly,
instead you will always inherit from it and call the method hooks that we’ll provide. The state machine will be a script that will
attach to any node, meaning we won’t need a dedicated scene for it, just the script. We need to keep track of the state that we’re
currently in, so I’ll create a variable named `state`. Sometimes it is useful to know what the previous
state was as well, so we’ll create another variable named `previous_state`. The state machine is going to act as a sort
of interface. It’s not going to perform any actions on
its own, instead it is going to check some conditions and then tell its parent what it
should be doing. To do this, we’re going to need a reference
to what we’ll be controlling. So I’ll create an onready variable named
parent, which we’ll set to the return value of `get_parent()`
Every tick, this state machine is going to have two phases. For the first phase, it is going to call the
logic required for the state. For example, in a jump state, we would allow
the player to move left and right, we would apply gravity, and then we want `move_and_slide`
to be called. But if we were in a wall-jumping state, then
we would not want the player to move horizontally else they would fall off the wall. So we’re going to create a method that will
handle the logic, which we’ll name `_state_logic.` We’re going to be calling this method in
`_physics_process()`, so we’ll want to access delta. Go ahead and add that as a parameter. This is going to be a virtual method, meaning
it’s expected that we’re going to override this method in the scripts that inherit from
this. So we’re not going to write any code for
it here, we’ll simply close this method with a pass statement. Next, we want to handle the transitions that
change our state. This is where we’ll determine if we can
go from standing to jumping, or jumping to falling. However you may not want to go from wall jumping
to moon walking, and this method will be how we manage those transitions. So we’ll make another method named `_get_transition()`. Although not as useful to us here, we’re
also going to pass in the delta parameter, since we’re also going to be calling this
in the `_physics_process()` method. This method will be virtual as well, but we’re
expecting it to return a value from it, so for now we’ll have it return null. Not only do we want to perform the state logic
every tick, but often we will also want to perform some logic once when we enter a new
state or as we’re exiting a state. So to prepare for this we’ll create a couple
of methods. The first being `_enter_state()`. This will take two parameters, the first being
`new_state` which will be the state that we’re transitioning into. This is the most common value that you’ll
be checking for. The 2nd parameter will be `old_state` which
we’re transitioning out of. In this method, it’s very common to do things
such as setting your animation for the state, or starting tweens and timers. This method is also virtual, so we’ll close
it with a pass statement. The next method will be `_exit_state()`, and
this will need the parameters `old_state` and then `new_state`. And finally we’ll close this off with a
pass statement as well. Now that we have these methods. We need some sort of mechanism for changing
our states, so we can ensure that these methods are properly called. So we’ll create a method named `set_state`. This will need a parameter of `new_state`
so that it knows what state to change in to. Since we’re keeping track of our previous
state, we can update our variables first and then call the enter and exit methods. So we’ll set `previous_state` equal to `state`,
and then we will set `state` equal to `new_state`. First, we’re going to call our `_exit_state`
method, so we’ll make a call to that and give it our `previous_state` as the first
parameter, and then `new_state` as the 2nd parameter. However if our `previous_state` is null, then
there is no reason for us to call this method, because there is no state for us to exit from. So we’ll add an if condition before this,
checking to see if `previous_state` does not equal null. Now that we’ve exited the state, we want
to enter the next state by calling our `_enter_state` method, and then pass to it our `new_state`,
and then `previous_state` parameters. But again, there’s no need to call this
method if we’re not entering a state. So we’ll preface this with an if condition
checking to see if `new_state` does not equal null. And that’s the mechanism for changing our
states. If you want to call this automatically anytime
the state variable is set outside of our script, then we can set our state variable to use
`set_state` with setget. Keep in mind though that in this script and
scripts that inherit from it. We will need to call the `set_state` method
directly. Now that we have these methods, we will go
ahead and create our `_physics_process()` method. We’re going to check to see if our current
state is not equal to null. There’s no reason for us to perform state
logic or transitions if we have no state. And then, inside of this if condition we will
call our method `_state_logic()`, and pass to it the delta parameter. We’re going to call `_get_transition()`
as well, passing to it the delta parameter, but we’re going to store its return value
in a variable named transition. Which we’ll immediately check to see if
it is not null. If it’s null then we’re going to assume
that we shouldn’t change our state, otherwise we need to change to our state, so we’ll
call our `set_state` method, passing to it the transition variable. The values that you use for the states are
entirely up to you. The only requirement is that they need to
be unique in some form or another. You can use enumerators, string constants,
and even integers if you really wanted to, but don’t be that guy. The thing to keep in mind however, is that
you may need to have nested inheritance of your state machines. So if you use an enumerator, then you will
be incapable of adding more states to the enum. What I’m going to do is set up a dictionary
of states that we can append to and use. So I’m going to create a variable named
states, and initialize it as an empty dictionary. Then I’ll create a method named `_add_state`
and it will be taking a string value as a parameter. And inside of this method, all I’m going
to do it set `states[state_name]` equal to the size of the states dictionary. So the first entry will start at 0, the 2nd
entry will be 1, and so on. You shouldn’t remove states from this, but
you will be able to add to them. There’s no real advantage to doing this. I just think it looks cleaner, and it allows
you to print out a list of your possible states. Now all you have to do to create a state machine
is inherit from this script, copy over the abstract functions to your new script. Add your states in the ready method, and then
set your initial state. You will most likely want to set your first
state using the `call_deferred` method. This will set the state during the idle time
which will allow the parent to populate their properties and be ready for manipulation. Because this state machine is a child of the
parent, it will load before the parent. To help demonstrate how you would use this
state machine in your projects, I have created a simple enemy ai that will stop moving when
you’re looking at it. But when you turn away from it, then it will
chase you. And once it is within attacking range, it
will jump at you as an attack. However it will not attack you if you are
looking at it. It will also turn to face you, but only if
it is not attacking. Without a state machine, your code would be
full of complicated if statements for each of these conditions, and this is a simple
ai with only three states. But with a state machine it is far more streamlined,
easier to read, and a lot less prone to bugs. I won’t go over all of the code right now
because we will cover enemy ai in a future video, but as you can see, we have our `_state_logic`
method that handles what the parent does. For every state we’re applying gravity. Then we check to see if we should turn, but
only if we’re not in the attack state. We chase the player if we’re in the chase
state, otherwise we stop moving. And then we use `_apply_velocity` to actually
apply the movement. In the transition method we use a match statement
to check which state we’re in, and if our state should change. For example if we’re in the sleep state
but we should chase, then we change our state to chase. Or if we’re in the attack state, which is
a jumping attack, then once we’re on the floor we will set our state back to the sleeping
state. And then we have our `_enter_state` method
which in this case is primarily used for setting animations. But as you can see, when we enter the attack
state we also call the parent’s attack method, which is what causes the sleeping angel to
jump towards the player. In future videos I will be showing you how
we can use this state machine to perform wall-jumping, enemy ai, and more. So if you’re new then join the sub club
to get notified for future videos. And until next time, y’all take it easy.
Great to see a little more advanced state machine so well explained!
Thank you for this very good tutorial.
I am using a similar approach for my states but I have separate classes for the actual states to avoid the if logic within the StateMachine. Each State class handles the enter, update and exit logic for the state it represents and the StateMachine handles only the transitions between the states.
You could also think about the StateMachine as a stack of states, where only the state on top of your stack is processed. Entering a new state would just add the new state to the top of stack. Exiting a state would pop the state from the stack and automatically fall back into the previous state. For instance if your default state is Idle you could always have it at the bottom of your stack. If the player presses the jump button you would add the Jump state which would exit if you touch the ground again and you would be back in your Idle state.
This stack like StateMachine is especially useful for game states. Let's say you press ESC while your are playing, a Pause state would be added to your stack which would pause the game and show an Options menu. Clicking on the continue button would remove the Pause state and you would be back in your game play.
I learned that some years ago when I was working with the Ogre3D engine and I would like to share the link, even it is C++ code. But it was very helpful for me to understand how that could work:
http://gamedevgeek.com/tutorials/managing-game-states-in-c/
http://wiki.ogre3d.org/Managing+Game+States+with+OGRE
Anyway, I like your approach, it is simple and easy to understand. Thank for sharing.
Awesome. I am a computer scientist with some junior software engineering experience. I know how to make complex state machines if I can design everything from scratch. But with Godot, as simple it makes some things, it sometimes also opens some new questions. How to implement a complex state machine was one of them that explicitly floated my mind just recently. It feels a bit like you read my mind.
Great topic, especially considering the lack of such tutorials for Godot! Subscribed to channel!! Thank You!