Building a Retro Dialogue plugin for Godot, Part 2
Tags: godot, tutorial, gamedev
Published: 2023-01-28 02:31:18

---
title: Building a Retro Dialogue plugin for Godot, Part 2
published: true
date: 2023-01-28 11:21:00 UTC
tags: godot, tutorial, gamedev
canonical_url: https://www.alexpech.com/tutorials/building-a-retro-dialogue-plugin-for-godot/part-2/
---

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#table-of-contents)Table of Contents

1. [Part 1 Recap](#part-1-recap)
2. [Introduction](#introduction)
3. [Swapping characters](#swapping-characters)
4. [Animating the text, printing character by character](#animating-the-text-printing-character-by-character)
5. [Adding effects using bbcode](#adding-effects-using-bbcode)
6. [Variables](#variables)
7. [Conclusion](#conclusion)

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#part-1-recap)Part 1 Recap

In [Part 1](https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-1-3jb9), we looked at:

- Project setup for a low-res 'SNES-style' game
- UI layout for our dialogue system
- Scripting a basic, branching conversation

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#introduction)Introduction

In Part 2, we're going to get a lot deeper into scripting.

We'll be making the conversation more dynamic, adding the ability to swap characters and insert variables into the text.

We're also going to add some animation and enhanced visuals to our text by printing out characters and using [BBCode](https://en.wikipedia.org/wiki/BBCode).

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#swapping-characters)Swapping characters

Continuing on from Part 1, what you should have is a scene where the player can have a conversation with a character and make choices to take different branches.

This is all great if your player character only ever has a conversation with one person. Realistically however, you'll want to be able to change which character to use per conversation, and also have conversations with multiple participants.

Let's get started by making our characters configurable on the node.

Export a couple of variables for the character name and portrait.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
export var character_name = "ALEX"
export var character_portrait: Texture
```

We also need to let our script know about the Portrait node.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
onready var portrait_node = get_node("PortraitRect/Portrait")
```

Now, let's set the name and portrait in our `_ready` function.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
portrait_node.texture = character_portrait
name_node.text = character_name
```

That's it! Go back to our scene and try setting a new character name on our node and dragging in a new portrait texture.

[![Screenshot of editor showing custom name and portrait set in inspector](https://www.alexpech.com/static/a153c4af6a52cdf542ce30ae9516fd6b/2b727/7-change-character.png "Screenshot of editor showing custom name and portrait set in inspector")](/static/a153c4af6a52cdf542ce30ae9516fd6b/2b727/7-change-character.png)

Run the scene. Easy as that, a configurable character name and portrait!

[![Screenshot of running scene showing configured character name and portrait](https://www.alexpech.com/static/7cdd90b37cc5d6af1eedca4bff6a0a9e/8c557/7-new-character-in-scene.png "Screenshot of running scene showing configured character name and portrait")](/static/7cdd90b37cc5d6af1eedca4bff6a0a9e/01dae/7-new-character-in-scene.png)

But what if we want different characters as part of the conversation? Well, let's start with how we think we'd define that in our conversation data structure.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
var conversation = [
  {
    "character": "alex",
    "dialogue": "Hey there.\nDo you like apples?",
    "choices": [
      {
        "dialogue": "Sure do!",
        "destination": "apples_good"
      },
      {
        "dialogue": "No way, gross!",
        "destination": "apples_bad"
      }
    ]
  },
  {
    "character": "alex",
    "label": "apples_good",
    "dialogue": "You like apples? Me too!",
    "destination": "part_2"
  },
  {
    "character": "alex",
    "label": "apples_bad",
    "dialogue": "You don't?\nThat's a shame."
  },
  {
    "label": "part_2",
    "character": "alex",
    "dialogue": "I like other fruits too."
  },
  {
    "character": "alex",
    "dialogue": "Hey JUPITER, what do you like?"
  },
  {
    "character": "jupiter",
    "dialogue": "I prefer oranges..."
  },
  {
    "character": "alex",
    "dialogue": "Bananas are my favourite!"
  }
]
```

We've added in a `"character"` key for each section of dialogue with the character's name. Notice we haven't specified the portrait texture here as well. It'll be easier if we just use this character name as a label to look up the portrait texture and the display name.

Let's modify our exported variables to accomodate that. (Replace the image filepaths with your own portraits that you're using if needed.)

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
export var characters = {
  "alex": {
    "name": "ALEX",
    "portrait": preload("res://images/Pixel Portraits/female_10_t.png")
  },
  "jupiter": {
    "name": "JUPITER",
    "portrait": preload("res://images/Pixel Portraits/female_11_t.png")
  }
}
```

It should now look like this in the editor:

[![Screenshot of editor showing new exported character variables](https://www.alexpech.com/static/3ddabd11d5c39b17dcc7196f12689abe/f0991/8-expanded-character-inspector.png "Screenshot of editor showing new exported character variables")](/static/3ddabd11d5c39b17dcc7196f12689abe/f0991/8-expanded-character-inspector.png)

Next, let's define a function that will let us fetch these names and portrait textures based on the current character in the conversation.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
func update_character():
  var current_character = conversation[current_index].get("character")
  
  portrait_node.texture = characters[current_character]["portrait"]
  name_node.text = characters[current_character]["name"]
```

Finally, let's call this function whenever the conversation progresses.

In `_ready`,

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
func _ready():
  update_text_labels()
  update_select_indicators()
  update_character()
```

and in `_process`,

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
if current_index != previous_index:
  update_text_labels()
  update_character()
  reset_selection()
```

That's all we need to do! Try running the scene. You'll see the character names and portraits change based on the character labels we provided in our conversation data.

[![Screenshot of scene running new character code](https://www.alexpech.com/static/7bfc5790845890f9b15153cc95bf1c41/8c557/9-multi-character-convo-1.png "Screenshot of scene running new character code")](/static/7bfc5790845890f9b15153cc95bf1c41/d2a60/9-multi-character-convo-1.png)[![Screenshot of running scene showing second character in conversation](https://www.alexpech.com/static/fc58adaa40cb426828bbf8bd0ad04a05/8c557/9-multi-character-convo-2.png "Screenshot of running scene showing second character in conversation")](/static/fc58adaa40cb426828bbf8bd0ad04a05/d2a60/9-multi-character-convo-2.png)

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#animating-the-text-printing-character-by-character)Animating the text, printing character by character

Currently, we really only have one state our conversation can be in, which is to display the current dialogue and available choices. If we want to show the text printing out before we make a choice, we need to introduce a second state. We can do this by simply introducing a boolean variable, `text_in_progress`, and having our `_process` function do different things based on its value.

1. When `text_in_progress` is `true`, we're in the process of printing out text. Our current choices should be hidden.
2. When `text_in_progress` is `false`, our full text is displayed and the current choices are shown.

We progress from 1) to 2) automatically when the text is complete.

We progress from 2) back to 1) (incrementing the dialogue index) when the player makes a choice.

To control the printing animation of the text, we're going to use a Timer node.

I've based this section on an existing example here [https://www.codegrepper.com/code-examples/go/TypeWriter+Text+Godot](https://www.codegrepper.com/code-examples/go/TypeWriter+Text+Godot)

Add a `Timer` node to scene, called `TextTimer`.

[![Screenshot of scene hierarchy with TextTimer node added](https://www.alexpech.com/static/a11e63096b7f86b140f70375a93790ee/008e2/10-text-timer.png "Screenshot of scene hierarchy with TextTimer node added")](/static/a11e63096b7f86b140f70375a93790ee/008e2/10-text-timer.png)

Text speed can be modified by changing the Wait time of the Timer.

[![Screenshot of Timer settings with Wait Time set to 0.1 seconds](https://www.alexpech.com/static/06caff410f4af19330c57586abf7374f/50ac3/10-text-timer-2.png "Screenshot of Timer settings with Wait Time set to 0.1 seconds")](/static/06caff410f4af19330c57586abf7374f/50ac3/10-text-timer-2.png)

Make the timer node available in our script.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
onready var text_timer_node = get_node("TextTimer")
```

Add in a variable to represent the new state we need.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
var text_in_progress = false
```

Define some functions to help us show/hide the choices between state transitions. You'll see how we use these below.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
func show_choices():
  set_choices_visible(true)
  reset_selection()
  choice_a_node.text = get_current_choice(0).get("dialogue", "...")
  choice_b_node.text = get_current_choice(1).get("dialogue", "")
  
func hide_choices():
  set_choices_visible(false)
  
func set_choices_visible(visible):
  var nodes = [
    select_a_node,
    select_b_node,
    choice_a_node,
    choice_b_node
  ]
  for node in nodes:
    node.visible = visible
```

Next, add this function. It will handle the printing of the dialogue one character at a time.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
func print_dialogue( dialogue ):
  text_in_progress = true
  update_character()
  hide_choices()
  dialogue_node.text = ""
  
  for letter in dialogue:
    text_timer_node.start()
    dialogue_node.add_text(letter)
    yield(text_timer_node, "timeout")

  show_choices()
  text_in_progress = false
```

Our transition into the 'printing' state happens at the start of this function. Within the loop, we add a letter and use `yield` to allow it to display and wait for the timer before showing the next one. Once the loop is done and all our text is printed out, we show the choices and toggle our state.

In `_process`, start with,

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
if text_in_progress:
  return
```

This prevents user action during the printing state.

To transition to the next piece of dialogue, we now simply call `print_dialogue` after a choice is made. Like this,

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
if current_index != previous_index:
  print_dialogue(conversation[current_index]["dialogue"])
```

All the required updates are now handled with `print_dialogue`.

Finally, in `_ready`, we just need to kick off the initial bit of dialogue,

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
func _ready():
  print_dialogue(conversation[current_index]["dialogue"])
```

We no longer need the `update_text_labels` function. It can be removed.

You can now run the scene!

There's one more feature here I'd like to add - press Enter to skip to the end (show all dialogue and choices).

Define a boolean variable to flag when we should skip the rest of the printing of the text:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
var skip_text_printing = false
```

During `_process`, if we're currently printing out text and Enter is pressed, then skip the text printing:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
func _process(delta):
  if text_in_progress:
    if Input.is_action_just_pressed("ui_accept"):
        skip_text_printing()

    return
```

Define `skip_text_printing()`. This is going to set our variable and stop our text timer.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
func skip_text_printing():
  skip_text_printing = true
  text_timer_node.emit_signal("timeout")
  text_timer_node.stop()
```

We also force the timer to fire immediately, so that we don't wave to wait when using slow text speeds.

Then, to skip the rest of the text inside `print_dialogue()`, put this after the call to `yield`,

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
if skip_text_printing:
  skip_text_printing = false
  dialogue_node.text = dialogue
  break
```

This will immediately complete the text and break out of our letter printing loop the next time our timer fires.

Run the scene. You should be able to skip through the conversation by pressing the Enter key to skip the letter-by-letter printing.

Here's where your script should be at:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
extends Control

export var characters = {
  "alex": {
    "name": "ALEX",
    "portrait": preload("res://images/Pixel Portraits/female_10_t.png")
  },
  "jupiter": {
    "name": "JUPITER",
    "portrait": preload("res://images/Pixel Portraits/female_11_t.png")
  }
}

onready var name_node = get_node("DialogueRect/CharacterName")
onready var portrait_node = get_node("PortraitRect/Portrait")
onready var dialogue_node = get_node("DialogueRect/Dialogue")
onready var choice_a_node = get_node("DialogueRect/ChoiceA")
onready var choice_b_node = get_node("DialogueRect/ChoiceB")
onready var select_a_node = get_node("DialogueRect/SelectA")
onready var select_b_node = get_node("DialogueRect/SelectB")
onready var text_timer_node = get_node("TextTimer")

var conversation = [
  {
    "character": "alex",
    "dialogue": "Hey there.\nDo you like apples?",
    "choices": [
      {
        "dialogue": "Sure do!",
        "destination": "apples_good"
      },
      {
        "dialogue": "No way, gross!",
        "destination": "apples_bad"
      }
    ]
  },
  {
    "character": "alex",
    "label": "apples_good",
    "dialogue": "You like apples? Me too!",
    "destination": "part_2"
  },
  {
    "character": "alex",
    "label": "apples_bad",
    "dialogue": "You don't?\nThat's a shame."
  },
  {
    "label": "part_2",
    "character": "alex",
    "dialogue": "I like other fruits too."
  },
  {
    "character": "alex",
    "dialogue": "Hey JUPITER, what do you like?"
  },
  {
    "character": "jupiter",
    "dialogue": "I prefer oranges..."
  },
  {
    "character": "alex",
    "dialogue": "Bananas are my favourite!"
  }
]

var current_index = 0
var current_choice = 0
var text_in_progress = false
var skip_text_printing = false

func _ready():
  print_dialogue(conversation[current_index]["dialogue"])

func _process(delta):
  if text_in_progress:
    if Input.is_action_just_pressed("ui_accept"):
      skip_text_printing = true
      text_timer_node.emit_signal("timeout")
      text_timer_node.stop()
      
    return
    
  if current_index < (conversation.size() - 1):
      var previous_index = current_index
      
      if Input.is_action_just_pressed("ui_up"):
        safe_select_previous_choice()
        
      if Input.is_action_just_pressed("ui_down"):
        safe_select_next_choice()
        
      if Input.is_action_just_pressed("ui_accept"):
        current_index = get_next_index()
    
      if current_index != previous_index:
        print_dialogue(conversation[current_index]["dialogue"])

func get_index_of_label(label):
  for i in range(conversation.size()):
    if conversation[i].get("label") == label:
      return i
  
  assert(false, "Label %s does not exist in this conversation!" % label)
  
func get_next_index():
  var destination = null
  if conversation[current_index].has("choices"):
    var choice = conversation[current_index]["choices"][current_choice]
    destination = choice.get("destination")
  else:
    destination = conversation[current_index].get("destination")
    
  if destination:
    return get_index_of_label(destination)
  else:
    return current_index + 1
  
func get_current_choice(choice_index):
  var choices = conversation[current_index].get("choices", [])
  if choice_index < choices.size():
    return choices[choice_index]
  else:
    return {}
  
func update_select_indicators():
  var select_nodes = [
    select_a_node,
    select_b_node
  ]
  for node in select_nodes:
    node.visible = false
    
  select_nodes[current_choice].visible = true

func get_current_choice_count():
  var choices = conversation[current_index].get("choices")
  if choices:
    return choices.size()
  else:
    return 1

func safe_select_previous_choice():
  current_choice = clamp(current_choice - 1, 0, get_current_choice_count() - 1)
  update_select_indicators()
  
func safe_select_next_choice():
  current_choice = clamp(current_choice + 1, 0, get_current_choice_count() - 1)
  update_select_indicators()
  
func reset_selection():
  current_choice = 0
  update_select_indicators()
  
func update_character():
  var current_character = conversation[current_index].get("character")
  
  portrait_node.texture = characters[current_character]["portrait"]
  name_node.text = characters[current_character]["name"]

func show_choices():
  set_choices_visible(true)
  reset_selection()
  choice_a_node.text = get_current_choice(0).get("dialogue", "...")
  choice_b_node.text = get_current_choice(1).get("dialogue", "")
  
func hide_choices():
  set_choices_visible(false)
  
func set_choices_visible(visible):
  var nodes = [
    select_a_node,
    select_b_node,
    choice_a_node,
    choice_b_node
  ]
  for node in nodes:
    node.visible = visible
  
func print_dialogue( dialogue ):
  text_in_progress = true
  update_character()
  hide_choices()
  dialogue_node.text = ""
  
  for letter in dialogue:
    text_timer_node.start()
    dialogue_node.add_text(letter)
    yield(text_timer_node, "timeout")
    
    if skip_text_printing:
      skip_text_printing = false
      dialogue_node.text = dialogue
      break

  show_choices()
  text_in_progress = false
  
func skip_text_printing():
  skip_text_printing = true
  text_timer_node.emit_signal("timeout")
  text_timer_node.stop()
```

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#adding-effects-using-bbcode)Adding effects using bbcode

What is BBCode? Basically, it allows as to apply formatting to our text by using special BBCode 'tags'. We can do things like **bold** or <u>underline</u> text. We can also set the color, and even add other special effects. The Godot docs for RichTextLabel have a lot of good examples for what's available: [https://docs.godotengine.org/en/stable/tutorials/ui/bbcode\_in\_richtextlabel.html](https://docs.godotengine.org/en/stable/tutorials/ui/bbcode_in_richtextlabel.html)

Let's get started by enabling BBCode on all our `RichTextLabels`. You'll notice that the Bb Code section has a separate `Text` attribute, so when you enable it, you'll see the original default text disappear. To have it show in the editor, you need to add some text to the `Text` section under the `Bb Code` heading.

[![Screenshot of RichTextLabel inspector with Bb Code enabled](https://www.alexpech.com/static/1a8df394b9c289560477e804d9626840/39e45/11-enable-bbcode.png "Screenshot of RichTextLabel inspector with Bb Code enabled")](/static/1a8df394b9c289560477e804d9626840/39e45/11-enable-bbcode.png)

Everywhere in the script we call `.text` on one of our RichTextLabel nodes, we need to replace it with `.bbcode_text`.

Once you've done that, run the scene. Everything should still work as it did before.

Now, let's try out some BBCode tags. Replace the first part of the conversation with this:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
{
  "character": "alex",
  "dialogue": "Hey there.\nDo you like [wave amp=10 freq=-10][color=green]apples[/color][/wave]?",
  "choices": [
    {
      "dialogue": "[wave amp=10 freq=10]Sure do![/wave]",
      "destination": "apples_good"
    },
    {
      "dialogue": "No way, [color=grey][shake rate=10 level=10]gross![/shake][/color]",
      "destination": "apples_bad"
    }
  ]
},
```

[![Screenshot showing BBCode tags within the printed out dialogue](https://www.alexpech.com/static/486033d0e377c427cb691e88d46af997/8c557/13-bbcode-error.png "Screenshot showing BBCode tags within the printed out dialogue")](/static/486033d0e377c427cb691e88d46af997/394f7/13-bbcode-error.png)

Except... oh dear! Our BBCode tags are showing up in our dialogue!

So, what's going on here? Well, recall that we're printing out the dialogue by adding the text character by character. Since our dialogue now needs to be interpreted as BBCode text, the `RichTextLabel` needs to be aware of the _whole_ dialogue chunk for it to render it.

We're going to need to change how we print out our dialogue. Luckily, the `RichTextLabel` class includes a convenient property called `visible_characters`, which we can use to set how much of our dialogue is visible. The best part is, the BBCode tags aren't included. What we can do is set `bbcode_text` to our dialogue upfront, start with `visible_characters = 0`, and then increment `visible_characters` on each timer tick.

Let's modify our `print_dialogue` function to use `visible_characters` instead,

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
func print_dialogue( dialogue ):
  text_in_progress = true
  update_character()
  hide_choices()
  
  dialogue_node.bbcode_text = dialogue
  dialogue_node.visible_characters = 0

  for i in dialogue_node.get_total_character_count():
    text_timer_node.start()
    dialogue_node.visible_characters += 1
    yield(text_timer_node, "timeout")

    if skip_text_printing:
      skip_text_printing = false
      dialogue_node.visible_characters = -1
      break

  show_choices()
  text_in_progress = false
```

You'll notice also that we've changed our loop to iterate over the total character count of the `RichTextLabel` rather than the characters in the dialogue string. Again, using `get_total_character_count` means that the BBCode tags will be ignored, so our character count will be accurate.

Logically, this should work. However, if you run the scene, you'll notice that the dialogue doesn't actually print out - it's getting stuck. The reason is that `get_total_character_count()` is returning 0. This is due to a limitation with the `RichTextLabel` class in the engine. Updating `visible_characters` (or its counterpart, `percent_visible`) won't update `get_total_character_count()` until the next frame.

I'm going to use a simple workaround for this and just **wait until the next frame** before checking the character count. For this application, a single frame delay isn't going to be noticable to our user.

Add this line just before our for loop:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
yield(get_tree(),"idle_frame")
```

What we're doing here is yielding until we receive the `"idle_frame"` signal from the SceneTree, which is emitted right before `Node._process`. Check out the Godot Docs for more info [https://docs.godotengine.org/en/stable/classes/class\_scenetree.html#class-scenetree-signal-idle-frame](https://docs.godotengine.org/en/stable/classes/class_scenetree.html#class-scenetree-signal-idle-frame)

If you run the scene now, you should see that the BBCode text prints our correctly, character by character.

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#variables)Variables

Let's say we don't know ahead of time exactly what our dialogue should be. For example, the player might choose during the game how they'd like to be referred to.

When Alex says "Hey there" at the start of the conversation, perhaps we'd like them to instead use a name or title. Let's see how we could go about inserting a variable into our dialogue.

For this, I'm going to use formatted strings. You can read about them here [https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript\_format\_string.html](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_format_string.html). Basically, the idea is that we can insert named variables between curly brackets {} and then pass in a dictionary of values. Our named variables will then get replaced with the value corresponding with the matching key in the dictionary.

To begin, let's modify our dialogue to define how we want to express our variables. I'm going to call mine `title`, like this:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
"dialogue": "Hey {title}.\nDo you like [wave amp=10 freq=-10][color=green]apples[/color][/wave]?"
```

Now, when we set the `bbcode_text` property on the RichTextLabel, we first need to format our string.

Modify the line where we set `bbcode_text` in `print_dialogue` to this:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
dialogue_node.bbcode_text = dialogue.format({ "title": "STRANGER" })
```

If you run the scene now, you'll see that Alex now refers to you as STRANGER (which makes sense, they haven't met us yet).

[![Screenshot of scene showing character referring to player as STRANGER](https://www.alexpech.com/static/e76840e8b5f23a0be2abcc127e039528/8c557/14-insert-variable.png "Screenshot of scene showing character referring to player as STRANGER")](/static/e76840e8b5f23a0be2abcc127e039528/c483d/14-insert-variable.png)

So, inserting variables is easy, but what about setting them?

Let's say we want the character to start the conversation by asking the player how they should be referred to.

Add this to the start of our conversation array.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
{
  "character": "alex",
  "dialogue": "Hi, I'm ALEX. How should I refer to you?",
  "choices": [
    {
      "dialogue": "Call me FRIEND"
    },
    {
      "dialogue": "It's CAPTAIN to you"
    }
  ]
},
```

Alex is now going to start by introducing themselves to you, and ask how they should refer to you.

Obviously, this won't work yet. What we need is for each choice to modify the title variable that gets passed into our call to `format`.

First, we need a title variable, so let's define one:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
var title = "STRANGER"
```

We also need to use this in our call to `format`:

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
dialogue_node.bbcode_text = dialogue.format({ "title": title })
```

We now need to figure out how to actually set this variable when each of our options are chosen.

For this, I'm going to introduce another option to our `"choices"` dictionary called `"call"`.

It's going to look something like this,

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
"choices": [
  {
    "dialogue": "Call me FRIEND",
    "call": ["set", "title", "FRIEND"]
  },
  {
    "dialogue": "It's CAPTAIN to you",
    "call": ["set", "title", "CAPTAIN"]
  }
]
```

I'll explain what my idea is here. I've added this `"call"` key to `"choices"`, as in "function to call". I'm setting the value as an array where the first element is the name of the function to call, and the remaining elements are the arguments for that function. In this case, I'm wanting to call the [Object#set](https://docs.godotengine.org/en/stable/classes/class_object.html#id4) function, which takes two arguments: the name of the property to set, and the value to set it to. So, if the player chooses the "Call me FRIEND" option, we're saying we want to call `set("title", "FRIEND")`.

Here's how we can handle this in the code. Add this function.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
func execute_current_choice():
  var call_array = get_current_choice(current_choice).get("call")
  
  if call_array:
    var call_method = call_array[0]
    var call_args = call_array.slice(1, call_array.size())
    callv(call_method, call_args)
```

This function fetches the `"call"` value for the currently selected choice and, if it exists, calls the given function with the given arguments. Note, we're using `callv` instead of `call` simply because it allows us to provide the arguments as an array.

Finally, we need to call this function when the choice is made. Add this just under where we handle our 'Enter' key press for selecting a choice.

<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewbox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"></path><path d="M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z"></path></svg>

```
if Input.is_action_just_pressed("ui_accept"):
    execute_current_choice()
    current_index = get_next_index()
```

One big advantage of doing it this way is it doesn't just restrict us to setting variables - we can call any function we want with arbitrary arguments, so we can do a lot more than assigning a value! More on that later though...

For now, we're ready to try this out! Run the scene a couple of times and make a different choice of title. You'll see that the character refers to you based on the choice you made in the conversation.

[![Screenshot of scene with dialogue with ALEX referring to the player as CAPTAIN](https://www.alexpech.com/static/f262565e17b6ac21befbd4e863353e9f/8c557/15-title-captain.png "Screenshot of scene with dialogue with ALEX referring to the player as CAPTAIN")](/static/f262565e17b6ac21befbd4e863353e9f/9f21b/15-title-captain.png)

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#conclusion)Conclusion

In Part 2 we added several features to make our conversations more dynamic and visually interesting.

- [Try it out in your browser](/tutorials/building-a-retro-dialogue-plugin-for-godot/godot-demo/godot-retro-dialogue-part-2.html)
- [View and download the project on GitHub](https://github.com/alexpech12/godot-retro-dialogue/tree/v0.2)

[Continue to Part 3](https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-3-2pnf), where we look at integrating the conversation into the context of a larger game, triggering events, and handling larger sections of dialogue.