In this tutorial we will learn how to use the AnimatedSprite node to animate sprites. In particular, we will animate our player by adding idle animations, walk animations and two types of attacks (sword and fireball).
Preparing player node
Let’s open the SimpleRPG project and pick up where we left off in the last tutorial. To get started, we need to import all the animation frames into the project. Download player sprites by pressing the button below.
In the FileSystem panel, delete player.png, import all new images and put them in the Entities/Player folder.
Select all the images you just added, go to the Import panel, disable Filter and click on Reimport.
Back to the Scene panel, right-click on the Sprite node and choose Change Type.
Select AnimatedSprite and press the Change button.
Once the node type is changed, a yellow triangle appears next to Sprite. This warning tells us that we need to create a SpriteFrames resource to show something on screen.
SpriteFrames is a resource (i.e. a data container) that contains all the textures that we will use as frames, as well as the list of all animations with their properties (speed, loop playback, etc.)
To create a SpriteFrames resource, go to the Inspector, click on [empty] next to the Frames property and select New SpriteFrames.
Click on the newly created SpriteFrames resource. The SpriteFrames editor will open.
The SpriteFrames editor
The SpriteFrames editor panel is divided into two columns. The left column lists all the animations, while on the right there is the list of the frames that make up the selected animation.
The left column has a toolbar with two buttons, one to add a new animation and one to delete the selected animation. At the bottom of the column, you can set the playback speed (in frames per second) and decide whether the animation should be looped or not.
In the toolbar of the right column there are 8 buttons:
- Load Resource: to add a resource to the animation frames. More simply, sprites can be dragged and dropped from the FileSystem panel.
- Copy: copy the selected frame.
- Paste: paste the previously copied frame.
- Insert empty before: inserts an empty frame before the selected one.
- Insert empty after: inserts an empty frame after the selected one.
- Move before: move the selected frame back one position.
- Move after: move the selected frame forward one position.
- Delete: deletes the selected frame.
To start, rename the default animation by clicking on it and call it down_idle. Drag the player_down_idle_1.png and player_down_idle_2.png files from the FileSystem panel to the right side of the SpriteFrames editor and then set Speed (FPS) to 1.
Now, in the Inspector, enable the Playing property. You will see the sprite animate with the newly created idle animation.
With the same procedure, create all the other animations. The table below contains all the information you need to create them.
|Animation name||Frames||Speed (FPS)||Loop|
In the Inspector, you can set the Animation property to the animation you want to preview. When you’ve finished creating the animations, set it to down_idle. It will be the default animation when the game starts.
Playing animations with script
Open the Player script. To choose which animation to play, we will write a new function named animates_player(). We will call this function at the end of _physics_process(), after the player’s movement has been calculated.
Let’s start by writing a simplified version of the animates_player() function to become familiar with how 2d sprite animation works.
Copy the following code into the script:
func animates_player(direction: Vector2): if direction != Vector2.ZERO: # Play walk animation $Sprite.play("down_walk") else: # Play idle animation $Sprite.play("down_idle")
The function animate_player() takes a Vector2 argument that represents the direction of the player movement.
The first line checks if the direction is different from zero (Vector2.ZERO is a constant equivalent to Vector2(0,0)). If true, the player is moving and the play() method of the Sprite node is called to play the down_walk animation. If false, it means that the player is still, and so the down_idle animation is performed. The $ operator, as we saw in Godot Tutorial – Part 3: First project, is used to get the reference to a child node.
Warning! Even if the node is still called Sprite, I remind you that now it’s of AnimatedSprite type. Do not confuse the name of the node with its type! play() is an AnimatedSprite method that is not available in Sprite-type nodes.
Now, at the bottom of the _physics_process() function, add this line to call animates_player():
# Animate player based on direction animates_player(direction)
If you try to run the game, you will see that when the player is still, the idle animation is performed, while when it is moving, the walk animation is shown as expected.
Choosing the right animation
Obviously, we don’t want to show the player always facing down, but we want to choose which animation to play based on the direction in which the player is facing.
To do this, we first need to declare a new variable at the beginning of the script:
var last_direction = Vector2(0, 1)
In this variable we will store the last direction in which the player moved before stopping. We set the initial value to Vector2 (0,1) to point the player down. This variable will help us decide which idle animation to show.
Now, we have to write the code that chooses whether to use the down, up, left or right animations, based on the direction vector. Since we will use this code on more than one occasion, we will write it in a new function, called get_animation_direction():
func get_animation_direction(direction: Vector2): var norm_direction = direction.normalized() if norm_direction.y >= 0.707: return "down" elif norm_direction.y <= -0.707: return "up" elif norm_direction.x <= -0.707: return "left" elif norm_direction.x >= 0.707: return "right"
How does this code work? First, it normalizes the direction vector to make sure it has length 1 (it can be less than 1 when using an analog stick). If we draw this normalized vector on a Cartesian plane, it will always be on a point of the unit circle (i.e. a circle with radius 1):
Let’s divide the plan into four quadrants as in the following figure:
Each area contains a quarter of the unit circle. Each of these arcs corresponds to one of the 4 possible directions of the animations.
How do we decide in which of these arcs is our vector? I won’t bore you with calculations, but it can be shown that all the points of the lower arc have the y coordinate greater than the square root of 2 over 2, or about 0.707, while those of the upper arc all have a value of y less than -0.707. Same thing for left and right, but in that case using the x coordinate.
Thus, the function simply checks the values of the x and y coordinates of the vector to decide in which of these arcs it’s located. Based on that, it returns the animation string prefix.
The return statement is used to end the execution of the function call and return the result value of the function. You can exit from a function at any point. If you don’t use the return keyword, the default return value is null.
Now, we can edit animates_player() to choose the correct animation direction:
func animates_player(direction: Vector2): if direction != Vector2.ZERO: # update last_direction last_direction = direction # Choose walk animation based on movement direction var animation = get_animation_direction(last_direction) + "_walk" # Play the walk animation $Sprite.play(animation) else: # Choose idle animation based on last movement direction and play it var animation = get_animation_direction(last_direction) + "_idle" $Sprite.play(animation)
As you can see, when the player is moving, we save in last_direction the current value of direction, so that it can be used later to choose the right idle animation.
We use get_animation_direction() to get the direction prefix, to which we append, with the + operator, the string indicating which animation we want (_walk or _idle). Then, as before, we use the play() method to play the animation.
If you try the game now, you will see that the player turns in the direction in which he walks. When the player stops, it remains oriented in the last direction of movement.
If you are using a joypad with analogue sticks, you will have noticed that when the player moves slowly, the walk animation is still performed at the speed of 10 FPS we initially set. This is visually unpleasant, because the speed of the animation is too high compared to the actual movement of the player on the map. We must therefore adjust the animation FPS based on player speed.
Doing it is quite simple, just add this new line to animates_player() before playing the walk animation:
$Sprite.frames.set_animation_speed(animation, 2 + 8 * direction.length())
This line accesses the SpriteFrame resource of the Sprite node and uses the set_animation_speed() method to set the FPS of the animation indicated by the first argument. The second argument is the number of FPS, which we calculate with a formula that returns a value from 2 to 10, depending on the length of the direction vector (which can range from 0 to 1).
Analog stick bounce problem
When using an analog stick, it can happen that when the player walk in one direction and suddenly release the stick, he end up facing the opposite direction. This can happen or not depending on the controller and the way the player handle the stick.
This is due to the fact that, whenever you release the stick, it returns back to its center position. But the spring that causes this movement is so strong, that the stick actually overshoots the center, going beyond for a short amount of time.
To solve this problem, we must ensure that the last_direction vector is not updated instantaneously with every change of direction, but that the change is gradual to filter out this “noise”. To do this, replace the line that updates last_direction with this:
# gradually update last_direction to counteract the bounce of the analog stick last_direction = 0.5 * last_direction + 0.5 * direction
Attack and fireball animations
The player, in addition to moving around the map, can attack enemies with his sword or casting fireballs. Since we have already created the animations for these actions, we want to play them when the player presses the attack keys.
First of all, we need to create input actions in the Input Map panel, which you can access by clicking on Project → Project Settings → Input Map. We will create two actions:
- attack: to attack with the sword; we will assign to this action the Space key and the Button 2 of the joypad (which corresponds to the X button of the XBox controller and to the Square button of the PlayStation controller)
- fireball: to cast a fireball; we will assign to this action the Control button and the Button 3 of the joypad (which corresponds to the Y button of the XBox controller and to the Triangle button of the PlayStation controller).
To add a new action, enter the name in the Action text field at the top of the panel, and press Add. We have already seen the procedure for mapping an input to an action in the Godot Tutorial – Part 5: Player movement, so refer to that if you don’t remember how to do it. The result will be the following:
Close the Project Settings window, go back to the script and add a new variable that we will use to find out if there is an attack animation in progress:
var attack_playing = false
We need this variable because animates_player() is called continuously by _physics_process(), so the attack animation would not have the time to be executed because it would be immediately replaced by a walk or idle animation. Because of this, we need to edit the _physics_process() code to call animates_player() only if attack_playing is false:
# Animate player based on direction if not attack_playing: animates_player(direction)
To handle attack input, we’ll use the _input method inherited from Node. This function is called whenever any input event occurs.
Add this code to the script:
func _input(event): if event.is_action_pressed("attack"): attack_playing = true var animation = get_animation_direction(last_direction) + "_attack" $Sprite.play(animation) elif event.is_action_pressed("fireball"): attack_playing = true var animation = get_animation_direction(last_direction) + "_fireball" $Sprite.play(animation)
If you followed Godot Tutorial – Part 5.1: Dragging the player with the mouse, then you already have this function in the script. You can add this code next to the existing one without any problems.
The function works like this: if one of the key/button assigned to the attack action is pressed, attack_playing is set to true, in order to bypass the animates_player() function. The correct animation is then chosen based on the last direction of the player and performed with the play() method. The same goes for the fireball action.
If you run the game now, you will see that the attack animations are performed correctly, but then the sprite remains on the last frame of the animation and the walk and idle animations are no longer played. This happens because attack_playing is not reset to false anywhere. We must then add some code that resets the variable when the animation ends.
Fortunately, AnimatedSprite provide the animation_finished() signal, which is sent each time the last frame of an animation is played. We will link this signal to a function in our script that resets attack_playing.
Open the Node panel and, if it is not already selected, click on Signals to see the list of signals.
Select animation_finished() and press Connect. A window will open to select the node to connect with the signal. Select Player and press Connect. The script will open with the cursor positioned on the function that was just created. Edit it like this:
func _on_Sprite_animation_finished(): attack_playing = false
As a last thing, we want the player speed to decrease when it’s simultaneously moving and attacking. To do this, simply add these lines to _physics_process(), just before calling move_and_collide():
if attack_playing: movement = 0.3 * movement
In this tutorial we learned how to use AnimatedSprite and SpriteFrames to animate sprites and how to use scripts to run animations. Furthermore, we have deepened our knowledge of the Godot input system.
You can try what we’ve done so far by clicking here:
In the next tutorial we will start developing the game GUI.