This article describes an implementation of an animated state machine for a 2D game. A state machine controls the transition of an object from one state to another.

There are several restrictions on state transitions, such as when an object can only transition from one state to a limited number of states, or when an object can only transition to the next state after the current animation ends. For example, “idle” and “run” can transition immediately in both directions, but “idle” to “attack” can transition immediately, but “attack” to “run” cannot, and “attack” to “idle” can transition only after the “attack” animation ends. The “attack” to “idle” transition occurs only after the “attack” animation is over.

If all of these controls were coded in script, the code would tend to be rather long and complex. On the other hand, Godot’s “AnimationTree” node can be used to reduce the amount of script code and improve readability. In this article, we will show you how to implement a state machine using the “AnimationTree” node.

Environment
Godot version: 3.5.1
Computer OS: macOS 12.6

Basic Articles
You may also find the following articles useful.
Downloading Godot
Project Manager of Godot3



Preparation

Project Settings

After starting a new project, do some preliminary project settings.

The window size is set as follows.
project settings - window size

Added the following actions to the input map for the player character’s movement and attacks.
project settings - add actions to input map


Download Assets

I have downloaded the asset pack from the following link.

itch.io - mystic woods

Use the three sprite sheets “player.png”, “slime.png” and “objects.png” in the downloaded folder. I can’t help but be thankful for this wonderful asset.

The three sprite sheets (.png files) are added to the project by dragging and dropping them into Godot’s file system.

If the added image appears blurry (it will by default), select the imported file, select “Presets” > “2D Pixel” from the “Import” tab, and click the “Reimport” button. The image will now be displayed with the edges characteristic of dot images.


Create a Player scene

Create a “Player” scene for the player character. The scene tree will look like this:

  • Player (KinematicBody2D)
    • Sprite
    • BodyCollisionShape (CollisionShape2D)
    • HitBox (Area2D)
      • HitBoxCollisionShape (CollisionShape2D)
    • AnimationPlayer
    • AnimationTree

Player scene tree



Edit a node in the Player scene

Sprite node

  1. Apply the previously imported resource “player.png” to the “Texture” property in the inspector.
    Sprite node - edit properies
    Set the value of the “Animation” > “Hframes” property to 6 and the “Vframes” property to 5. This is because the applied sprite sheet consists of 6 columns horizontally and 5 rows vertically. Since this is a free version of the sprite sheet, the fourth row is missing, so don’t worry about it. Later, we will create an animation by changing the number of frames in the “AnimationPlayer” node.
    Sprite node - edit properies Sprite node - edit properies
  2. Change “Offset” > “Offset” property to (0, -17). The feet of the player character’s texture will now be at the coordinates (0, 0). This is done so that when the player character’s feet overlap with other objects on the game screen, the y-coordinates of each object are compared, and the object in front (with the larger y-coordinate) is displayed in the foreground.
    Sprite node - edit properies Sprite node - edit properies

BodyCollisionShape (CollisionShape2D) node

In the inspector, the “CircleShape2D” resource was applied to the “Shape” property, and the size and position were adjusted as follows.
BodyCollisionShape node - edit properies


HitBox (Area2D) node

This node is for the Hit Box for melee attacks. There is no need to edit this node. The implementation of melee attacks is explained in detail in the following article.

2D Hit Detection for Melee Attacks in Godot3


HitBoxCollisionShape (CollisionShape2D) node

The “RectangleShape2D” resource is applied to the “Shape” property. The size and position of the collision shape of the Hit Box will be changed in the animation of the “AnimationPlayer” node, so we will leave them as they are and set them when creating the animation. The “Disabled” property is also changed in the attack animation, but since the Hit Box needs to be disabled except during the attack, we leave it turned on.
HitBoxCollisionShape node - edit properies


AnimationPlayer node

Confirm that the “Root Node” is set to the “Player” node in the inspector.
AnimationPlayer node - edit properies


Create animations

In the animation panel, create the following six types of animations.

  • idle
    Just change the “Frame” property of the “Sprite” node. Looping is on.
    Animation - idle
  • run
    Just change the “Frame” property of the “Sprite” node. Looping is on.
    Animation - run
  • attack1
    This is the first animation for the attack. In addition to changing the “Frame” property of the “Sprite” node, the “Disabled” properties of the “BodyCollisionShape” and “HitBoxCollisionShape” are turned on and off. Looping is off. At this timing, the position and size of the “HitBoxCollisionShape” collision shape should be set to match the sprite’s sword trajectory.
    Animation - attack1
  • attack2
    This is the second animation the attack. This is played during the first animation to make it look as if the player character is attacking continuously. It is just “attack1” played backwards. Looping is turned off.
    Animation - attack2
  • hurt
    This is the animation when the player character takes damage. The sprite sheet does not have a texture when it is damaged, so it is represented by blinking colors. The “Modulate” property of the “Sprite” node is switched between white and red (translucent) multiple times. Two “Call Method Tracks” were added by clicking “+Add Track” in the upper left corner of the panel. One is to call the set_physics_process() method at the end of the animation. In the inspector, turn on “Args” > “0” > “Value”. This means that the argument of this method is passed true. The loop is turned off. The other method will be defined later in the script and then added (here is the state after the addition). The die_on_hurt_anim() method is called. This will be the method that will transition to the “die” animation when the life reaches 0, and will be explained again when scripting.
    Animation - hurt
  • die
    Animation when dead. Just change the “Frame” property of the “Sprite” node. Looping is off.
    Animation - die

The naming convention “attack1” and “attack2” is based on the following video.
YouTube - Name Files Logically


AnimationTree node

Using this node, we will create a state machine to control the animation prepared by “AnimationPlayer”. 1. In the inspector, select “New AnimationNodeStateMachine” for the “Tree Root” property. This is a setting to enable state management of animations.

  1. Make sure that the “AnimationPlayer” node is selected in the “Anim Player” property.
  2. Turn on the “Active” property. If it is not turned on, this node will not function. However, when adding or editing animations on the “AnimationPlayer” node, you may need to turn it off to stop the animation from playing.
    AnimationTree - Edit properties

Create a State Machine

From this point on, we will work on the animation panel. We will compose the animation tree that will carry the state machine.

  1. Open the Animation Tree panel.
    AnimationTree pannel
  2. Right-click in the panel with the Select/Move tool
    AnimationTree pannel or use the node creation tool and click in the panel,AnimationTree pannel then you can select an animation you want to add as a node from the animations created in “AnimationPlayer”.
    AnimationTree pannel
  3. At first, all the animations created by “AnimationPlayer” are added as nodes in the animation tree. In the screenshot below, the nodes are arranged in an orderly fashion, but in the actual creation of the animation tree, the nodes will be connected to each other, and the arrangement will be adjusted each time to make it easier to see.
    AnimationTree pannel
  4. Then, in the Select/Move tool, hold down the Shift key and press
    AnimationTree pannel or in the Node Connection tool, press
    AnimationTree pannel node to node by dragging.
    AnimationTree pannel
  5. If you look at the inspector with the connected arrow selected, you will see that it is a resource called “AnimationNodeStateMachineTransition”. By changing the “Switch Mode” property of this resource from the default “Immediate” to “AtEnd,” it is possible to transition to the next node’s animation after the node’s animation ends. At this time and in the panel, the arrows will be displayed with lines.AnimationTree pannel AnimationTree pannel In addition, if the “Auto Advance” property is turned on, the transition can be made automatically without being controlled by a script. In this case, the arrow will be green.
    AnimationTree pannel AnimationTree pannel For example, when the player character takes damage from an enemy, we want to immediately switch the animation from “idle” to “hurt”, but automatically transition to “idle” when the “hurt” animation ends. In this case, the following is used.
    AnimationTree pannel
  6. We want the first animation at the beginning of the game to play automatically, so select the “idle” animation node and enable autoplay.
    AnimationTree pannel AnimationTree pannel Similarly, the node for the “die” animation when the player character dies should be selected and set as the last node.
    AnimationTree pannel AnimationTree pannel
  7. The final configuration was as follows. This time, the attack is not allowed while moving.
    AnimationTree pannel


Attach a script to the Player node

Attach the script to the “Player” node and edit as follows:

###Player.gd###
extends KinematicBody2D

# Life
var life: int = 3
# Speed
var speed := 80.0
# Velocity
var velocity: Vector2

# Referencing a Sprite node
onready var sprite = $Sprite
# Parameters > Playback property of the AnimationTree node
# i.e. AnimationNodeStateMachinePlayback resource
onready var state_machine = $AnimationTree.get("parameters/playback")


func _physics_process(_delta):
	get_input()
	velocity = move_and_slide(velocity)

# Methods for player input
func get_input():
    # Get the current state
	var current_state = state_machine.get_current_node()

    # Input for attack
	if Input.is_action_just_pressed("attack"):
        # If current_state is not attack1, move to attack1
		if current_state ! = "attack1":
			state_machine.travel("attack1")
        # If current_state is attack1, move to attack2
		else:
			state_machine.travel("attack2")
        # If an attack is entered, no movement input is accepted and the operation ends.
		return

    # Input of movement
	velocity = Vector2()
	if Input.is_action_pressed("right"):
		velocity.x += 1
		sprite.flip_h = false
	if Input.is_action_pressed("left"):
		velocity.x -= 1
		sprite.flip_h = true
	if Input.is_action_pressed("down"):
		velocity.y += 1
	if Input.is_action_pressed("up"):
		velocity.y -= 1
	velocity = velocity.normalized() * speed
    # If velocity length is 0, transition the state to idle
	if velocity.length() == 0:
		state_machine.travel("idle")
    # If velocity length is greater than 0, transition the state to run
	if velocity.length() > 0:
		state_machine.travel("run")


# Methods called when a player is damaged
func hurt():
    # Life is reduced by 1
	life -= 1
    # Stop physics process and stop accepting input
	set_physics_process(false)
    # Transition state to hurt
	state_machine.travel("hurt")


# Method called at the end of the hurt animation
func die_on_hurt_anim():
    # If life is greater than 0, do nothing and exit
	if life > 0: return
    # Transition the state to die
	state_machine.travel("die")
    # Stop the physical process and stop accepting input
	set_physics_process(false)

As mentioned earlier, the method die_on_hurt_anim must be called within the “hurt” animation. At this point, you should go back to the Animation Panel and edit the `hurt’ animation in the “AnimationPlayer” node.

The “AnimationNodeStateMachinePlayback” resource class, which is the value of the “AnimationTree” node’s “Parameters” > “Playback” property, has a method called get_current_node that can be called from the current node. Calling this method will get the current animation node. This is useful when you want to make an if statement conditional on the current node.

The travel method, also a built-in function of this class, can be used to control the animation transition by passing the name of the animation node to be transitioned as a string argument. Of course, it is necessary to set up the transition on the animation tree (connected by arrows).

Connect the “body_entered” signal of the “HitBox (Area2D)” node at the end of the script and edit the generated method as follows:

###Player.gd###
func _on_HitBox_body_entered(body):
    # Call the hurt method of the body object that was hit by the sword
	body.hurt()


Operation check

This time, a “Tree” scene and a “Slime” scene were created separately for operation checks. Both scenes can inflict damage by colliding with the “HitBox” in the “Player” scene. The “Slime” scene also has a “HitBox” node, and when the “Player” hits the “Slime”, the player side takes damage and its life is reduced by one. The life property of the “Player” is set to 3, so that the player will die after hitting Slime three times.

We prepared a “World” scene and added instances of the “Player,” “Tree,” and “Slime” scenes. To check the operation, set the “World” scene as the main scene and run the project.
World scene tree

You can see how the state machine smoothly transitions the animation according to the situation.
World scene - 2D Workspace



Conclusion

In this article, we explained how to use a state machine to transition the animation (state) of the player character. Let’s look back at the key points of our work.

  • Identify the states to be used in the game first.
  • Create an animation for each state using “AnimationPlayer”
  • Create a state machine with “AnimationTree”
  • Control animation transitions with a script.

In fact, if you have a large number of animations, the nodes and the lines connecting them in the animation tree panel may become cluttered and difficult to understand visually. In such cases, it is a good idea to write them down on a piece of paper with a pen first to organize them in your mind.

Of course, you can also create a state machine with a script instead of relying on the “AnimationTree” node, so if you are not comfortable with editing the animation tree panel, you can try that.


References