Creating the end screen

When there are no more instructions on a stack for the HitSpawner to process, the player is trapped in the gameplay loop with nothing to do.

In this lesson, we’ll add a panel with a “back” button to display after finishing the track. Clicking the button will take the player back to the track selection screen.

To do so, we’ll reload the current scene, resetting the game’s state. It works well for this project.

Create a new User Interface scene and rename the root node UITrackFinished. You can save it in the UI/TrackFinished/ directory.

Also, set the node’s Mouse -> Filter to Ignore. Otherwise, it’ll consume mouse input.

Add the following four nodes with that nested structure:

We want the GameOverInfo and ButtonBack as children of the Background panel to align them relative to their parent.

Setting up the scene’s layout

Right now, our nodes are in the top-left of the viewport, but we want to align everything properly to have the panel centered on the screen.

Select the Background node and apply the Layout -> Center option to it.

Select the button and apply the Layout -> Bottom Right option to anchor it in the bottom-right of the Background.

In the Inspector, turn on its Expand property to make it scalable and scale it down a bit, as it is too large by default. You can then move it to place it inside the panel.

For the label, use the Layout -> Top Wide option to anchor it at the top of the panel. You should then resize the node horizontally to limit the textbox’s width and move it down to have the text inside the panel.

To have the text wrap and preserve the node’s width, turn on its Autowrap property.

This way, you can write multiple lines of text without losing your layout.

Fading animation

We add a fade animation using the AnimationPlayer to animate the menu appearing. I named it show.

Only two properties need to change: the Modulate’s alpha which goes from 0 to 1.0 and Visible.

I added a two-second delay to the fade animation as the Events.track_finished signal emits as soon as the HitSpawner spawned the song’s last element. And we’ll use it to trigger this animation.

Attach a script to UITrackFinished. In it, we connect to Events.track_finished and play the animation.

extends Control

onready var _animation_player := $AnimationPlayer


func _ready() -> void:
    Events.connect("track_finished", self, "_on_Events_track_finished")


func _on_Events_track_finished() -> void:
    _animation_player.play("show")

Connect the pressed signal on the ButtonBack to UITrackFinished.gd to reload the scene.

# ...

func _on_ButtonBack_pressed():
    get_tree().reload_current_scene()

Finally, instantiate the UITrackFinished.tscn scene to RhythmGameDemo and disable its visibility to complete the UI trio.

Because we emit the track_finished signal in HitSpawner.gd already when reaching the end of the track, the UI will appear and allow you to return to the main menu.

Congratulations! You have a working rhythm game!

You should have enough to adapt these concepts and techniques into your own game, but if you’d like to continue with this style of rhythm game, in the next and final lesson, we’ll build on the gameplay by adding the HitRoller. See you there!

Code reference

UITrackFinished.gd

extends Control

onready var _animation_player := $AnimationPlayer


func _ready() -> void:
    Events.connect("track_finished", self, "_on_Events_track_finished")


func _on_Events_track_finished() -> void:
    _animation_player.play("show")


func _on_ButtonBack_pressed() -> void:
    get_tree().reload_current_scene()