04.scoreboard-listing-names

Listing names in the score panel

In this lesson, we’ll add code to the scoreboard to display a list of names.

Right-click on the Scoreboard node and select Attach Script to give it a new script.

Ensure that the template is set to Empty, then click Create.

We want to list names and scores in the ScoresColumn node. For each name, we’ll use a Label node.

As we’ll need various names, we don’t want to create label nodes by hand.

Instead, we’ll use code to create labels dynamically.

Creating a node using code

So far, we have created nodes in the editor using the Create New Node window.

You can also create nodes using GDScript code, like so:

  1. You write the node type with its name followed by .new(). For example, Timer.new(), or Sprite.new().
  2. You initialize the node to work the way you want using its properties, like Sprite.texture.
  3. You add it as a child of another node with the add_child() member function.

Until you add a node as a child of another, it only exists in the computer’s memory and does not affect the display.

Here’s a code example to create Label nodes using code.

func add_label(text: String) -> void:
    var label := Label.new()
    label.text = text
    # This call adds the label as a child of the node with this script attached.
    add_child(label)

And another one creating a Timer.

func add_timer(duration: float) -> void:
    # We create a new Timer node.
    var timer := Timer.new()
    # We set the timer to last duration seconds and not cycle.
    timer.wait_time = duration
    timer.one_shot = true
    # And we add it as a child of the node with the script attached.
    add_child(timer)

The advantage of using code is that we can create as many nodes as we need without knowing their count in advance.

A function to create labels

Let’s code a function to add a name to the scoreboard. We’ll call it add_line() as we’ll later use it to register the player’s name and score.

We first create a Label node and store a reference to it in the line variable. We then access the label’s text member variable through the line variable.

func add_line(player_name: String) -> void:
    var line := Label.new()
    line.text = player_name

Setting a node doesn’t make it appear on the screen. To display the label, we must add it to our node tree.

We want to add labels as children of the ScoresColumn node so they arrange vertically.

So we first get the ScoresColumn node, and add the Label as its child by calling the ScoresColumn’s add_child() function.

func add_line(player_name: String) -> void:
    #...
    $MarginContainer/VBoxContainer/ScoresColumn.add_child(line)

Note: In Godot, the $ is an alias for the get_node() function.

You give it the path to a node, and the function returns a reference to the node. Getting the node allows you to access the node’s functions and variables.

Storing the scores column in a variable

You can see how the node path for the ScoresColumn is long and difficult to read. Our code would be more readable with a variable to label it.

    # We want to add the line in our ScoresColumn, so we call add_child() on the
    # ScoresColumn node.
    scores_column.add_child(line)

A good practice in GDScript is to create member variables for nodes you use at the top of your script.

It shows you the nodes that the script needs to work at a glance.

However, we can’t just store a node in a member variable. We need to wait for a node to be in its ready state before we access it.

Waiting for nodes to be ready

When you run a scene, Godot creates nodes and adds them to the scene tree sequentially. Until then, child nodes are not accessible in your code: you can’t use the $ sign to access the node.

We say that the nodes are not ready.

Godot provides a special function you can write to run code when all child nodes are ready:

func _ready() -> void:
    pass

You can use it to get a child node and store it in a script member variable.

var scores_column: Node

func _ready() -> void:
    scores_column = $MarginContainer/VBoxContainer/ScoresColumn

Then, every time you want to access the ScoresColumn node, you can write scores_column.

The onready shortcut

As assigning references to variables is very common, GDScript provides a keyword to set variables when children are ready: onready.

We can use it to store the ScoresColumn in a variable. You’ll want to add the following line above the add_line() function.

onready var scores_column := $MarginContainer/VBoxContainer/ScoresColumn

Note that the onready must come before the var keyword. Also, it only works when defining a member variable.

With the line above, you can use the variable in the add_line() function. Your code should look like this:

extends PanelContainer

onready var scores_column := $MarginContainer/VBoxContainer/ScoresColumn

func add_line(player_name: String) -> void:
    var line := Label.new()
    line.text = player_name
    # We want to add the line in our ScoresColumn, so we call add_child() on the
    # ScoresColumn node.
    scores_column.add_child(line)

Adding names to the score

To add a line to the scoreboard, you need to call the add_line() function.

We must do this inside the _ready() function as the add_line() function requires the ScoresColumn node.

func _ready() -> void:
    add_line("Athos")
    add_line("Portos")
    add_line("Aramis")

You can call the function as many times as you’d like. If you run the scene (F6), you should see a list of names.

Your questions

How does the _ready() guarantee that I can get the node?

The _ready() function, like the _process() function, is defined in the Godot engine. It exists on every node.

When you run a scene, Godot reads your scene file and prepares to create a tree of nodes.

Godot first creates the nodes sequentially, from top to bottom.

Then, Godot calls the _ready() function on the nodes following two rules:

  1. The engine processes nodes from top to bottom.
  2. Whenever a node has children, the engine calls _ready() on the children before the parent.

In our Scoreboard scene, the engine will create the nodes from top to bottom, making them ready to use, starting with the Label, then the HSeparator, and then the ScoresColumn, in this order.

Then, it will make the parent VBoxContainer ready, then the MarginContainer, and finally, the Scoreboard.

This approach allows you to have child nodes initialize themselves properly before getting them in the _ready() function or using the onready keyword.

What does “void” mean?

The void keyword after the add_line() and _ready() function definitions means that the functions do not return a value.

If you specify that the function is void, Godot will warn you if you or a teammate tries to return a value from it.

The code

Here is the complete code for this first lesson.

extends PanelContainer

onready var scores_column := $MarginContainer/VBoxContainer/ScoresColumn


func _ready() -> void:
    add_line("Athos")
    add_line("Portos")
    add_line("Aramis")


func add_line(player_name: String) -> void:
    var line := Label.new()
    line.text = player_name
    # We want to add the line in our ScoresColumn, so we call add_child() on the
    # ScoresColumn node.
    scores_column.add_child(line)