Building a Retro Dialogue plugin for Godot, Part 1
Tags: godot, tutorial, gamedev
Published: 2023-01-28 02:30:11

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

## [<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. [Introduction](#introduction)
2. [Project setup](#project-setup)
3. [Defining dialogue via a script](#defining-dialogue-via-a-script)
4. [Introducing branching conversation options](#introducing-branching-conversation-options)
5. [Making defining dialogue choices easier](#making-defining-dialogue-choices-easier)
6. [Scripting the UI](#scripting-the-ui)
7. [Refining the UI controls](#refining-the-ui-controls)
8. [Cleaning up our `_process` function](#cleaning-up-our-_process-function)

9. [Conclusion](#conclusion)

[![Cyberpunk scene in SNES pixel art style with dialogue box and options](https://www.alexpech.com/static/571251a39ce4987c097f486e1ac2e45a/8c557/title-image.png "Don't worry, yours doesn't need to be about apples...")](/static/571251a39ce4987c097f486e1ac2e45a/21335/title-image.png)
_Don't worry, yours doesn't need to be about apples..._

## [<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

Today we’re going to build a simple dialogue system in the Godot engine. This tutorial is broken up into several parts, each including completed source code on GitHub, plus a HTML build of the project at the end of each part that you can play right in your browser.

This is Part 1, which covers,

- The initial Godot scene setup, and any assets you'll need.
- Scripting a conversation with branching dialogue options
- Scripting UI controls to navigate the conversation

Here's an example of where we'll get to at the end of Part 1.

[![Screenshot of dialogue box showing character image and two dialogue options](https://www.alexpech.com/static/c16b9044b5ba7e5fe9f16e0e5e1cbb35/8c557/title-image-part-1.png "Screenshot of dialogue box showing character image and two dialogue options")](/static/c16b9044b5ba7e5fe9f16e0e5e1cbb35/20c85/title-image-part-1.png)

[Click here to play it in your browser](https://www.alexpech.com/tutorials/building-a-retro-dialogue-plugin-for-godot/godot-demo/godot-retro-dialogue-part-1.html)

There is also a [Part 2](https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-2-imo) and a [Part 3](https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-3-2pnf).

Part 2 covers,

- Conversations with multiple characters
- Advanced text effects and animation
- Using variables to dynamically modify the dialogue

Part 3 covers,

- Triggering events during conversation
- Handling large blocks of text
- Incorporating conversations into the context of a larger game

To see where we'll end up at the end of Part 3, [click here](https://www.alexpech.com/tutorials/building-a-retro-dialogue-plugin-for-godot/godot-demo/godot-retro-dialogue-part-1.html)

Part 4 is coming soon, and will cover how we can turn this into a reusable plugin for Godot.

* * *

The style I'm aiming for in this project is similar to retro nintendo RPGs. Think NES/SNES, or early Gameboy titles. Here's a few references for the kind of thing we want to build.

[![Screenshot from Chrono Trigger showing character dialogue box](https://www.alexpech.com/static/9e1c4b8531be0b5cce123b20116ac85e/8c557/chrono-trigger-2.png "Screenshot from Chrono Trigger showing character dialogue box")](/static/9e1c4b8531be0b5cce123b20116ac85e/2bef9/chrono-trigger-2.png)

![Screenshot from Chrono Trigger showing dialogue selection](https://www.alexpech.com/static/0dc9513698636f8d7b06047da356a2c6/8c557/chrono-trigger-1.png "Screenshot from Chrono Trigger showing dialogue selection")These screenshots from [Chrono Trigger (1995)](https://en.wikipedia.org/wiki/Chrono_Trigger) show how a window appears at the bottom of the screen to show dialogue, starting with the speaking character's name in all caps. Simple choices are also shown with a selection indicator pointing to the chosen option.

![Screenshot from Ducktales 2 (1993, NES) showing dialogue window with character portraits](https://www.alexpech.com/static/2015eef21d8b086a68260d69d7cb91cf/29d31/ducktales-2-nes.jpg "Screenshot from Ducktales 2 (1993, NES) showing dialogue window with character portraits")[Ducktales 2 (1993, NES)](https://en.wikipedia.org/wiki/DuckTales_2) has these character portraits beside the dialogue window to show who's speaking.

![Screenshot from Golden Sun (2001, GBA) showing dialogue options with character](https://www.alexpech.com/static/9887ceeadadb3411620324e56bc91088/e85cb/golden-sun-dialogue-screenshot.png "Screenshot from Golden Sun (2001, GBA) showing dialogue options with character")[Golden Sun (2001, GBA)](https://en.wikipedia.org/wiki/Golden_Sun_(video_game)) included a dialogue window and portrait window positioned dynamically on the screen, with simple yes/no options suitable for the smaller screen.

![Screenshot from Donkey Kong Country 3 (1996, SNES)](https://www.alexpech.com/static/c47723b858419c17cd4e362b96d63054/f4e34/donkey-kong-country-3-funky.webp "Screenshot from Donkey Kong Country 3 (1996, SNES)")[Donkey Kong Country 3 (1996, SNES)](https://en.wikipedia.org/wiki/Donkey_Kong_Country_3:_Dixie_Kong%27s_Double_Trouble!). This game also used a dialogue window at the bottom of the screen, with selectable options built into the game scene itself.

![Screenshot from Dune (1992, PC) showing dialogue options with character image](https://www.alexpech.com/static/f7e7b7f20efd230381dfe04924c22171/cb69c/dune.jpg "Screenshot from Dune (1992, PC) showing dialogue options with character image")[Dune (1992, PC)](https://en.wikipedia.org/wiki/Dune_(video_game)). A different layout and sizing suitable for a computer monitor, but still contains the main elements of a dialogue window, character portrait, and selectable dialogue options.

![Screenshot from Banjo Kazooie on N64](https://www.alexpech.com/static/e77fe1396a63f422686ceac737e3e09d/c08c5/329669-banjo-kazooie-nintendo-64-screenshot-mumbo-jumbo-the-shaman.jpg "Screenshot from Banjo Kazooie on N64")[Banjo Kazooie (1998, N64)](https://en.wikipedia.org/wiki/Banjo-Kazooie_(video_game)), a slightly later game for Nintendo 64, also includes a dialogue banner with a character portrait at the bottom of the screen, and also features text colouring and animation.

## [<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>](#project-setup)Project setup

Since I’m aiming for a SNES game style, we’ll be working with a screen resolution of 256x224. I've gone with a simple static layout that will occupy the bottom section of the screen, with a height of 64 pixels. I'm also going to include a character portrait element, which will go in the bottom left, in a 64x64 square.

I plan on using the dialogue section to also include dialogue options underneath.

[![Scene layout with pixel dimensions](https://www.alexpech.com/static/72f69766f4319fac700670a8800f542f/6f3f2/layout-mockup.png "Scene layout with pixel dimensions")](/static/72f69766f4319fac700670a8800f542f/6f3f2/layout-mockup.png)

* * *

I like designing things top-down, so before we get into scripting, let’s start by mocking up a UI.

Assets I’ll be using:

1. This 24x24 sprite for the NinePatchRect (I quickly whipped this up using [Piskel](https://www.piskelapp.com/). 
 ![ui border](https://www.alexpech.com/static/34ff8734ddb8107aeeb24dbe10da15af/8f029/ui_border.png "ui border")
2. zx\_spectrum font [https://www.dafont.com/zx-spectrum-7.font](https://www.dafont.com/zx-spectrum-7.font)
3. These 32x32 portrait images [https://emily2.itch.io/pixel-portraits-32x32](https://emily2.itch.io/pixel-portraits-32x32)
4. These great cyberpunk sprite sets [https://ansimuz.itch.io/warped-city](https://ansimuz.itch.io/warped-city) and [https://ansimuz.itch.io/warped-city-2](https://ansimuz.itch.io/warped-city-2)
5. This 32x32 speech bubble sprite. Another asset I quickly draw in Piskel. 
[![speech bubble](https://www.alexpech.com/static/a93b6a3d3eb8f70b6370a8f537fea0cc/103b3/speech_bubble.png "speech bubble")](/static/a93b6a3d3eb8f70b6370a8f537fea0cc/103b3/speech_bubble.png)

Having high quality assets available FOR FREE is incredibly helpful for writing tutorials like this one. If you download these assets, please remember to support artists in their tireless work and donate, if you can!

* * *

First, we have some settings to change.

Open your Project Settings (Project -> Project Settings)

- Rendering -> 2d -> Snapping -> Use Gpu Pixel Snap ![2D Rendering Settings - Turning on GPU pixel snap](https://www.alexpech.com/static/f442f4605391dcb4e3962a84458b3051/8c557/0-turn-on-gpu-pixel-snap.png "2D Rendering Settings - Turning on GPU pixel snap") (If you're using an older version of Godot, you might instead find this setting under Rendering -> Quality -> Turn on pixel snap. You can check out the [pull request](https://github.com/godotengine/godot/pull/46552) for the change in GitHub if you're interested.)
- Display -> Window -> Resolution to 256x224, set stretch mode to 2D [![Window Display Settings - Setting resolution and stretch mode](https://www.alexpech.com/static/9416486e1406b530327794804d3e6807/8c557/0a-window-display-settings.png "Window Display Settings - Setting resolution and stretch mode")](/static/9416486e1406b530327794804d3e6807/f4b77/0a-window-display-settings.png)

Our scene is going to be made up of these nodes:

- Control, the base node. This will contain the script for our DialogueBox
- NinePatchRect, for the dialogue box. This will have four RichTextLabel children: Character name, dialogue, Choice A and Choice B
- NinePatchRect, for the portrait box. This will have a Sprite for the character portrait

Let's get started!

1. Create a new scene. Choose 'Other node' and choose Control. Name it 'DialogueUI'.
2. Add a NinePatchRect and call it PortraitRect
3. Expand its Rect properties to set its x,y position to 0, 160, and set its size to 64x64. This will be the area for our portrait.
4. Select the ui\_border.png image from the FileSystem and drag it into the Texture property of our PortraitRect node. Also ensure the import settings for the image have Filter unchecked so that we get crisp pixels.
5. Set Patch Margin to 8 pixels for each side.
6. Add another NinePatchRect and call it DialogueRect
7. Set position to 64, 160. Set size to 192x64. Update the Patch Margin to 8 pixels each side.
8. Set its texture to ui\_border.png as well. Your viewport should be looking something like this: [![Viewport with NinePatchRect textures laid out](https://www.alexpech.com/static/26f8679046ad716d44c4314090abd4c2/8c557/1-viewport-with-panels.png "Viewport with NinePatchRect textures laid out")](/static/26f8679046ad716d44c4314090abd4c2/fbfd6/1-viewport-with-panels.png)
9. Drag in a portrait image as a child of the PortraitRect. Name it 'Portrait' and position it at (32,32) to centre it. In my case, my portrait is only 32x32 pixels, so I’ve scaled it up 2x. I’ve also edited it to add transparency at the edges so it doesn’t overlap the border. [![Adding a portrait image](https://www.alexpech.com/static/1ee227cd070dd3dbece8cdf3490fe475/8c557/2-adding-portrait.png "Adding a portrait image")](/static/1ee227cd070dd3dbece8cdf3490fe475/ef6b9/2-adding-portrait.png)

Creating the font resource:

1. Create a new resource of type DynamicFont. Call it `zx_spectrum.tres`. Drag the imported `zx_spectrum.ttf` file into the Font attribute of your new resource. Set the size of the font to 7.[![Creating the font resource](https://www.alexpech.com/static/5729336f29c4c076a78d38d8b07d08ef/6bdcf/3a-adding-name-font.png "Creating the font resource")](/static/5729336f29c4c076a78d38d8b07d08ef/6bdcf/3a-adding-name-font.png)

Now, let’s start adding our text:

1. Add a RichTextLabel as a child of the DialogueRect and position it at the top of the dialogue box. Call it 'CharacterName'. 
2. To set up the font, expand the Theme Overrides tab. Drag our font into the Normal Font attribute section. Add in some text to help with placement.
3. Add a second RichTextLabel below the first. Call this one Dialogue. Again, set the font, add some text, and position and scale it. I’ve kept the size as two lines only so that we can fit in two selection options below.
4. Add two more RichTextLabels for Choice A and Choice B. I’ve also added two extra labels called SelectA and SelectB, which I plan on using as indicators for which option is selected.

At this point, it should look something like this.

[![Viewport layout with text labels](https://www.alexpech.com/static/648fd257ad073932312ce825ca0c29c0/8c557/5-adding-choices-text.png "Viewport layout with text labels")](/static/648fd257ad073932312ce825ca0c29c0/c929c/5-adding-choices-text.png)

And your scene hierarchy should look something like this.

[![Scene hierarchy with portrait and text labels added](https://www.alexpech.com/static/5afb5634ce9c28e32f2d0bb4f94d1ccf/5f7d4/5-scene-heirarchy.png "Scene hierarchy with portrait and text labels added")](/static/5afb5634ce9c28e32f2d0bb4f94d1ccf/5f7d4/5-scene-heirarchy.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>](#defining-dialogue-via-a-script)Defining dialogue via a script

Great! It’s starting to look like something now. Unfortunately, it doesn’t actually \*do \*anything yet. If we play the scene, it’ll just show us the static text we just defined.

So, what sorts of things do we want our dialogue box to be able to do?

1. Follow a predefined text sequence, with player choices determining the path through the conversation.
2. Show the name and portrait of the character currently speaking.

These two will be enough to get started. It won’t be very dynamic or flexible, but it’ll be enough to get a basic conversation happening.

Let’s get stuck into some scripting!

Add a script to our base Control node and call it `DialogueUI`.

Let’s start simple. Instead of having our text set in the inspector, let’s define it in our script and set it there instead. To do that, we’re going to need to define some string constants, and get some references to our text labels so that we can modify them.

<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

onready var name_node = get_node("DialogueRect/CharacterName")
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")

func _ready():
  name_node.text = "ALEX"
  dialogue_node.text = "Hey there.\nDo you like apples?"
  choice_a_node.text = "Sure do!"
  choice_b_node.text = "No way, gross!"
```

All we’re doing here is grabbing our text labels and then setting their text properties when we start the scene. If you want to make sure it’s working, go ahead and change the text in the inspector so that we can see our script is actually setting the correct text.

Playing the scene gives us this:

[![Scene view with text displayed via script](https://www.alexpech.com/static/96f13e9d2f72928630c89e003ef4506e/160a3/6-scripted-text.png "Scene view with text displayed via script")](/static/96f13e9d2f72928630c89e003ef4506e/160a3/6-scripted-text.png)

That’s great, but it still doesn’t actually _do_ anything. Let’s start with the most basic thing we can think of. How about, “When I press Enter, show some new dialogue”. Add this to your 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>

```
func _process(delta):
  if Input.is_action_just_pressed("ui_accept"):
    dialogue_node.text = "This is what I'll say next."
```

Now, if you play the scene, the dialogue text will be replaced with this new text when you hit Enter.

Exciting stuff! But not very flexible. What we’d like instead is to be able to step through a whole list of dialogue pieces one bit at a time. To do that, we’ll need to starting tracking the current ‘state’ of our conversation.

For now, our ‘state’ will just be the index of where we’re up to in the conversation. Our dialogue will be defined in an array that we can step through using the Enter key.

Add this to the top of your 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>

```
var dialogue = [
  "Hey there.\nDo you like apples?",
  "I like other fruits too.",
  "Bananas are my favourite!"
]

var current_index = 0
```

Then, change our `_process` function to look 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>

```
func _process(delta):
  if Input.is_action_just_pressed("ui_accept"):
    current_index += 1
    dialogue_node.text = dialogue[current_index]
```

We also need to modify our \_ready function to set the dialogue initially.

<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():
  name_node.text = "ALEX"
  dialogue_node.text = dialogue[current_index]
  choice_a_node.text = "Sure do!"
  choice_b_node.text = "No way, gross!"
```

Now, when we run the scene, we’ll see that when we press the Enter key, we step through each piece of dialogue defined in our array!

There’s still some issues with our script. We’re overrunning the end of our array, so we’ll get an error once we get to the end of the conversation. Let’s fix our `_process` function so prevent that from happening.

<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 (Input.is_action_just_pressed("ui_accept")
      and current_index < (dialogue.size() - 1)):
    current_index += 1
    dialogue_node.text = dialogue[current_index]
```

We will now only progress to the next part of the conversation if the next index won’t overrun the size of the array.

## [<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>](#introducing-branching-conversation-options)Introducing branching conversation options

Great! We can now create a conversation as a pre-defined sequence of dialogue sections. Unfortunately, it’s not so much a conversation as it as a monologue. Next, let’s look at how we can introduce some choices.

First, what does it look like to have choices in our conversation? How does that affect the flow of the dialogue, and how might we represent that in our script?

![](https://imgs.xkcd.com/comics/blockchain.png)

We currently have control in our script over which part of the conversation we show in the UI. It’s just that the only control we’ve given the player is to go one step forward. What if, instead of just stepping forward, we allow the _choice_ the player makes to define the next index?

We’ll want to have different choices for each step of the conversation too. At the moment, each step in our conversation is just a string representing the dialogue. We need to make this more complex, so that we can include our choices in there too. For that, we’ll use a dictionary. Let’s modify the dialogue array to look 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>

```
var conversation = [
  {
    "dialogue": "Hey there.\nDo you like apples?"
  },
  {
    "dialogue": "I like other fruits too."
  },
  {
    "dialogue": "Bananas are my favourite!"
  }
  ]
```

We haven’t added any additional information for choices yet, we’ve just modified our data structure so that our dialogue now lives inside a "dialogue" key on a dictionary. We’ve also renamed our variable to `conversation` in preparation for this holding additional data for our conversation, other than just dialogue.

Let’s fix up our `_ready` and `_process` functions to use this new 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>

```
func _ready():
  name_node.text = "ALEX"
  dialogue_node.text = conversation[current_index]["dialogue"]
  choice_a_node.text = "Sure do!"
  choice_b_node.text = "No way, gross!"

func _process(delta):
  if (Input.is_action_just_pressed("ui_accept")
      and current_index < (conversation.size() - 1)):
    current_index += 1
    dialogue_node.text = conversation[current_index]["dialogue"]
```

Running the scene should still allow us to step through the conversation exactly as before.

Now, let’s modify our conversation array to include 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>

```
var conversation = [
  {
    "dialogue": "Hey there.\nDo you like apples?",
    "choices": [
      {
        "dialogue": "Sure do!",
        "destination": 1
      },
      {
        "dialogue": "No way, gross!",
        "destination": 2
      }
      ]
  },
  {
    "dialogue": "You like apples? Me too!"
  },
  {
    "dialogue": "You don't?\nThat's a shame."
  },
  {
    "dialogue": "I like other fruits too."
  },
  {
    "dialogue": "Bananas are my favourite!"
  }
  ]
```

As you can see, our choices have their own piece of dialogue, plus something called destination. This is the index of the next piece of dialogue to follow this choice. Index 1 is the response for when the player says they like apples. Index 2 is for when the player says they don’t like apples.

Again, before we start writing code for a UI to manage our choice selection, let’s write the simplest code we can to test out our new dialogue paths.

<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 current_index < (conversation.size() - 1):
    var previous_index = current_index
    
    if conversation[current_index].has("choices"):
      if Input.is_action_just_pressed("ui_up"):
        current_index = conversation[current_index]["choices"][0]["destination"]
        
      if Input.is_action_just_pressed("ui_down"):
        current_index = conversation[current_index]["choices"][1]["destination"]
      
    if Input.is_action_just_pressed("ui_accept"):
      current_index += 1
    
    if current_index != previous_index:
      dialogue_node.text = conversation[current_index]["dialogue"]
```

There’s a couple of things we’ve done here.

First, we’re using the Up and Down arrows (`ui_up` and `ui_down`) for our choice selection. We only check these when our current conversation step actually has choices. We’ll add the UI controls later.

Second, we’ve introduced a local variable, `previous_index`. This is how we’re going to detect changes in the `current_index` so that we know when we need to update the text label.

Now, if we run the scene, at the first step of the conversation we can press either the Up or Down arrow, and we’ll see the appropriate response. Afterwards, by pressing Enter, the conversation will continue sequentially.

There’s still one problem here we need to fix. If we take the “I like apples” path to index 1, we’ll end up moving forward through the “I don’t like apples” part too. We need a way to skip ahead from index 1 to index 3, where our two conversation branches come together again. Basically, our dialogue sections need destinations too.

First, add a destination to this piece 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>

```
{
  "dialogue": "You like apples? Me too!",
  "destination": 3
},
```

Next, let’s handle that when calculating our update to `current_index`.

<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"):
  current_index = conversation[current_index].get(
    "destination", current_index + 1
  )
```

This will check to see if our current conversation piece has a destination and use that to update `current_index`. If it doesn’t, then we just use `current_index + 1` as the default.

And that’s it! Now, when you run the scene and press Up to take the “I like apples” path, you’ll skip over the negative response and continue the conversation afterwards.

## [<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>](#making-defining-dialogue-choices-easier)Making defining dialogue choices easier

Before we continue on, there’s one more feature we can add that will make our lives a lot easier while editing conversations. At the moment, our destination values are all absolute indices within the array. This means that, if we wanted to insert an extra piece of dialogue at the beginning, we’d need to update _all_ the destinations afterwards.

What would be a lot nicer is if we could label our conversation sections in some way, and then just say “go to part 2 of the conversation”.

Sounds much more convenient. Let’s do it!

Let’s update our conversation to look 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>

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

We’ve replaced our destination integers with some string values instead. We’ve also added some label keys with values to match the destinations we want to use.

Next, we’re going to define a helper function for us to find the index of a dictionary with a given label:

<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 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)
```

We’ve added an `assert` here for when we can’t find a part of the conversation with a label. If we hit this, it means we’ve make a typo or forgotten to define a particular label and we need to go back and fix it!

Finally, let’s modify our `_process` function to use labels to calculate updates to current\_index.

<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 current_index < (conversation.size() - 1):
    var previous_index = current_index
    var destination = null
    
    if conversation[current_index].has("choices"):
      if Input.is_action_just_pressed("ui_up"):
        destination = conversation[current_index]["choices"][0]["destination"]
        
      if Input.is_action_just_pressed("ui_down"):
        destination = conversation[current_index]["choices"][1]["destination"]
      
    if Input.is_action_just_pressed("ui_accept"):
      destination = conversation[current_index].get("destination", false)
    
    if destination != null:
      if destination:
        current_index = get_index_of_label(destination)
      else:
        current_index += 1
    
    if current_index != previous_index:
      dialogue_node.text = conversation[current_index]["dialogue"]
```

Instead of setting the `current_index` directly, we now set a destination variable instead, and then calculate the `current_index` afterwards. We’ll still fall back to incrementing by one if no label is defined. This saves us from needing to label every single conversation piece.

Notice that we default `destination` to `null`, and then set it to `false` if we haven’t got a label as a destination. We need to distinguish between these two states because `null` means we shouldn’t progress through the conversation at all, while `false` means we should step forward one. Using `null` and `false` to mean two separate things isn’t ideal, but it works well enough for now so we’ll come back and clean it up later.

That’s it! Everything should work exactly as it did before, only now we don’t need to use absolute indices as a reference for each part of the 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>](#scripting-the-ui)Scripting the UI

Now that we can make choices, let’s hook up our UI so that we can select them from our dialogue box.

Let’s start by setting the text on our choice labels from our conversation data. First, let’s define a couple of new functions.

<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 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_text_labels():
  name_node.text = "ALEX"
  dialogue_node.text = conversation[current_index]["dialogue"]
  choice_a_node.text = get_current_choice(0).get("dialogue", "...")
  choice_b_node.text = get_current_choice(1).get("dialogue", "")
```

The `get_current_choice` function is a helper that let’s us safely fetch choices and return convenient defaults if none exist.

The `update_text_labels` wraps up the updates for all our RichTextLabels into one place. You can see here how we’re using the `get_current_choice` function here to set the text on our choice labels. When we haven’t defined any choices and our only option is to go forward, we default to using the string "..." for Choice A, and a blank string for Choice B.

We can now update our script to use `update_text_labels` wherever we need them to update.

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>

```
func _ready():
  update_text_labels()
```

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()
```

Now when we run the scene, we should see the Choice A and Choice B labels updating as we move through the 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>](#refining-the-ui-controls)Refining the UI controls

Great! Next thing we need to do is let the player actually select the choices available. At the moment, we’re using the Up and Down arrows to make the choice directly. Let’s change that so that Up and Down only change our selection, and the player needs to actually press Enter to make the choice. We’ll also hook up those Select A and Select B nodes so that we can show the player the selection.

Our node will now need to remember the current selection so that when we press Enter, it knows which path to take. So, to start off, let’s define a new state variable for our choice, and add code to change it when we press the Up and Down keys.

Add a `current_choice` variable under our existing `current_index` state variable.

<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 current_index = 0
var current_choice = 0
```

Change the code that runs when we push the Up and Down arrows, to change our current 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_up"):
        current_choice -= 1
        
      if Input.is_action_just_pressed("ui_down"):
        current_choice += 1
```

Finally, when we press Enter, we need to go to the destination selected by our choice. There’s a little bit of complexity here now, because we don’t always have a choice, or even a destination. Let’s list the different scenarios we have:

1. No `choices` or `destination`. Result: just go to next conversation piece (increment index)
2. No `choices`, but `destination` defined. Result: find the next index based on the destination label.
3. `choices` is defined, and current choice has `destination`. Result: find the next index based on the destination label of the current choice.
4. `choices` is defined, but current choice has no `destination`. Result: just go to next conversation piece (increment index)

You’ll notice that scenario 4) isn’t something we’ve encountered yet, but it follows on from the other scenarios. As the dialogue designer, you could potentially have one choice that just continues on the main path, and another that takes you onto a side tangent and then loops back.

Currently, when we press Enter, all we do is set the destination by fetching it from our current conversation piece:

<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>

```
destination = conversation[current_index].get("destination", false)
```

This covers scenario 1) and 2), but we still need to implement 3) and 4), where we have some choices defined. Add a conditional statement around our existing code so we can handling these scenarios.

<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 conversation[current_index].has("choices"):
    var choice = conversation[current_index]["choices"][current_choice]
    destination = choice.get("destination", false)
  else:
    destination = conversation[current_index].get("destination", false)
```

This should be pretty straightforward. If the "choices" key exists in our dictionary, fetch the "destination" for the current choice and assign it to destination. Otherwise, just execute our existing code.

That should be enough now to test out our code. Try running the scene again, and pressing Down before pressing Enter to see how the conversation will follow the second path.

## [<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>](#cleaning-up-our-_process-function)Cleaning up our `_process` function

Let’s make one more improvement before moving on. We can make our `_process` function a lot simpler by pulling the logic to calculate our next index out into a separate 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 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
```

Now, in `_process`, we only need to call this, and then check if the `current_index` is going to change.

<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"):
      current_index = get_next_index()
  
    if current_index != previous_index:
      update_text_labels()
```

All of the code to calculate the `destination` and the `current_index` can now be removed from `_process`.

* * *

### [<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>](#aside-design-notes)Aside: Design notes

We could make this function simpler if we decided not to allow a destination at top level of our data structure, and instead just require that a single choice is defined. For example, 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>

```
{
  "label": "apples_good",
  "dialogue": "You like apples? Me too!",
  "choices": [
    {
      "dialogue": "...",
      "destination": "part_2"
    }
  ]
},
```

instead of 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>

```
{
  "label": "apples_good",
  "dialogue": "You like apples? Me too!",
  "destination": "part_2"
},
```

However, this is a lot of additional boilerplate code just to say what the next piece of dialogue is. If we were using this tool across a whole project, it’d get pretty annoying needing to write out a choice array with a single item every time. In this case, we have the opportunity to keep our data structure simple by handling the complexity in the internals of our class. We only have to do this _once_ and then we benefit from then on, so it’s a good payoff!

* * *

There’s still a couple of pretty obvious issues that we should fix:

1. There’s nothing to indicate to the player which option they’ve chosen.
2. There’s nothing stopping the player from trying to choose options other than the ones defined and causing an error when trying to fetch an unknown index from the choices array. We need to bound our choices to just those defined in our conversation.

Let’s start with the first issue. You’ll remember that when we first set up the scene we included a couple of extra nodes (SelectA and SelectB) intended to be toggled to show the current selection. Well, it’s time to make our script aware of those nodes! Let’s add them at the top of our script, under the nodes we’ve already defined:

<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 name_node = get_node("DialogueRect/CharacterName")
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")
```

Now, when we have our first choice selected, we want to show SelectA and hide SelectB. When we have the second choice selected, vice versa we want SelectA hidden and SelectB shown.

Add this to our `_process` function, just under the part where we update our `current_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 current_choice == 0:
      select_a_node.visible = true
      select_b_node.visible = false
    elif current_choice == 1:
      select_a_node.visible = false
      select_b_node.visible = true
```

This isn’t very generic (it only caters to having one or two options), but it’s enough for us to use for now.

If you run the scene, you’ll now see that pressing Down will appear to move the selection indicator down, and pressing Up will appear to move it back up again.

For this example, I’ve chosen to use two separate nodes to be shown/hidden to represent the two choices available, but your project might have different needs. You might want more than two choices, or you might want a completely different way to show the current selection. We can take a step in the right direction by pulling this logic out into a separate function which is solely responsible for displaying the current selection.

<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_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
```

This implementation works for my example, but you can change it to fit whatever your needs.

We also need to call the function when we update our current 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_up"):
      current_choice -= 1
      update_select_indicators()
      
    if Input.is_action_just_pressed("ui_down"):
      current_choice += 1
      update_select_indicators()
```

We can now remove our `if current_choice == 0:...` code block.

Finally, now that we only call this update after an arrow key is pressed, we also need to call this at the start of the conversation. Otherwise, both select indicators will be showing initially. Update the `_ready` function so that we do an initial update of the select indicators.

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

Try running the scene again to make sure everything is still working.

* * *

Ok, let’s fix our second issue now. At the moment, you might have noticed that if you press Up and Down multiple times, `current_choice` keeps incrementing or decrementing past the valid options of 0 or 1. Let’s define a few functions to help us keep our `current_choice` valid.

<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 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()
```

If we use the `safe_` functions, there’s no way we can move past the first or last valid choice index. Notice in `get_current_choice_count` that we default to 1 if there are no choices defined. This is because we always have at least one valid choice (to move forward in the conversation).

We also call `update_select_indicators()` inside these functions. We want this to happen whenever `current_choice` gets updated, and this means we don’t need to remember to call it from within `_process`.

All that’s left is to use these in our `_process` 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>

```
  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()
```

Try out the scene again. You’ll notice now that, no matter how many times you press Up or Down, it only requires a single press in the opposite direction to change your selection.

You might now have noticed there’s one final thing to fix. If we choose the second option at the first stage, we’ll end up with a blank second option selected at the next one. We need to reset our choice whenever we progress so we don’t end up with an invalid selection.

Let’s define a function for that as well, again also calling `update_select_indicators` since `current_choice` has changed.

<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 reset_selection():
  current_choice = 0
  update_select_indicators()
```

And at the end of `_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()
      reset_selection()
```

And that’s it! Try out the scene a few times, taking different paths. You should be able to make choices and follow each branch of the conversation through to the end.

## [<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

Here’s the final script for reference:

<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

onready var name_node = get_node("DialogueRect/CharacterName")
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")

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

var current_index = 0
var current_choice = 0

func _ready():
  update_text_labels()
  update_select_indicators()

func _process(delta):
  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:
      update_text_labels()
      reset_selection()
  
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_text_labels():
  name_node.text = "ALEX"
  dialogue_node.text = conversation[current_index]["dialogue"]
  choice_a_node.text = get_current_choice(0).get("dialogue", "...")
  choice_b_node.text = get_current_choice(1).get("dialogue", "")
  
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()
```

And that's the end of Part 1!

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

* * *

[Continue to Part 2](https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-2-imo), where we look at adding multiple characters, text animation, and how to insert variables.