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.
Godot version: 3.5.1
Computer OS: macOS 12.6
After starting a new project, do some preliminary project settings.
The window size is set as follows.
Added the following actions to the input map for the player character’s movement and attacks.
I have downloaded the asset pack from the following link.
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)
- BodyCollisionShape (CollisionShape2D)
- HitBox (Area2D)
- HitBoxCollisionShape (CollisionShape2D)
Edit a node in the Player scene
- Apply the previously imported resource “player.png” to the “Texture” property in the inspector.
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.
- 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.
BodyCollisionShape (CollisionShape2D) node
In the inspector, the “CircleShape2D” resource was applied to the “Shape” property, and the size and position were adjusted as follows.
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.
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.
Confirm that the “Root Node” is set to the “Player” node in the inspector.
In the animation panel, create the following six types of animations.
Just change the “Frame” property of the “Sprite” node. Looping is on.
Just change the “Frame” property of the “Sprite” node. Looping is on.
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.
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.
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 when dead. Just change the “Frame” property of the “Sprite” node. Looping is off.
The naming convention “attack1” and “attack2” is based on the following video.
YouTube - Name Files Logically
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.
- Make sure that the “AnimationPlayer” node is selected in the “Anim Player” property.
- 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.
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.
- Open the Animation Tree panel.
- Right-click in the panel with the Select/Move tool
or use the node creation tool and click in the panel, then you can select an animation you want to add as a node from the animations created in “AnimationPlayer”.
- 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.
- Then, in the Select/Move tool, hold down the Shift key and press
or in the Node Connection tool, press
node to node by dragging.
- 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.
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.
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.
- We want the first animation at the beginning of the game to play automatically, so select the “idle” animation node and enable autoplay.
Similarly, the node for the “die” animation when the player character dies should be selected and set as the last node.
- The final configuration was as follows. This time, the attack is not allowed while moving.
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.
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()
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.
You can see how the state machine smoothly transitions the animation according to the situation.
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.
- Godot Docs - Introduction to the animation features
- Godot Docs - Using AnimationTree
- Godot Docs - AnimationNodeStateMachinePlayback
- KidsCanCode - CONTROLLING ANIMATION STATES
- YouTube - Godot Recipes: Animation States
- YouTube - Make an Action RPG in Godot 3.2 (P9 | Attacking Animation + State Machines)
- YouTube - Name Files Logically
- itch.io - mystic woods