Building a 2D Platformer Game with Godot. Part 2
In the previous article, we learned how to install Godot, how to create a Godot project, and how to create a scene and nodes.
In this article, we are going to learn how to add animations to the player node.
Adding Animations
We go to the player scene.
We click on the "script" symbol.
This window will show up.
Now, let's take a look at the code of this script:
extends CharacterBody2D
const SPEED = 300.0
const JUMP_VELOCITY = -400.0
# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
func _physics_process(delta):
# Add the gravity.
if not is_on_floor():
velocity.y += gravity * delta
# Handle Jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var direction = Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
move_and_slide()
This code extends the CharacterBody2D class, which has collision shapes and handles gravity automatically.
SPEED
and JUMP_VELOCITY
are constants for the character's speed and jump velocity.
The physicsprocess()
function is called every physics frame. gravity
gets the project gravity setting to match the RigidBody nodes. velocity.y += gravity * delta
adds gravity to the velocity of each frame. if
Input.is
_action_just_pressed("ui_accept")
checks if the jump key is pressed. velocity.y = JUMP_VELOCITY
sets the jump velocity when jumping. direction = Input.get_axis
gets the left/right input and stores the direction (-1 for left, 1 for right).
velocity.x = direction * SPEED
moves the character left or right based on the direction.
move_and_slide()
applies the velocity and handles collisions.
Now, the first animation that we are going to add is the Idle
animation.
We add the following line of code: $AnimatedSprite2D.animation = "Idle"
to the _physics_process()
function.
Let's change the value of the constant SPEED to 100 instead of 300. If we keep the SPEED's value to 300, it will move too fast and the player will fall.
func _physics_process(delta):
# Add the gravity.
if not is_on_floor():
velocity.y += gravity * delta
$AnimatedSprite2D.animation = "Jump"
# Handle Jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var direction = Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
$AnimatedSprite2D.animation = "Run"
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
$AnimatedSprite2D.animation = "Idle"
move_and_slide()
The line of code we just added plays the Idle animation when the player is not moving.
We play the scene, and we can see the player in the Idle animation.
Let's add the "Jump" and "Run" animations.
We need to change a little bit the script's code.
func _physics_process(delta):
var direction = Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
$AnimatedSprite2D.animation = "Run"
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
_animated_sprite.play("Idle")
# Add the gravity.
if not is_on_floor():
velocity.y += gravity * delta
$AnimatedSprite2D.animation = "Jump"
# Handle Jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
move_and_slide()
Now, if we play the scene again, all the animations should be playing, if we move the player to right and left, the "Run" animations should play. If we press the space bar key on our keyboard, the "Jump" animation should play, if we don't move the player, the "Idle" animation should play.
So far, this is the complete script we have.
extends CharacterBody2D
const SPEED = 100.0
const JUMP_VELOCITY = -400.0
# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
@onready var _animated_sprite = $AnimatedSprite2D
func _physics_process(delta):
var direction = Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
$AnimatedSprite2D.animation = "Run"
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
_animated_sprite.play("Idle")
# Add the gravity.
if not is_on_floor():
velocity.y += gravity * delta
$AnimatedSprite2D.animation = "Jump"
# Handle Jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
move_and_slide()
On top of the physics_process()
function we declare a new variable, _animated_sprite
, and we assign it $AnimatedSprite2D
as value.
Now, instead of writing $AnimatedSprite2D
to load a sprite. We can just write animate_sprite
and use their methods.
@onready var _animated_sprite = $AnimatedSprite2D
func _physics_process(delta):
var direction = Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
apply_friction(delta)
_animated_sprite.play("Run")
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
_animated_sprite.play("Idle")
# Add the gravity.
if not is_on_floor():
velocity.y += gravity * delta
_animated_sprite.play("Jump")
# Handle Jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
move_and_slide()
Let's create a new function, to add friction to the game.
func apply_friction(delta):
velocity.x = move_toward(velocity.x, 0, FRICTION * delta)
We have to create a new constant, FRICTION on top of the physics_process()
function.
extends CharacterBody2D
const SPEED = 100.0
const JUMP_VELOCITY = -400.0
const FRICION = 400
# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
@onready var _animated_sprite = $AnimatedSprite2D
func _physics_process(delta):
var direction = Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
apply_friction(delta)
$AnimatedSprite2D.animation = "Run"
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
_animated_sprite.play("Idle")
# Add the gravity.
if not is_on_floor():
velocity.y += gravity * delta
$AnimatedSprite2D.animation = "Jump"
# Handle Jump.
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
move_and_slide()
func apply_friction(delta):
velocity.x = move_toward(velocity.x, 0, FRICION * delta)
An issue that we are having with the animations so far, is that the player doesn't flip to the left side when it moves to the left.
Let's change that, by using set_flip_h()
function, to flip the sprite horizontally.
func _physics_process(delta):
var direction = Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
apply_friction(delta)
_animated_sprite.play("Run")
# Check for flip horizontal based on direction
if direction > 0 :
_animated_sprite.set_flip_h(false)
else:
_animated_sprite.set_flip_h(true)
....
Now, the script flips the sprite horizontally based on the direction. If moving to the right, the flip is false, but if the player is moving to the left, the flip is true.
Camera 2D
One of the inconveniences that I have found building this game, is that every time I play the "World" scene, the game's camera seems too far away from the player.
It is difficult to appreciate the character's details and the assets of the game.
To solve this issue we are going to create another child node for the Player node. And select "Camera2D".
In the scene panel(blue rectangle in the image below), we select Camera2D. Then we go to change the Camera2D settings(red rectangle in the image below).
Now, we have to change Zoom's parameters.
If we change the X and Y parameters to 3.
This is how the game looks when we play it.
Adjust the zoom of the camera to your preferences.
Conclusion
In this article, we learn how to add the idle, run and jump animation to the player node. And how to adjust the zoom of the camera.
I want to shout out to Ansimuz for allowing us to use its assets for free. Coding Quest, HeartBeast and BornCG for creating very good content about Godot, their content motivated me to create this tutorial. Their videos were a great help for me to learn Godot and Game development.
Thank you for taking the time to read this article.
If you have any recommendations about other packages, architectures, how to improve my code, my English, or anything; please leave a comment or contact me through Twitter, or LinkedIn.
Resources
Godot 2D Sprite Animation documentation
Video - Pixel Platformer Tutorial / Code Along P3 (Animation and Character Skins) - Godot Engine
Video - Build Your Own Platformer with this FREE Godot 4.0 Crash Course! Part 2
Subscribe to my newsletter
Read articles from Carlos Armando Marcano Vargas directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Carlos Armando Marcano Vargas
Carlos Armando Marcano Vargas
I am a backend developer from Venezuela. I enjoy writing tutorials for open source projects I using and find interesting. Mostly I write tutorials about Python, Go, and Rust.