04.displaying-text-and-portrait

Displaying the right text and character portrait

In this lesson, we’ll write the code to display a line of dialogue with the correct character portrait.

The code is similar to the slideshow series, so we’ll go through some code samples faster than before.

Like before, we’ll create a function to show a line of dialogue that takes the unique ID and displays the corresponding line.

We want to do three things:

  1. Extract a line’s data from the dialogue dictionary.
  2. Display the correct character portrait on our TextureRect.
  3. Display the correct text in our RichTextLabel.

We’ll split this process into three functions. Let’s start with two functions that update the RichTextLabel’s text and the displayed expression.

Writing a function to update the displayed text

First is the set_text() function. It changes the RichTextLabel’s bbcode_text property. RichTextLabel has both a text and a bbcode_text property. You need to use bbcode_text for BBCode text formatting to work.

onready var rich_text_label := $MarginContainer/VBoxContainer/HBoxContainer/RichTextLabel

func set_text(new_text: String) -> void:
    rich_text_label.bbcode_text = new_text

Like in the slideshow, we create a function because we’ll later animate the text, which will require more lines of code.

Writing a function to change the displayed expression

Our dialogue variable stores the desired character’s facial expression as a String instead of preloading textures.

# ...
"expression": "neutral",
# ...

In general, you do not want to hardcode preloaded textures in your dialogue data because it becomes difficult to change once you have thousands of dialogue lines.

If you decide to move the portrait files, you will have to change every call to the preload() function to use the new paths.

When coding games, we very often need to move files like this.

To make it easier to update loaded file paths, we can write a function that centralizes all loading in one place.

The function takes a String parameter and displays the corresponding texture to use.

We can use conditions like the following to use a given texture depending on the expression.

onready var texture_rect := $MarginContainer/VBoxContainer/HBoxContainer/TextureRect

func set_expression(expression: String) -> void:
    if expression == "sad":
        texture_rect.texture = preload("portraits/sophia_sad.png")
    # Elif is the contraction of "else if." This block will run if the previous
    # condition didn't pass.
    elif expression == "happy":
        texture_rect.texture = preload("portraits/sophia_happy.png")
    # Any `expression` that we don't list above will display the neutral
    # character portrait.
    else:
        texture_rect.texture = preload("portraits/sophia_neutral.png")

Displaying a dialogue line

We now have the two functions we need to update the displayed dialogue line.

We define a new show_line() function that extracts the line’s data from the dialogue variable and sets the text and the character’s texture. We’ll call this function to advance through the dialogue.

Note the explanations inside of code comments. We’ll place explanations inside the code frequently moving forward to keep them as close as possible.

func show_line(id: int) -> void:
    # We extract the line's data from the `dialogue` variable.
    var line_data: Dictionary = dialogue[id]
    # We then pass the text and expression values of the line's data to
    # functions that update the displayed text and character portrait.
    set_text(line_data.text)
    set_expression(line_data.expression)

Finally, in the _ready() function, we call the show_line() function.

func _ready() -> void:
    show_line(0)

And that’s all we need to display some text and update the character’s portrait.

If you run the scene with F6, you should see the first line of dialogue.

There is a little problem, though. The text contains BBCode so the words “wake up” should appear in italics.

var dialogue = {
    0: {
        "text": "Hey, [i]wake up![/i] It's time to make video games.",
        # ...

Italics don’t work because we didn’t provide our RichTextLabel node with the necessary font files.

Adding font overrides to the RichTextLabel node

A RichTextLabel needs dedicated font resources to display text in italics, bold, and bold-italics.

Let’s add font overrides to the node to see our formatted text. We prepared several DynamicFont files to save you some repetitive steps. You’ll find them in the DialogueBoxes/common/fonts/ folder.

Select the RichTextLabel node in the scene dock and, in the Inspector, expand the Theme Overrides -> Fonts category.

In the FileSystem dock, open the DialogueBoxes/common/fonts/ directory. You want to drag and drop fonts onto the correct slots:

After assigning the fonts, your Inspector should look like this.

You do not need to override the Normal Font as the RichTextLabel will use the default font from our Theme resource.

Lastly, the Mono Font allows you to format code, which we don’t have in our text data. We can ignore it.

Before seeing the formatted text, you need to turn on Bbcode -> Enabled on the RichTextLabel node.

You should now see text in italics and bold when running the scene.

Why do we split the code into multiple functions?

You could have all the code we wrote above in a single show_line() function, which would work the same.

Why do we split it, then?

There are two reasons to split your code into functions:

  1. You need to reuse code in different places. You can wrap that code into a function and call that function to avoid duplicating the code.
  2. You have a lot of code in one place, and it gets a bit hard to read. Splitting it into functions can make the logic easier to follow.

In this course, we mainly use functions for readability and learning. We try to group related instructions into chunks that aren’t too overwhelming for you to process.

You might wonder how to know when to split code into functions. Some professionals advocate using many short functions, while others tend to use much longer ones.

Either style can work fine, but it takes experience with both to get their advantages and drawbacks. For now, please don’t worry about it too much and focus on making your code work.

In the next lesson, we’ll write code to generate buttons from our dialogue’s data.