Skip to main content

This guide will update a score (+2) if the player destroys an obstacle. If the player gets more than five points they have won / completed the level. In this example when the player completes the level the game will pause and a pop up will appear with the score and a button to return to the main menu. In other games this could be a button to take you to the next level.

Tracking the player with the camera

Open the Player scene.

At the moment the camera is fixed.

We will add a Camera node so that the camera will follow the player.

Select the Player node.

Add a Camera2D node.

In the inspector check the Current property. This will enable the camera to lock on to the player.

Save the Player scene and switch to the Level scene.

Press F6 to test the scene, the camera should not be centered on the player and follow them around.

Adding the score

Create a new scene called HeadsUpDisplay set it with a User Interface type.

Add a CanvasLayer node to the scene.

Add a Label to the object

Select layout and choose Top Wide

In the inspector, add text to the label put Temp Score in the text box, align center, and scale the label.

Note the default font doesn't scale well. Consider downloading additional fonts. Google has some open source fonts here.

Add a button node and set the Text to Continue in the inspector.

Set the layout to Center

Now the button is almost centered.

To fully center, change the grow direction to both for horizontal and vertical.

The button is now centered.

This scene is not attached to any other scenes yet.

If we preview it we get the following.

We want to turn off the button as it should only be shown when the level is completed.

Under CanvasItem in the inspector for the button uncheck visibility.

Now the button is not visible.

Save the HeadsUpDisplay scene.

Open the Player scene

Add the HeadsUpDisplay scene to the Player scene.

Test the game, you should see the HUD as an overlay.

Now we need to add code to our scripts to handle scoring.

Open the HeadsupDisplay scene.

Attach a script to the label used for the score.

Save this script as ScoreManager.gd

Add the following code to the script. Point 1 creates the score variable.

We will update this variable everytime that we destroy an obstacle.

At point 2 we display the initial score of 0. Note that text is a variable that is part of the label node class.

extends Label

# 1 Set the score to 0
var score = 0

# Declare member variables here. Examples:
# var a = 2
# var b = "text"


# Called when the node enters the scene tree for the first time.
func _ready():
# 2 Display the initial score
text = "Score %s" % score
pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass

Run the level scene you should see Score 0

We will also add a function here to be called when the signal / event occurs for hitting the top of an obstacle.

Point 3 creates the function. Point 3 updates the score variable and point 5 updates the label on the scene.

extends Label

# 1 Set the score to 0
var score = 0

# Declare member variables here. Examples:
# var a = 2
# var b = "text"


# Called when the node enters the scene tree for the first time.
func _ready():
# 2 Display the initial score
text = "Score %s" % score
pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass

# 3 Create the function to score points, the parameter is the number of points to add
func score_points(points):
# 4 updates the score variable
score = score + points
# 5 Update the text on the screen
text = "Score %s" % score

Now we need to link up the Player as it is the player that will award the points.

Open the Player script.

Add in the score_points function as shown in points 28 and 29. This runs the function attached to the obstacle script.

# 1 Extend the kinematic body node to allow for 2d physics
extends KinematicBody2D

# 2 Create a gravity variable of type int and set it to 800
# In capitals as it is a constant and won't change
var GRAVITY : int = 80

# 3 Create a variable to store the player velocity
# Set this to the type of a 2D vector e.g. x and y
var velocity: Vector2 = Vector2()
# 7 Create a variable to set the speed of the player
# 20 Update the speed that the player moves
var speed : int = 50
# 14 Jump force
var jump : int = 50

# 4 Create a sprite variable and link it to the sprite node. When the node is
# initialised.
onready var sprite = $Sprite
# 21 Add a variable for the audio stream player
onready var audioPlayer = $AudioStreamPlayer2D

# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.

# 5 Runs the physics updates to the game
func _physics_process(delta):
# 6 Update the y axis velocity by the time elapsed
velocity.y += delta * GRAVITY

# 8 Check is the left input is pressed
# note player_left is the action in the input map
if Input.is_action_pressed("player_left"):
# 9 Update the velocity by the speed of the player
# 18 set x velocity to -speed to have fixed speed
velocity.x = -speed
# 10 do the same for the moving right
elif Input.is_action_pressed("player_right"):
# 19 set x velocity to speed to have fixed speed
velocity.x = speed
# 11 If left or right is not pressed set the x velocity to 0
else:
velocity.x = 0

# 12 Flip the sprite depending on the direction of movement
if velocity.x < 0:
sprite.flip_h = false
elif velocity.x > 0:
sprite.flip_h = true

# 15 Detect the jump button being pressed
# 17 Add the is_on_floor() check to the jump condition
if Input.is_action_pressed("player_jump") && is_on_floor():
# 16 Multiply by -1 to turn the velocity into a negative value
velocity.y = jump * -1
# 22 Play the audio for the jump
audioPlayer.play()

# 13 Move the object, the second parameter sets the vertical axis
move_and_slide(velocity, Vector2.UP)

func _on_DeathArea_body_entered(body):
# 23 Test object overlap
print("Player death")
# 24 Reload the scene
#get_tree().reload_current_scene()
# 25 Respawn player at location
position = $"../SpawnPoint".position

# 26 Respawn function
func respawn():
# 27 respawns the player
position = $"../SpawnPoint".position

# 28 Score points function that calls the score points function on the UI
func score_points(points):
# 29 Get the Label on the node that has the score points script attached
get_node("Control/CanvasLayer/Label").score_points(points)

Finally we need to link up the collision detection on the obstacle so that it will update the score when it occurs.

Open the obstacle script.

Go to the TopArea function. This is what handles killing / destroying an obstacle. When this happens we want to award points.

Add in the code at point 5 so that the player node runs the score_points script.

extends RigidBody2D
const SCORE_MANAGER := preload("res://ScoreManager.gd")

# Declare member variables here. Examples:
# var a = 2
# var b = "text"

# 5 positions array to store Vector2 positions for the obstacle to move to.
# Set size to 1 for static obstacle, 2 for two positions, 3 for three positions etc.
# Position 0 will be the start position and set from the initial location in the scene
var locations = []
# 6 Target position index
var target_pos_index = 0
# 7 Variable to store the target position of the obstacle
var target_pos = global_position

# 13 Set the speed for the obstacle to move at
var speed = 25;

# Called when the node enters the scene tree for the first time.
func _ready():
# 8 Add the starting position to the array
locations.append(global_position)
# 9 Set the target position to the starting position of the array
target_pos = locations[0]

# 10 Go through each node added as children of Waypoints and get the position
for n in get_node("Waypoints").get_children():
locations.append(n.global_position)


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
# 11 Call the local function that handles movement
_move_around_locations(delta)

pass

# 12 Function to move the obstacle around waypoints
func _move_around_locations(delta):
# 15 Check if the obstacle is at the target position
if position == target_pos:
# 16 Get next position
target_pos_index = target_pos_index + 1
# 17 If next position falls off end of the array, reset to start of the array
if target_pos_index + 1 > locations.size():
target_pos_index = 0
# 18 Update target position to new location
target_pos = locations[target_pos_index]

# 14 Move the obstacle towards the target position at set speed
position = position.move_toward(target_pos, delta * speed)

func _on_KillArea_body_entered(body):
# 1 Print hit message
print("Obstacle hit player")
# 2 Call the respawn function of the player (body) using dot notation
body.respawn()

func _on_TopArea_body_entered(body):
# 5 Run the score_points function and alloate 2 points
body.score_points(2)

# 3 Print message to test that a collision / overlap has been detected.
print("Player hit top of obstacle")
# 4 Queue free destroys the node / object
queue_free()
pass # Replace with function body.

Save and run the program.

Points should now be awarded.

Winning and displaying the win menu

Finally we want to stop the game and display the win menu, or in this case the Continue button.

We need to set our win condition. This will be five or more points. e.g. score >= 5

Open the HeadsUpDisplay.gd script

In the score_points function we will add a condition to check if we have enough points.

Point 6 in the script shows this.

To display the button we need to access it and make it visible. As the script is on the Label node we need to get the parent node and then find the node that is a child of this node which is the button.

Point 7 gets the parent node of the label and then finds the node named Button and then sets it to visible.

There is a Godot guide here for about pausing games, find it here.

At point 8 we call the get_tree() to get global project features and access the paused variable and set this to true.

extends Label

# 1 Set the score to 0
var score = 0

# Declare member variables here. Examples:
# var a = 2
# var b = "text"


# Called when the node enters the scene tree for the first time.
func _ready():
# 2 Display the initial score
text = "Score %s" % score
pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass

# 3 Create the function to score points, the parameter is the number of points to add
func score_points(points):
# 4 updates the score variable
score = score + points

# 6 Check if there are enough points
if score >= 5:
# 7 display the button, we need to get the parent node and then the button as this is at the same level in the scene
get_parent().get_node("Button").visible = true
# 8 Pause the game
get_tree().paused = true

# 5 Update the text on the screen
text = "Score %s" % score

Open the HeadsUpDisplay scene.

Add a pressed() signal to the Button. Attach this to the HeadsUpDisplay.gd script.

As shown in point 9 get the tree and run the change scene method and load the main scene.

extends Label

# 1 Set the score to 0
var score = 0

# Declare member variables here. Examples:
# var a = 2
# var b = "text"


# Called when the node enters the scene tree for the first time.
func _ready():
# 2 Display the initial score
text = "Score %s" % score
pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass

# 3 Create the function to score points, the parameter is the number of points to add
func score_points(points):
# 4 updates the score variable
score = score + points

# 6 Check if there are enough points
if score >= 5:
# 7 display the button, we need to get the parent node and then the button as this is at the same level in the scene
get_parent().get_node("Button").visible = true
# 8 Pause the game
get_tree().paused = true

# 5 Update the text on the screen
text = "Score %s" % score


func _on_Button_pressed():
# 9 Load the main menu scene
get_tree().change_scene("res://MainMenu.tscn")
pass # Replace with function body.

When we run the program the continue button won't work.

This is because the pause mode for the button is disables when the game is paused.

We can fix this.

Go to the inspector for the Button node.

Scroll down to Pause Mode and select Process.

Run the program again, the button will now work.

Finally we need to unpause the game when we click on the button, this is shown in point 10.

extends Label

# 1 Set the score to 0
var score = 0

# Declare member variables here. Examples:
# var a = 2
# var b = "text"


# Called when the node enters the scene tree for the first time.
func _ready():
# 2 Display the initial score
text = "Score %s" % score
pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass

# 3 Create the function to score points, the parameter is the number of points to add
func score_points(points):
# 4 updates the score variable
score = score + points

# 6 Check if there are enough points
if score >= 5:
# 7 display the button, we need to get the parent node and then the button as this is at the same level in the scene
get_parent().get_node("Button").visible = true
# 8 Pause the game
get_tree().paused = true

# 5 Update the text on the screen
text = "Score %s" % score


func _on_Button_pressed():
#10 Unpause the game
get_tree().paused = false
# 9 Load the main menu scene
get_tree().change_scene("res://MainMenu.tscn")
pass # Replace with function body.