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.

Download “SimpleRPG Player Frames” player_frames.zip – 35 KB

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.

Change node type to AnimatedSprite

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 will open at the bottom of the window.

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.

The down_idle animation

With the same procedure, create all the other animations. The table below contains all the information you need to create them.

Animation nameFramesSpeed (FPS)Loop
down_attackplayer_down_idle_1.png
player_down_attack_1.png
player_down_attack_2.png
7Off
down_fireballplayer_down_idle_1.png
player_down_fireball_1.png
player_down_fireball_2.png
7Off
down_idleplayer_down_idle_1.png
player_down_idle_2.png
1On
down_walkplayer_down_idle_1.png
player_down_walk_1.png
player_down_idle_1.png
player_down_walk_2.png
10On
left_attackplayer_left_idle_1.png
player_left_attack_1.png
player_left_attack_2.png
7Off
left_fireballplayer_left_idle_1.png
player_left_fireball_1.png
player_left_fireball_2.png
7Off
left_idleplayer_left_idle_1.png
player_left_idle_2.png
1On
left_walkplayer_left_idle_1.png
player_left_walk_1.png
player_left_idle_1.png
player_left_walk_2.png
10On
right_attackplayer_right_idle_1.png
player_right_attack_1.png
player_right_attack_2.png
7Off
right_fireballplayer_right_idle_1.png
player_right_fireball_1.png
player_right_fireball_2.png
7Off
right_idleplayer_right_idle_1.png
player_right_idle_2.png
1On
right_walkplayer_right_idle_1.png
player_right_walk_1.png
player_right_idle_1.png
player_right_walk_2.png
10On
up_attackplayer_up_idle_1.png
player_up_attack_1.png
player_up_attack_2.png
7Off
up_fireballplayer_up_idle_1.png
player_up_fireball_1.png
player_up_fireball_2.png
7Off
up_idleplayer_up_idle_1.png
player_up_idle_2.png
1On
up_walkplayer_up_idle_1.png
player_up_walk_1.png
player_up_idle_1.png
player_up_walk_2.png
10On

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"
	return "down"

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.

Walking speed

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.

All signals of AnimatedSprite

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

Conclusions

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.

Did you enjoy this article? Then consider buying me a coffee! Your support will help me cover site expenses and pay writers to create new blog content.


46 Comments

Avatar

Ben · November 3, 2019 at 5:24 pm

Thanks for all the work on these so far! I’ve noticed one interesting behavior after this chapter, and I can’t seem to fix it. When moving down and right (1, 1) or up and left (-1, -1), the attack animation will not play. The other six directions (up, up/right, right, down, down/left, left) work just fine, and, even more oddly, the fireball works in all eight directions. I did some investigating in my own code and the only guess I have is that somehow the call to _input() just isn’t happening when moving in those directions. I added print(event) as the first line inside _input() and it does not print anything when pressing space and moving in those directions, but it does with every other combination. I also tested the web version you uploaded and the behavior is the same. Is this a bug in Godot? or is something weird happening with the direction math?

Thanks!

    Avatar

    Davide Pesce · November 3, 2019 at 8:34 pm

    Hi Ben, I tried to replicate your problem but without success, both within Godot and playing the game in the browser.
    The only thing that comes to mind is that your keyboard suffers from ghosting, and cannot detect when you press space while pressing certain arrow key combinations. Go to this page and try testing the key combinations that don’t work. If this is the problem, you can only solve it by mapping the attack action to another key. Otherwise, I will try to investigate further.

      Avatar

      Ben · November 5, 2019 at 10:01 pm

      Wow. I didn’t even realize that could be a thing. According to that site, my space bar does indeed fail to register when pressed with certain combinations of arrow keys (and there appear to be a few other conditions that will trigger this as well). For good measure, I tested it on a different keyboard and it worked just fine. Thanks for the keyboard tip. I’ve never had other issues with it so that possibility never even crossed my mind.

Avatar

Matthew · December 27, 2019 at 2:19 am

I for the the life of me cant get the attack animation to play ive stuck for days… I know the key is working because the mana bar is going down when i press fireball but the animation will not play

    Avatar

    Davide Pesce · January 1, 2020 at 4:43 pm

    To help you I need more details. If you want, write me in private on the Contact page.

    Avatar

    Anthony Uzmezler · February 3, 2020 at 12:40 am

    did you figure it out?

      Avatar

      Davide Pesce · February 3, 2020 at 10:28 am

      He never contacted me. Do you have the same issue? If so contact me privately, I have to see your project to understand the problem.

Avatar

James · January 10, 2020 at 11:41 am

I’ve been following your tutorials since day one, but i’m having problems with where to paste some of your codes. I will be glad if you can take a screenshot of all the codes and how you have arranged them in godot. Right now when I try to play the game it is just loading and it says that Godot is having problems.

    Avatar

    Davide Pesce · January 10, 2020 at 2:03 pm

    You can find the complete code of the project on GitHub.

      Avatar

      James · January 13, 2020 at 12:06 pm

      Thank You !
      I checked GitHub and I actually found the codes updates, however i tried to copy them and paste them in godot . After doing that another error came up.
      On func _input_event(event):
      Godot is saying error parsing expression, misplaced: func

      I’ll be glad if you can check on that. So far I’m enjoying these tutorials and discovering a lot too.
      Thank You.

        Avatar

        Davide Pesce · January 13, 2020 at 2:49 pm

        I double-checked the code on GitHub and it works, so probably in the copy-paste you have lost some tabs (in GDScript indentation is essential to identify a block of code).

        If you download the entire project from GitHub and import it into Godot does it work?

          Avatar

          James · January 14, 2020 at 1:19 pm

          Hey Davide Pesce !

          I tried downloading the whole project and running it. It actually works fine. I’ll try following the tutorial carefully again and see where i’ve missed out. It was really great experiencing the end result of the project. How I wish if you could’ve added buttons such as “Play”, “Pause”, “Quit” and the like.

          Thank you for the information now I know it is not the game or my PC Probably I just missed some important information.

          Avatar

          Davide Pesce · January 14, 2020 at 2:56 pm

          How I wish if you could’ve added buttons such as “Play”, “Pause”, “Quit” and the like.

          Maybe in one of the next tutorials I will add a pause screen where you can quit the game. If you want to know how to pause the game, I have already talked about it in this tutorial.

Avatar

James · January 13, 2020 at 4:13 pm

Thank you.
I’ll try to download the whole project and see what happens. Thanks for tips though. I’ll let you know as soon as u try it out.
Thanks

Avatar

Anastasia · February 26, 2020 at 6:31 pm

Hello! Decided to go trough this tutorial after all and I encountered a problem. it’s suposed to work fine, but i get an error saying “cant play() based on null instances or something like that…

extends KinematicBody2D

# Player movement speed
export var speed = 75
var last_direction = Vector2(0, 1)

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 “right”

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)

func _physics_process(delta):
# Get player input
var direction: Vector2
direction.x = Input.get_action_strength(“ui_right”) – Input.get_action_strength(“ui_left”)
direction.y = Input.get_action_strength(“ui_down”) – Input.get_action_strength(“ui_up”)

# If input is digital, normalize it for diagonal movement
if abs(direction.x) == 1 and abs(direction.y) == 1:
direction = direction.normalized()

# Apply movement
var movement = speed * direction * delta
move_and_collide(movement)
animates_player(direction)

Avatar

Anastasia · February 26, 2020 at 6:32 pm

Also here it doesn’t show the tabs for some reason?

Avatar

Anastasia · February 26, 2020 at 6:40 pm

OKOK nvm my sprite was named player whoops

Avatar

Alex · February 27, 2020 at 11:20 pm

Hello David!
I’m trying to implement the “last_direction = 0.5*last_direction + 0.5*direction”, but keep getting this error: “Invalid operands ‘Nil’ and ‘String’ in operator ‘+’.” Checked your github to see if I entered the code in the right place, and I did, so I have no idea why this doesn’t work for me.

Avatar

Oded · March 1, 2020 at 1:43 pm

I’ve been following and learning from your great tutorial. So first of all thanks a whole lot!

Both in your version (tutorial 8 conclusion) and in mine I see an animation problem when attacking repeatedly. Sometimes it gets stuck on the last frame of attack/fireball and only releases when the other attack action is pressed – like an edge case where _on_Sprite_animation_finished() isn’t called (movement still works but w/o animations).
Did you notice that? Do you know how it happens?

    Avatar

    Davide Pesce · March 1, 2020 at 8:13 pm

    Hi Oded, it has never happened to me, I will try to reproduce the problem and let you know.

    Avatar

    Davide Pesce · March 1, 2020 at 8:45 pm

    Sorry Oded, I didn’t realize we’re at tutorial 8! The problem will be solved in tutorial 11, when attack cooldown times are introduced. As you said, the problem is due to the attack animation that is not yet finished when the next attack starts.

      Avatar

      Oded · March 2, 2020 at 7:43 am

      Thanks for taking your time to reply, I’ll keep following and see if it’s solved for me as well later.

      I’d love to see more of your guides, your style and clarity are awesome

      Avatar

      Balázs · October 1, 2020 at 8:52 pm

      Hi!

      This problem appeared for me as well. I tried to put “if not attack_playing:” into the “func _input(event):” function, but it didn’t solve the problem either.

      I’ve put some print statements into the code and it seems if the spacebar is spammed fast enough, the “func _on_Sprite_animation_finished():” function will never be called again. The attack / fireball animation will be stuck on the last frame and it has something to do with input events.

      This problem is fixed in tutorial 11 indeed with the timers, but I just don’t know why my solution with the “if not attack_playing:” statement in the _input() function doesn’t work.

      func _input(event):
      if not attack_playing:
      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”):
      if mana >= 25:
      mana = mana – 25
      emit_signal(“player_stats_changed”, self)
      attack_playing = true
      var animation = get_animation_direction(last_direction) + “_fireball”
      $Sprite.play(animation)

      Avatar

      Balázs · October 11, 2020 at 3:15 pm

      Hello guys,

      I still couldn’t figure out why the animation can still stuck on the last frame if we introduce the “if not attack_playing:” condition just before we handle the attack animation (see my other comment on this).

      Do you guys have any clue about this?

      Also, I don’t understand either why the animations won’t stuck in other cases, like when we run idle and walk animations. We poll/play those more often than we can initiate user input. Is it because they are called from _physics_process()? Or does it have to do something with looping enabled?

        Avatar

        Davide Pesce · October 18, 2020 at 9:12 am

        Hi Balázs, unfortunately I don’t have an answer for you. My guess is that the problem lies in how signals are implemented internally in Godot. Probably when some functions are executed in the same frame, the order in which they are executed is not necessarily what we expect. The cooldown time prevents these side effects from being observed. But I repeat, it’s just my hypothesis.

Avatar

Anastasia · March 1, 2020 at 2:28 pm

Okkkkk I have another problem :/. Im a biiit stuck with this thing:
if i press the mana button or the attack button: nothing happens! Exeeept the attack_playing turns true and:
https://vimeo.com/394757529
It slows down permanently??

Cant find the problem: maybe you can?
btw yes i did conect the bars

extends KinematicBody2D

var health = 100
var health_max = 100
var health_regeneration = 1
var mana = 100
var mana_max = 100
var mana_regeneration = 2
var attack_playing = false

signal player_stats_changed

# Player movement speed
export var speed = 75
var last_direction = Vector2(0, 1)

func _ready():
emit_signal(“player_stats_changed”, self)

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”):
if mana >= 25:
mana = mana – 25
emit_signal(“player_stats_changed”, self)
attack_playing = true
var animation = get_animation_direction(last_direction) + “_fireball”
$Sprite.play(animation)

func _on_Sprite_animation_finished():
attack_playing = false

func _process(delta):
# Regenerates mana
var new_mana = min(mana + mana_regeneration * delta, mana_max)
if new_mana != mana:
mana = new_mana
emit_signal(“player_stats_changed”, self)

# Regenerates health
var new_health = min(health + health_regeneration * delta, health_max)
if new_health != health:
health = new_health
emit_signal(“player_stats_changed”, self)

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 “right”

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)

func _physics_process(delta):
# Get player input
var direction: Vector2
direction.x = Input.get_action_strength(“ui_right”) – Input.get_action_strength(“ui_left”)
direction.y = Input.get_action_strength(“ui_down”) – Input.get_action_strength(“ui_up”)

# If input is digital, normalize it for diagonal movement
if abs(direction.x) == 1 and abs(direction.y) == 1:
direction = direction.normalized()

# Apply movement
var movement = speed * direction * delta
if attack_playing:
movement = 0.3 * movement
move_and_collide(movement)
animates_player(direction)

    Avatar

    Davide Pesce · March 1, 2020 at 9:19 pm

    Hi Anastasia, an if statement is missing at the end of your _phyisics_process function. It must end in this way:

    if not attack_playing:
    	animates_player(direction)
    
      Avatar

      Anastasia · March 7, 2020 at 5:32 pm

      Hellooo! No idea how i missed that, thank you!
      Now it keeps on looping… is that normal or did i make another silly mistake?

        Avatar

        Anastasia · March 7, 2020 at 5:35 pm

        Indeed a silly mistake, I forgot to conect the animated sprite to my player!

Avatar

Cr0 · March 3, 2020 at 9:38 am

Hello David
I keep getting this error “Invalid operands ‘Nil’ and ‘String’ in operator ‘+’.” when I move up using the arrow key, but if I start moving down, and then left or right, then I can move freely, without errors. I have copied your code fro github, to see if it was my code that was wrong, but I still get the error. Is there a potential fix to this problem?

    Avatar

    Davide Pesce · March 3, 2020 at 8:10 pm

    Surely there is a fix! To find it, however, I need to see your project. If you can upload your project to Dropbox or Google Drive and send me the link (here or privately via the Contact page) I take a look at it and let you know!

    Avatar

    Davide Pesce · March 4, 2020 at 1:51 pm

    Ok, I found the problem: at the end of the get_animation_direction function the return “down” line is missing. This line is executed only in the (rare) case where its argument is zero. I corrected this problem in tutorial 9 but forgot to update the code here.
    So, the updated function code is this:

    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"
    	return "down"
    
      Avatar

      Cr0 · March 5, 2020 at 8:19 am

      Thanks for the solution, it worked. Keep up the good work, I really like these tutorials.

Avatar

Ed · May 14, 2020 at 9:50 am

Hi and thanks for the tutorial. Unfortunately, it doesn’t seem to be working for me, though I don’t get any errors and the game loads fine.

Here’s my script:

extends KinematicBody2D

export (int) var speed = 10

const MAX_SPEED = 75

var x = position.x
var y = position.y
var oldx = position.x
var oldy = position.y

var char_direction = Vector2()
var is_diagonal = false

var velocity = Vector2.ZERO
var last_direction = Vector2(0, 1)

var anim_player = null
var currentAnim = “”
var newAnim = “”

func _ready():
pass

func _physics_process(delta):
var input_vector = Vector2.ZERO
input_vector.x = Input.get_action_strength(“ui_right”) – Input.get_action_strength(“ui_left”)
input_vector.y = Input.get_action_strength(“ui_down”) – Input.get_action_strength(“ui_up”)
input_vector = input_vector.normalized()

if input_vector != Vector2.ZERO:
velocity = input_vector * MAX_SPEED
else:
velocity = Vector2.ZERO

move_and_collide(velocity * delta)

oldx = position.x
oldy = position.y
velocity = move_and_slide(velocity)

if char_direction.abs().x > 0 and char_direction.abs().y > 0:
is_diagonal = true
else:
is_diagonal = false

if is_diagonal == true:
global_position = global_position.round()

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 “right”
return “down”

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
$AnimatedSprite.play(animation)
else:
# Choose idle animation based on last movement direction and play it
var animation = get_animation_direction(last_direction) + “_idle”
$AnimatedSprite.play(animation)

pass

…would appreciate any help you could give. Thanks 🙂

    Avatar

    Davide Pesce · May 15, 2020 at 10:17 pm

    Hi Ed! A few questions:
    – What script is this?
    – Have you tried the original script?
    – What exactly is not working?

Avatar

Justin Mätschke · May 30, 2020 at 2:41 pm

Hi Davide Pesce,

first of all , this is such gr8 tut. i luv it.

But ive got a questin, my Player Animation is always the opposition after i walk to the Right or Left.
So if i walk to the Left i end up looking the right or if i walk to the right i look the left. If i walk up or down its working without a problem do u have any idea why this could be thing?

Greetings from Germany

    Avatar

    Davide Pesce · May 31, 2020 at 3:27 pm

    Hi Justin, glad you like the tutorials!
    About your problem, are you using an analog controller by any chance? Or does it happen with the keyboard?

Avatar

Max · September 13, 2020 at 4:27 pm

Hi, Davide!

First I want to say thank you for the tutorial series! It has been very helpful so far.

I have completed this portion of the tutorial, and have linked a screenshot below. As you will see, the GUI we built is showing perfectly in the editor. However, when I run the project it does not seem to render the whole scope of the camera node. I tried modifying that node (widening the zoom a bit), but it does not seem to resolve the issue.

The line which I circled in the screenshot shows where the screen is cut off in my project. The line to the right of that shows that my camera node is more wide than what is being shown.

https://imgur.com/a/y5c0Mti

Any help will be appreciated,

Thanks!!

    Avatar

    Davide Pesce · September 15, 2020 at 8:20 am

    Hi Max, I tried to replicate your problem but I couldn’t, if you upload your project to Dropbox or Drive and send me the link (here or using the contact page) I’ll have a look.

Avatar

Gareth · September 18, 2020 at 3:57 pm

Why don’t you use the Animateplayer and animatetree func?
example
var velocity = Vector2.ZERO

onready var animationPlayer = $AnimationPlayer
onready var animationTree = $AnimationTree
onready var animationState = animationTree.get(“parameters/playback”)

func _physics_process(delta):
var input_vector = Vector2.ZERO
input_vector.x = Input.get_action_strength(“ui_right”) – Input.get_action_strength(“ui_left”)
input_vector.y = Input.get_action_strength(“ui_down”) – Input.get_action_strength(“ui_up”)
input_vector = input_vector.normalized()

if input_vector != Vector2.ZERO:
animationTree.set(“parameters/Idle/blend_position”, input_vector)
animationTree.set(“parameters/Walk/blend_position”, input_vector)
animationState.travel(“Walk”)
velocity = velocity.move_toward(input_vector * MAX_SPEED, ACCELERATION * delta)
else:
animationState.travel(“Idle”)
velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)

velocity = move_and_slide(velocity)

    Avatar

    Davide Pesce · September 18, 2020 at 6:44 pm

    Hi Gareth! Honestly I don’t remember why I chose AnimatedSprite over AnimationPlayer + AnimationTree, probably when I wrote the tutorial I thought it was easier to explain… anyway thank you for pointing out this different approach, it will surely be useful to someone!

Avatar

.. · September 30, 2020 at 11:23 am

Hi I was wondering about how to make an enemy character have a continuous movement, like side to side. Because for my game, I would like it to just be there as it will be a kind of maze game.

Avatar

Anurag · October 11, 2020 at 3:10 pm

Thanks for the tutorial! Only one problem…i cannot animate my sword collision shape with this method so enemy doesn’t take any damage..but then if i use Animation Player , I can’t add individual sprites as in the Animated Sprites ..how can i deal with this?Should i make these images into a spritesheet?

    Avatar

    Davide Pesce · October 18, 2020 at 8:43 am

    From what I understand, you are doing something different than the tutorial, so I am unable to answer you without knowing the details of your project.

Avatar

Matt · March 13, 2021 at 12:36 pm

Hey, I’ve just started learning Godot and I’m finding these tutorials super helpful. I’ve followed up till this point and everything works for the most part. I have however noticed that If I spam the attack button (using both controller and keyboard) eventually the player will be stuck in 0.3 * movement speed and stuck on the last animation cell of the attack animation. This can be correct in-game by using the fireball_attack which looks to reset everything. I’ wondering if I’ve missed a step somewhere though?

This is my code:

extends KinematicBody2D

#Player Movement Speed
export var speed = 75

var last_direction = Vector2(0,1)
var attack_playing = false

func _ready():
pass # Replace with function body.

func _physics_process(delta):
#Get player input
var direction: Vector2
direction.x = Input.get_action_strength(“ui_right”) – Input.get_action_strength(“ui_left”)
direction.y = Input.get_action_strength(“ui_down”) – Input.get_action_strength(“ui_up”)

#If Input is digitial, normalise it for diagonal movement
if abs(direction.x) == 1 and abs(direction.y) == 1:
direction = direction.normalized()

#Apply Movement
var movement = speed * direction * delta
if attack_playing:
movement = 0.3 * movement
move_and_collide(movement)
#Animate play based on direction
if not attack_playing:
animates_player(direction)

func animates_player(direction: Vector2):
#update last_direction
if direction != Vector2.ZERO:
last_direction = 0.5 * last_direction + 0.5 * direction
#Choose walk animation based on movement direction
var animation = get_animation_direction(last_direction) + “_walk”
#Calculate FPS based on walk Speed
$Sprite.frames.set_animation_speed(animation, 2 + 8 * direction.length())
#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)

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 “right”
elif norm_direction.x <= -0.707:
return "left"
return "down"

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)

func _on_Sprite_animation_finished():
attack_playing = false

Leave a Reply

Your email address will not be published. Required fields are marked *

By continuing to browse this Website, you consent to the use of cookies. More information

This Website uses:

By continuing to browse this Website without changing the cookies settings of your browser or by clicking "Accept" below, you consent to the use of cookies.

Close