This is the third part of the tutorial creating platformer game on Godot Engine. In this tutorial will refine our character movement and how to make the character jumping. So let’s get started!
Current method will move directly by MOVE_SPEED without any smoothing (low to high).
Acceleration
The idea is, if you have play super mario, the character movement is started from low until reaching the max speed. We will do it like that, so add a new variable called export var MOVE_SPEED_TIME_NEEDED = .15. This variable will determine how long character need to reach MOVE_SPEED, in this case is .15 second. Next one, add a new variable var move_step. Then modify our code into like this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#other code
func _ready():
move_step = MOVE_SPEED / MOVE_SPEED_TIME_NEEDED
set_fixed_process(true)
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")
#Apply the horizontal movement
if right_input:
movement.x += move_step * delta
elif left_input:
movement.x -= move_step * delta
else:
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
#other code
Because we use real time calculation (.15 second) then don’t forget to multiply by delta. Okay, maybe some of you will be confused with abs and sign.
abs is absolute, that means always return a positive value. For example, the movement.x = -2, then call abs(movement.x) will return 2, but the movement.x still -2 because we don’t assign it.
sign will return -1 or 1, depending on the value. If the value is less than 0, then return -1, and otherwise.
A simple explanation about that code which implements abs and sign is, if the absolute value of movement.x (always positive) more than MOVE_SPEED, then set the movement.x with the sign of movement.x (if movement.x less than 0, then return -1 and moving left, and otherwise) multiply by MOVE_SPEED.
Deceleration
When the horizontal input is pressed, the character movement is smooth now, but, when the button is up (unpressed), the character will stop immediately, this is weird right? So, we need to add the deceleration too. Add 2 variable export var DECELERATION_TIME_NEEDED = .15 and var dec_step. Then modify our code into like this
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
#other code
func _ready():
move_step = MOVE_SPEED / MOVE_SPEED_TIME_NEEDED
dec_step = MOVE_SPEED / DECELERATION_TIME_NEEDED
set_fixed_process(true)
func _fixed_process(delta):
#other code
#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
#other code
Explanation : So, when right or left input not pressed and movement.x is not zero, then we need to decelerate the movement. First, we need to get the direction of movement by doing sign(movement.x). So we know where the direction for deceleration (sign(movement.x) * -1). Then calculate the deceleration amount with sign(movement.x) * -1 * dec_step * delta. Then just apply to the movement. Also, don’t forget to stop it when reaching the 0 point.
Final code will looks like this
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
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
# store the player velocity
var velocity = Vector2()
# 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
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")
#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
# 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
The idea is, when user press jump input, then the character will jumping. The jump height depends on how long user presses the jump input. But we don’t do it at once, I’ll explain it step by step.
Utilize the Jump Input
In the part 2, we have add jump action at Input Map. So, let’s implement it! First of all, add one variable to define jump power.
export var JUMP_POWER = 8
Then add one line code for the input right below after left_input
1
2
3
4
5
func _fixed_process(delta):
#prev code
var left_input = Input.is_action_pressed("left")
var jump_input = Input.is_action_pressed("jump")
#next code
Now, implement the jump ability
1
2
3
4
5
6
7
8
func _fixed_process(delta):
#prev code
if jump_input:
movement.y = -JUMP_POWER
# set the velocity = movement
velocity = movement
#next code
If you try, whenever you pressed jump key, the character always go up, until you release jump key. Don’t worry, your code not error, because the code says like that.
movement.y = -JUMP_POWER, don’t forget the up direction is negative!!!
Fixing Jump Method
Weird?? Don’t worry, no error for now, because our code says like that. Our input is triggered whenever the user presses the jump button. So, our movement.y always same as JUMP_POWER. So we need to make the character only jump when the user pressed down the jump button. Add one more variable
var is_jump_pressed = false
Then modify our implementation
1
2
3
4
5
6
7
8
9
func _fixed_process(delta):
#prev code
if jump_input:
if !is_jump_pressed:
movement.y = -JUMP_POWER
is_jump_pressed = true
elif !jump_input && is_jump_pressed:
is_jump_pressed = false
#next code
Here the full code
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
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 JUMP_POWER = 5
# store the player velocity
var velocity = Vector2()
# store status of jump input
var is_jump_pressed = false
# 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
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:
movement.y = -JUMP_POWER
is_jump_pressed = true
elif !jump_input && is_jump_pressed:
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
We have a problem, the character can jump anywhere and everywhere! We need to make the jump can only be done while on the ground, that was the idea. But the implementation is not that simple. I will start from the dirty way. Add a new variable named var last_frame_grounded = false. Then modify our code into like this
But the problem with this implementation is when the character on the air and meet a wall, he can jumping. Because is_colliding() will return true whenever the character gets collision, in this case, wall/ceiling will be detected as collision too. Look at the image for the illustration
So, to fix this, we need to determine our floor direction. Basically, floor direction is up which means (0, -1). Modify our code into like this
This implementation only works with a game without slope ground, only flat ground which is the direction of the floor is (0, -1). You have been WARNED! I will make it more advance when Godot 3 release. So stay tuned on this website!
Controlling the Jump Height
The character jumps always at the same height. Most of the platformer game can control the jump height by how long user pressing the jump button. So let’s implement it by modify and add some code.
First, change the export var JUMP_POWER = 5 to export var MAX_JUMP_POWER = 5
Then add one more variable export var MIN_JUMP_POWER = 2
Now, we need to modify our implementation at jump method.
1
2
3
4
5
6
7
8
9
10
11
#prev code
#Apply jumping
if jump_input:
if !is_jump_pressed && last_frame_grounded:
movement.y = -MAX_JUMP_POWER
is_jump_pressed = true
elif !jump_input && is_jump_pressed:
if movement.y < -MIN_JUMP_POWER:
movement.y = -MIN_JUMP_POWER
is_jump_pressed = false
#next code
So the final code looks like this
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
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
# 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
# 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
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
is_jump_pressed = true
elif !jump_input && is_jump_pressed:
if 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
When user presses the jump button, movement.y directly set as MAX_JUMP_POWER. But, when user releases the jump button and the movement.y is still more than MAX_JUMP_POWER, then set movement.y into MIN_JUMP_POWER, don’t set it to 0, because the result will be weird. Try it yourself if you don’t trust me, hehe.
Multiple Jump
The idea is, the character can jump while in the air, but not infinite jump. Add 2 new variable, one for how many jumps can be done in the air, and the other is for counting how many jumps in the air has been done. If the counter has reached the maximum jump air can be done, the character can’t jump again. So let’s get started. Add 2 variable, export var MAX_AIR_JUMP_COUNT = 2 and var air_jump_count = 0. Then modify our code into like this
I want to make the air jump is not as same height as the ground jump. So I will add 2 new variable for defines the air jump power, one for the max jump power and the other is for min jump power. Let’s add 2 new variable export var MAX_AIR_JUMP_POWER = 3 and export var MIN_AIR_JUMP_POWER = 1.5. Then modify our code into like this
Okay lately, I just figured out after making some gif image, the character always jiggling! However this is not noticeable for now, but in the future, this will be worse! Making sure that was really jitter movement, or maybe just my gif image quality? So, I record my screen and play it in slow motion, and ahaaaa, I’ve noticed it, jitter movement happened.
To be honest, this glitch is hard to notice. However, I’m trying to reproduce this jitter movement and really hard to notice the jitter movement.
What we only need to do is activate the motion_fix_enabled in Project Settings - Physics 2D. This will handle jitter movement
This is the preview when I using old implementation and the new one
BEFORE
AFTER
Okay that was all for today, next tutorial will be Tilemap and Camera, see ya in the next tutorial.