How to Create a State Machine in Godot 4

Ready to elevate your game development skills? Discover the magic behind creating a robust Godot state machine to manage your in-game behaviors effortlessly!

State machines are a brilliant tool in artificial intelligence (AI) which allows a game character to transition between different behaviors or ‘states’. This powerful system not only makes AI easier to manage as it becomes more complex, but also allows for easy sharing of different states among various AI types. In this tutorial, you’ll learn how to set up a Godot state machine and create different states for your AI.

What’s required of you for this tutorial? A familiarity with Godot and a basic understanding of GDScript; no previous knowledge of state machine implementation is required. However, you can also review our full course, Make an AI State Machine in Godot 4 which covers AI State Machine concepts more in-depth.

Project Files

If you want to work with similar materials we’ll be basing this tutorial on, you can download these asset files below. Though not the focus of this tutorial, they should help with implementing the techniques described here!

Download Project Files Here

CTA Small Image
FREE COURSES AT ZENVA
LEARN GAME DEVELOPMENT, PYTHON AND MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

State

To start our Godot state machine, we will begin work on the state machine code itself, specifically working on the states.

A state machine is a method of building game AIs that are required to perform multiple different actions. State machines allow us to define these actions as different states, which the AI can then transition between depending on its needs. A simple example of this is an enemy AI that will normally wander – however, if the player gets within range, the enemy will start chasing them – finally attacking them if the enemy gets close enough. This example contains three different states:

  • Wandering
  • Chasing
  • Attacking

This could be done in one simple script, however, as you make more complex AIs this becomes hard to manage. This is why we use state machines as it makes it much easier to organize our AI’s behaviors. A final advantage of state machines is that they allow different states to be shared between different types of AI, making state machines very powerful when working on larger projects.

Creating the State Machine Script

Before getting started making states, we will first create a Godot state machine script so that we can reference it later in this lesson. However, we won’t be adding any functionality to this until the next lesson. To do this, we will right-click in the FileSystem window and select the new script button.

FileSystem window

We will call this script ‘StateMachine.gd’ and press Create.

StateMachine.gd script

We can delete the _ready and _process functions from this script, as they won’t be needed. So that we can use this script as a variable type, we will define a class_name of StateMachine at the top.

class_name StateMachine
extends node

Writing the State Script

With this lesson’s goal in mind, we are ready to begin creating the states for our Godot state machine. We will begin by making a new script in the FileSystem window.

new script

We will call this script ‘State.gd’ and then press Create.

State.gd script

For this script, we can then delete the _ready and _process functions, as we won’t be needing them. So that we can extend this script later, we will give it a class_name of State.

class_name State
extends Node

Defining the Variables

This script won’t be holding any AI behavior, instead, it will act as a base that our future states can extend from. With this in mind, we will need a few variables. The first will be called ‘active’ with a type of bool. This will keep track of whether this state instance is the currently active state for the parent AI.

var active : bool

We can then store a reference to the parent StateMachine in a variable called ‘state_machine’.

var state_machine : StateMachine

Our AIs also use the AIController script. This will be needed if our state is doing any kind of movement, so a reference to this can be stored in a variable called ‘controller’.

var controller : AIController

To set a value for controller, we will need to create a new variable called ‘controller_path’ with a type of node_path. This will use a tag of export_node_path, which will allow us to set the controller variable through the Inspector window. We can’t use a normal export tag here, as AIController is not a serializable type in Godot (since it is a custom class).

@export_node_path("AIController")] var controller_path : NodePath

Defining the Functions

Our states will also require a range of functions that can be called from the state machine as required. However, as this state script is intended to be extended, most of these functions will be designed with the intent of being overridden in further state scripts. You can think of this as the functions in this script are blueprints for our future states. The first function will be called ‘initialize’ and will be called when a new state instance is created.

func initialize ():

This function will need some code to set the variables that we have just created. The first line will set the state_machine property, which can be set to the parent node, as this will be the state machine.

func initialize ():
    state_machine = get_parent()

We can also set the controller property to the node at the defined controller_path (which will be defined in the Inspector window).

func initialize ():
    ...
    controller = get_node(controller_path)

The next function will be called ‘enter’ and will be run whenever the Godot state machine transitions to this state. With this in mind, we can also set the active variable to true here.

func enter ():
    active = true

The function opposite of this will be called ‘exit‘ and will run whenever the state machine transitions away from this state. Here, we can reset the active variable to false.

func exit ():
    active = false

We will add another function called ‘update’ which will be called once per frame, similar to how _process works normally in Godot, although we can not use that for our states. Like _process, the update function will also have a parameter of delta.

func update (delta):
    pass

We can then do the same thing for the _physics_process function, which we will call ‘physics_update’.

func physics_update (delta):
    pass

The next function will run when the AI has reached the end of its NavigationAgent3D path. We will call this ‘navigation_complete’.

func navigation_complete ():
    pass

Our states will also make use of a ‘random_offset’ function, which will return a Vector3 (denoted with the ->’ operator).

func random_offset() -> Vector3:
    var offset = Vector3(randf_range(-1, 1), 0, randf_range(-1, 1))
    return offset.normalized()

This code will simply return a random position on a circle. This will be helpful when making the wander and flee states for our AI, as it can be used to generate random positions to move around the AI. With these functions defined, our State class is ready for use in creating AIs. In the next section, we will set up the StateMachine script to control its states and handle transitions between them.

State Machine

In the previous section, we created a StateMachine.gd script but added no functionality to it. In this next part, we will be adding the required code to handle the different AI states that our Godot state machine may be given.

Creating the State Machine Node Structure

Before we can start work on the Godot state machine script, we need a child node to the AI root node that we can add the script to. To fix this, we will right-click on the AI node, and select Add Child Node.

Child Node

Here we will use the basic Node type as it will only be used to hold the state machine script.

basic Node type

So that we can easily identify it in our scripts, we will rename this node to ‘StateMachine’.

StateMachine node

Finally, we will attach the StateMachine.gd script to the Script property of this node.

Script property

From here, we will add each different state (as we make them) as a child node of this StateMachine node. This lets us use Godot’s node system to easily manage states for our AI. An example of this structure can be seen below, although we haven’t yet created the state scripts to add to these nodes.

node structure

Coding the StateMachine Script

In the previous lesson, we set up a script called ‘StateMachine.gd’ and gave it a class name of StateMachine. We can now begin building this script to handle our AI’s multiple states. To start with, we will create three variables. The first will be called ‘default_state’ with a type of State (which is the type for the script we defined in the last lesson). We can also add an export tag to this variable so that it can be set in the Inspector window. This will define the state that the Godot state machine starts on.

@export var default_state : State

The next variable will be called ‘current_state’ and also be of type State. This will keep track of the currently active state.

var current_state : State

Finally, we will create a dictionary to track every State that is a child node of this Godot state machine. This variable will have a default value of an empty dictionary (which is defined using ‘{}’), and can be called ‘states’.

var states : Dictionary = {}

Updating the Dictionary

A dictionary is similar to an array, but each value is accessed using a value (such as a name for our states) instead of an index. This value is known as a key and can be used using the ‘[]’ operators, like an array. To set the values in this dictionary, we will create a new _ready function and loop through each child node of this StateMachine node.

func _ready():
    for child in get_child():

As mentioned before, the states for our Godot state machine will be defined as child nodes of the StateMachine node. This means we can check if the child is a State type, and if so, add it to the dictionary using the key of the child node’s name.

func _ready():
    for child in get_child():
        if child is State:
            states[child.name] = child

We will also call the initialize function of the state here.

func _ready():
    for child in get_child():
        if child is State:
            ...
            child.initialize()

The Change State Function

The next function we will create for this script will be called ‘change_state’ and will have a parameter of state_name. This function will be used to switch the current state to a different state, based on a key in the dictionary.

func change_state (state_name):

We will start this function by finding the state in the states dictionary that corresponds to the state_name key.

func change_state (state_name):
    var new_state = states.get(state_name)

The next step is to check whether new_state has a value. If it does not, it means there isn’t a state in the states dictionary that uses the state_name key. Because of this, we won’t be able to change the state, so we will return out of the function to avoid any further code running. This will mean that if a state isn’t found, the AI will continue using the previous state.

func change_state (state_name):
    ...
    if new_state == null:
        return

Similarly, if new_state is the same state as the current_state, we will again return out of the function. This is because there is no point in restarting a state that is already active, and it could also lead to some unexpected behavior.

func change_state (state_name):
    ...
    if new_state == current_state:
        return

Before changing the current_state variable, we will first call the exit function on the current_state so that the old state can clean up and set its active property to false. However, it is important to check that the current_state variable is not null here, as there may be occasions when this function is called but the Godot state machine does not have an active state yet.

func change_state (state_name):
    ...
    if current_state != null:
        current_state.exit()

Once the old state has been told to exit, we can then update the current_state property to the new_state and call the enter function to tell the new state it is active.

func change_state (state_name):
    ...
    current_state = new_state
    new_state.enter()

Finally, to help us with debugging later on, we will add a print statement to output that the AI’s state has changed.

func change_state (state_name):
    ...
    print("Change State: " + state_name)

We can now call this function from the _ready function to use the default_state variable if the default_state variable has been set in the Inspector.

func _ready():
    ...
    if default_state != null:
        change_state(default_state.name)

Updating the Current State

The final section for this lesson is to call the update functions on the current state. The current state’s update function will be called from the StateMachine’s _process function, passing through the delta value.

func _process(delta):
    if current_state != null:
        current_state.update(delta)

This means that on every frame, if the current_state variable is set, we will call the update function. We can then do the same thing for the current state’s physics_update function using the _physics_process Godot function.

func _physics_process(delta):
    if current_state != null:
        current_state.physics_update(delta)

The final function to update on the current state is the navigation_complete function. However, to do this we will need to connect a signal to the StateMachine node. The first step here is to select the NavigationAgent3D node in the Scene window.

image 31

From here, go to the Node -> Signals window. This window contains a signal called target_reached which will be called whenever the AI agent reaches the end of a path.

image 32

Double-click this signal, and select the StateMachine node as the node to connect this signal to. Then, press Connect to generate the new function.

image 33

Within this function, we can call the current state’s navigation_complete function.

func _on_navigation_agent_3d_target_reached():
    if current_state != null:
        current_state.navigation_complete()

Godot State Machine Wrap-Up

Bravo on completing this tutorial on building a Godot state machine! You can now define different states for your game AI and manage transitions between them with ease. But there’s always more to learn! Consider practicing these new skills by creating additional, more complex states, or adding new AIs with shared states to your game. For further learning, explore more about AI state behaviors and nodes in Godot to add a variety of unique AI characters to your games.

Remember, practice makes perfect. Happy gaming!

Curious to dive deeper? Enroll in our comprehensive Make an AI State Machine in Godot 4  course.

Did you come across any errors in this tutorial? Please let us know by completing this form and we’ll look into it!

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.