*You can find the links to the previous parts at the bottom of this tutorial.
In Donkey Kong, you increase your score points by jumping, dodging, or smashing things, and then adding those points to whatever is left on the bonus timer upon clearing the board.
For our final Pickup item, we will boost our player’s score by 1000 points. We’ll also up our score if they jump over a box or a bomb, but if they get damaged, we’ll decrease their score by 10 points.
WHAT YOU WILL LEARN IN THIS PART:
- Further practice with RayCast2D nodes.
STEP 1: INCREASING OUR SCORE
The score boost that the player gets when they jump over a box or a bomb will depend on how long they are jumping over it. We’ll be using another RayCast2D node to check if the player is jumping over the box or the bomb. The RayCast will continuously hit our nodes below, so each time it hits our box or bomb it will increase the score.
Let’s add a new RayCast2D node to our Player scene. Call it “ScoreRayCast”.
In the inspector panel, enable the ScoreRayCast’s collider to also collide with areas.
In our Player script, let’s create a new variable that will capture our Player’s score. We also need to create a new signal that will update our score value in the UI later on.
### Player.gd
#older code
#custom signals
signal update_lives(lives, max_lives)
signal update_attack_boost(attack_time_left)
signal update_score(score)
#score stats
var score = 0
We should also create a new variable that will capture if our player got hurt or not because we don’t want the raycast to increase the score if the player got hurt whilst jumping over the box or bomb.
### Player.gd
#older code
#health stats
var max_lives = 3
var lives = 3
var is_hurt = false
We will set this value to true if our player gets damaged in the take_damage() function. In our take_damage() function we will also decrease our score by 10.
### Player.gd
#older code
# takes damage
func take_damage():
#deduct and update lives
if lives > 0 and Global.can_hurt == true:
lives = lives - 1
update_lives.emit(lives, max_lives)
#play damage animation
$AnimatedSprite2D.play("damage")
#allows animation to play
set_physics_process(false)
is_hurt = true
#decrease score
if score >= 10:
score -= 10
update_score.emit(score)
Then, we’ll reset our is_hurt variable back to false after our jump animation has been completed.
### Player.gd
#older code
#reset our animation variables
func _on_animated_sprite_2d_animation_finished():
if attack_time_left <= 0:
Global.is_attacking = false
set_physics_process(true)
is_hurt = false
We will calculate our score boost in our physics_process() function. If the player’s is_hurt variable is false (meaning they didn’t get hurt by the box or bomb), and their ScoreRayCast is hitting the box or the bomb, we’ll increase their score by 1 each time it hits the collision whilst jumping over it.
### Player.gd
#older code
#movement and physics
func _physics_process(delta):
# older code
#score boost
if Input.is_action_pressed("ui_jump"):
# Get the colliders of our raycast
var target = $ScoreRayCast.get_collider()
# Check if target is valid and player is not damaged
if target != null:
if target.name == "Box" or target.name == "Bomb":
if is_hurt == false:
#increase score
score += 1
update_score.emit(score)
Finally, we’ll increase our score when our Player picks up our Score Boost pickup.
### Player.gd
#older code
#adds pickups to our player and updates our lives/attack boosts
func add_pickup(pickup):
#increases life count if we don't have 3 lives already
if pickup == Global.Pickups.HEALTH:
if lives < max_lives:
lives += 1
update_lives.emit(lives, max_lives)
#temporarily allows us to destroy boxes/bombs
if pickup == Global.Pickups.ATTACK:
Global.is_attacking = true
#increases our player's score
if pickup == Global.Pickups.SCORE:
score += 1000
update_score.emit(score)
If we print our score and we run our scene, we should see our score update when we jump over collisions, as well as when we pick up a score boost.
I also want us to increase our score when we are attacking collisions. Each time we hit the boxes or the bombs while our attack boost is enabled, I want our score to increase by 10. To change our score and emit our signal each time we want to increase or decrease our score is a bit tedious, so let’s create two custom functions to handle our score increasing and decreasing. Then, wherever we increase/decrease our score we can simply call the function and pass in the value that we want to add or subtract as a parameter.
### Player.gd
#older code
#increases our score
func increase_score(score_count):
score += score_count
update_score.emit(score)
#decreases our score
func decrease_score(score_count):
if score >= score_count:
score -= score_count
update_score.emit(score)
Now, let’s increase our score when we hit our box or bomb. We’ll also swap out our code to call our function instead when our player jumps over the collisions. Our score will increase indefinitely by 10 if we hit our Boxes, but because our bomb is still moving along our path when we move it, our score will increase for however long our AttackRayCast is hitting it. This is not an issue though, because the score should increase more if we hit a bomb (it’s not a bug, it’s a feature!).
### Player.gd
#older code
#movement and physics
func _physics_process(delta):
#older code
#countdown for attack boost
if Global.is_attacking == true:
attack_time_left = max(0, attack_time_left - 1)
update_attack_boost.emit(attack_time_left)
if Input.is_action_pressed("ui_attack"):
#gets the colliders of our raycast
var target = $AttackRayCast.get_collider()
#is target valid
if target != null:
#remove box
if target.name == "Box":
Global.disable_spawning()
target.queue_free()
increase_score(10)
#remove bomb
if target.name == "Bomb":
Global.is_bomb_moving = false
increase_score(10)
Global.can_hurt = false
else:
Global.can_hurt = true
#score boost
if Input.is_action_pressed("ui_jump"):
# Get the colliders of our raycast
var target = $ScoreRayCast.get_collider()
# Check if target is valid and player is not damaged
if target != null:
if target.name == "Box" or target.name == "Bomb":
if is_hurt == false:
increase_score(1)
Then, let’s update our code in our take_damage() function:
### Player.gd
#older code
# takes damage
func take_damage():
#deduct and update lives
if lives > 0 and Global.can_hurt == true:
lives = lives - 1
update_lives.emit(lives, max_lives)
#play damage animation
$AnimatedSprite2D.play("damage")
#allows animation to play
set_physics_process(false)
is_hurt = true
#decrease score
decrease_score(10)
And in our add_pickups() function:
### Player.gd
#older code
#adds pickups to our player and updates our lives/attack boosts
func add_pickup(pickup):
#increases life count if we don't have 3 lives already
if pickup == Global.Pickups.HEALTH:
if lives < max_lives:
lives += 1
update_lives.emit(lives, max_lives)
#temporarily allows us to destroy boxes/bombs
if pickup == Global.Pickups.ATTACK:
Global.is_attacking = true
#increases our player's score
if pickup == Global.Pickups.SCORE:
increase_score(1000)
Now if we run our scene our score should update if we hit our boxes and bombs.
STEP 2: SCORE UI
Just like with our Attack and Health pickups, we need to display the value of our score on our screen. Copy and paste your Attack UI element and rename it to “Score”.
Change your Sprite2D texture to “res://Assets/star/star/sprite_04.png”. Then, change its position to be (x: 25, y: 17).
Change your Score ColorRect node’s size to (x: 200. y: 40) and its position to (x: 260, y; 20).
We also want to change the size of our Label node since our score will take up a lot of space. Change its size to be (x: 150, y: 22). Also re-anchor it to be centered-right.
It should look like this:
Attach a new script to your Score node and save it underneath your GUI folder.
Just like we did in the two previous parts, we’ll create a function that will be connected to our update_score signal to display the score value in real time.
### Score.gd
extends ColorRect
#ref to our label node
@onready var label = $Label
# updates label text when signal is emitted
func update_score(score):
label.text = str(score)
### Player.gd
#older code
func _ready():
current_direction = -1
#set our attack timer to be the value of our wait_time
attack_time_left = $AttackBoostTimer.wait_time
#updates our UI labels when signals are emitted
update_lives.connect($UI/Health.update_lives)
update_attack_boost.connect($UI/Attack.update_attack_boost)
update_score.connect($UI/Score.update_score)
#show our correct lives value on load
$UI/Health/Label.text = str(lives)
Your code should look like this.
Now if you run your scene your score should update if you run through score boosts, hit collisions, or jump over collisions!
Congratulations on setting up the base for your Score Boost System. In the next part, we’ll add our Level Progression mechanics which will give our player a rating and feedback once they complete the level, and it will allow them to move on to the next level or retry the current level.
Now would be a good time to save your project and make a backup of your project so that you can revert to this part if any game-breaking errors occur. Go back and revise what you’ve learned before you continue with the series, and once you’re ready, I’ll see you in the next part!
Next Part to the Tutorial Series
The tutorial series has 24 chapters. I’ll be posting all of the chapters in sectional daily parts over the next couple of weeks. You can find the updated list of the tutorial links for all 24 parts of this series on my GitBook. If you don’t see a link added to a part yet, then that means that it hasn’t been posted yet. Also, if there are any future updates to the series, my GitBook would be the place where you can keep up-to-date with everything!
Support the Series & Gain Early Access!
If you like this series and would like to support me, you could donate any amount to my KoFi shop or you could purchase the offline PDF that has the entire series in one on-the-go booklet!
The booklet gives you lifelong access to the full, offline version of the “Learn Godot 4 by Making a 2D Platformer” PDF booklet. This is a 451-page document that contains all the tutorials of this series in a sequenced format, plus you get dedicated help from me if you ever get stuck or need advice. This means you don’t have to wait for me to release the next part of the tutorial series on Dev.to or Medium. You can just move on and continue the tutorial at your own pace — anytime and anywhere!
This book will be updated continuously to fix newly discovered bugs, or to fix compatibility issues with newer versions of Godot 4.