This is the fifth part of the tutorial creating platformer game on Godot Engine. In this tutorial I will explain more about character setup, such animation and flipping.

  1. Part 1 : Preparation
  2. Part 2 : Player Creation
  3. Part 3 : Player Creation 2
  4. Part 4 : Tilemap and Camera
  5. Part 5 : Player Animation
  6. Part 6 : Parallax Background and Level Bounds
  7. Part 7 : Character Controller and Enemy

Player Direction

Before we implement the animation, it will be good if the sprite direction following the character direction. If the character goes to the left direction, the character should be facing the left direction and otherwise. In the previous part, we have created facing_dir, now it’s the time to use it for fixing the sprite direction. Add one new variable onready var sprite = get_node("Sprite") and add one line code at the end of _fixed_process

1
2
3
4
5
6
7
#other code
func _fixed_process(delta):
#other code
if velocity.x != 0:
facing_dir = sign(velocity.x)
#flip the character
sprite.set_flip_h(facing_dir != 1)

Try it, and see what happens. When facing_dir is not equal than 1, the sprite will be flipping, and otherwise.

Flipping The Character

Animation

Animation in Godot can be controlled by Animation Player or Animation Tree Player node. What we will use is the Animation Player node, add this node to our player. Open player.tscn and add Animation Player as a child of Player (KinematicBody2D). Then rename it as “anim”.

Character Node Structure

Idle

Now, we are ready to animate the sprite. Select the anim node, Animation window will appear automatically at the bottom. Click new button for creating a new animation clip.

Animation Window

Then, give it a name as ‘Idle’. This clip will be our idle animation.

Animation Creation Window

Next, see picture below.

Animation Window

If you come from unity or you understand about animating, this will be not really hard. Idle animation only needs 1 frame. Select the sprite node and see at Inspector. Do you notice it? There is a key button on the right side each option. Find Frame option and make sure the value is 0, then click the key (This action called as keyframe). As I said, idle only need 1 frame, leave it like that. Don’t forget to activate the Auto-play option (The button right after Animation name).

Animation Idle

Move

Next, create a new animation clip called ‘Move’. ‘Move’ animation using 4 frames (0, 1, 2, 11), and the frame will change each 0.15 sec, set it into step (step = 0.15). So the length must be 0.75, this value is from total_frame * step. Don’t forget to activate the loop option (The button right after step). Now our job is keyframing, select sprite node again, and keyframing!! See image below how I’ve done it.

Animation Move

Jump

I’m sure I don’t need to explain this over again, the jump is like idle, only need 1 frame, and the index frame is 11. See image below how I’ve done it

Animation Jump

Sorry, but explaining how UI works from text is really hard. I think video will be good for this.

Implements the Animation into Code

Add 2 new variable var last_anim = "" and onready var anim = get_node("anim"). Then, add this code at the last line inside of _fixed_process

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#other code
func _fixed_process(delta):
#other code
sprite.set_flip_h(facing_dir != 1)
var new_anim = "Idle"
if last_frame_grounded:
if velocity.x != 0:
new_anim = "Move"
else
new_anim = "Jump"
#apply animation
if new_anim != last_anim:
anim.play(new_anim)
last_anim = new_anim
#other code

TLDR, if grounded and velocity.x not 0, then set move. If velocity.y not 0, then set jump, otherwise just idle.

last_anim store our last frame animation played. new_anim store what animation will be played in the current frame. If the character grounded and velocity is not 0, then new_anim set as Move. If not grounded, then set the new_anim as jump. Then, we need to check if our last frame animation is not equal as new animation, if the condition meets, then play the animation.

Animation

This is the full code of player.gd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
extends KinematicBody2D
# defines GRAVITY
# `export` makes your variable editable in the editor
# `var GRAVITY = 10` defines a variable named GRAVITY and assign it 10
export var GRAVITY = 10
# set the maximum falling speed per frame
export var MAX_FALLING_SPEED = 15
# MOVE_SPEED
export var MOVE_SPEED = 5
export var MOVE_SPEED_TIME_NEEDED = .15
var move_step = 0
export var DECELERATION_TIME_NEEDED = .15
var dec_step = 0
# jump power
export var MAX_JUMP_POWER = 5
export var MIN_JUMP_POWER = 2
export var MAX_AIR_JUMP_POWER = 3
export var MIN_AIR_JUMP_POWER = 1
export var MAX_AIR_JUMP_COUNT = 2
# store the player velocity
var velocity = Vector2()
# store status of jump input
var is_jump_pressed = false
# store status if last frame grounded
var last_frame_grounded = false
#store jump counter
var air_jump_count = 0
var facing_dir = 1
var last_anim = ""
onready var anim = get_node("anim")
onready var sprite = get_node("Sprite")
# Called when the node is "ready", that means called when the game started.
# Use this function for initialize
func _ready():
move_step = MOVE_SPEED / MOVE_SPEED_TIME_NEEDED
dec_step = MOVE_SPEED / DECELERATION_TIME_NEEDED
last_anim = anim.get_current_animation()
# makes `_fixed_process(delta)` running
set_fixed_process(true)
# Called during the fixed processing step of the main loop.
# Fixed processing means that the frame rate is synced to the physics,
# i.e. the delta variable should be constant.
# only active when set_fixed_process(true) is called
func _fixed_process(delta):
# make a Vector2 variable movement and add gravity into y axis
var movement = Vector2(velocity.x, velocity.y + GRAVITY * delta)
#input
var right_input = Input.is_action_pressed("right")
var left_input = Input.is_action_pressed("left")
var jump_input = Input.is_action_pressed("jump")
#Apply the horizontal movement
if right_input:
movement.x += move_step * delta
elif left_input:
movement.x -= move_step * delta
elif movement.x != 0:
#get the direction of movement
var _dir = sign(movement.x)
#calculate deceleration amount and direction
var _dec = _dir * -1 * dec_step * delta
# apply to movement
movement.x += _dec
# stop it when reached 0
if _dir == 1 && movement.x < 0:
movement.x = 0
elif _dir == -1 && movement.x > 0:
movement.x = 0
#if the movement.x more that max_speed, gap it
if abs(movement.x) > MOVE_SPEED:
movement.x = sign(movement.x) * MOVE_SPEED
#Apply jumping
if jump_input:
if !is_jump_pressed && last_frame_grounded:
movement.y = -MAX_JUMP_POWER
elif !is_jump_pressed && !last_frame_grounded && air_jump_count < MAX_AIR_JUMP_COUNT:
movement.y = -MAX_AIR_JUMP_POWER
air_jump_count += 1
is_jump_pressed = true
elif !jump_input && is_jump_pressed:
if air_jump_count != 0 && movement.y < -MIN_AIR_JUMP_POWER:
movement.y = -MIN_AIR_JUMP_POWER
elif movement.y < -MIN_JUMP_POWER:
movement.y = -MIN_JUMP_POWER
is_jump_pressed = false
# set the velocity = movement
velocity = movement
# set the maximum falling speed
if velocity.y > MAX_FALLING_SPEED:
velocity.y = MAX_FALLING_SPEED
# apply the movement by calling move(velocity) and store the remaining movement
var remaining_movement = move(velocity)
# collision handling
if is_colliding():
var normal = get_collision_normal()
remaining_movement = normal.slide(remaining_movement)
velocity = normal.slide(velocity)
move(remaining_movement)
# if normal is floor, then set as grounded
if normal == Vector2(0, -1):
last_frame_grounded = true
air_jump_count = 0
elif last_frame_grounded:
last_frame_grounded = false
if velocity.x != 0:
facing_dir = sign(velocity.x)
sprite.set_flip_h(facing_dir != 1)
var new_anim = "Idle"
if last_frame_grounded:
if velocity.x != 0:
new_anim = "Move"
else:
new_anim = "Jump"
#apply animation
if new_anim != last_anim:
anim.play(new_anim)
last_anim = new_anim
func get_center_pos():
return get_pos() + get_node("CollisionShape2D").get_pos()

Today that is enough, the next part will be Part 6 : Parallax Background and Level Bounds.

Download Project Here