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

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

## [<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/2 Recap](#part-12-recap)
2. [Introduction](#introduction)
3. [Triggering conversation events and scrolling text](#triggering-conversation-events-and-scrolling-text)

4. [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-12-recap)Part 1/2 Recap

In [Part 1](https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-1-3jb9), we set up our project and scripted a basic conversation with branching options.

In [Part 2](https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-2-imo), we added character swapping, text formatting using BBCode, and handling variables within 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>](#introduction)Introduction

This part is going to focus on extending our script with a couple of extra enhancements: triggering arbitrary events during the conversation, and also handling longer sections of dialogue by scrolling the text.

Afterwards, we're also going to look at how to integrate our conversation node into a larger game scene.

## [<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>](#triggering-conversation-events-and-scrolling-text)Triggering conversation events and scrolling text

There are two features we're going to implement in this section:

1. The ability to call arbitrary functions at any point during a conversation.
2. Allow longer sections of text, and scroll the dialogue box when overflow occurs.

What I mean by the first point is 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>

```
"dialogue": "Hi. Um...<pause 2.0> who are you?"
```

The section between the angle brackets `<>` is a function call. It won't print out, but it will call a `pause` function that stops text printing for 2.0 seconds.

You could use the same concept to, for example, trigger an animation. 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>

```
"dialogue": "Let's go!<move player left 10.0>"
```

The second point, scrolling the dialogue, may sound trivial, but it's deceptively complex. The `RichTextLabel` node doesn't give us good information about whether or not line-wrapping is occurring, how many lines our text actually uses, whether overflow is occurring, etc.

To give a brief overview of a couple of useful functions the RichTextLabel node _does_ give us:

1. [`get_total_character_count()`](https://docs.godotengine.org/en/stable/classes/class_richtextlabel.html#class-richtextlabel-method-get-total-character-count) - Gives us the total number of characters in the text. **Does not include BBCodes.**
2. [`scroll_to_line(int line)`](https://docs.godotengine.org/en/stable/classes/class_richtextlabel.html#class-richtextlabel-method-scroll-to-line) - Allows us to scroll the top of the window to match `line`. **Wrapped text counts as one line.**

In a nutshell, our strategy for correctly scrolling our text is going to be:

1. Define a constant `characters_per_line` variable. For me, this is 21. **This only works if you're using a monospaced font (which we are). Otherwise, your characters per line will be variable and you won't be able to use this method.**
2. Define a constant `max_visible_lines` variable. For me, this is two. If we go over this, it means we need to start scrolling.
3. Detect where in the dialogue a word will take a line over `characters_per_line` and then insert a newline (`\n`) appropriately (ensuring `scroll_to_line` will work for us)
4. Using the same principle as the inline function calls, insert a `<scroll>` after these newlines where vertical overflow will occur. Our `scroll` method will use `scroll_to_line` to increment the scroll position by one.

So, both of these are going to depend on us being able to call functions during a piece of dialogue. Let's modify our first piece of dialogue in the conversation so that it uses both of these features.

<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": "Hi, I'm ALEX. <pause 0.5>.<pause 0.5>.<pause 0.5>. [color=yellow]It's nice to meet you![/color] Uh...<pause 2.0> How should I refer to you?",
```

You can see that I've included several calls to a `pause` function which we'll need to define. I've also included a bbcode tag to ensure the combination doesn't interfere. I've also made the line longer so that it will need to wrap and scroll.

First, let's figure out how we can interpret these inline function call tags within the `<>` brackets.

For the string parsing, we're going to use a [Regular Expression](https://www.regular-expressions.info/) (GDScript class [RegEx](https://docs.godotengine.org/en/stable/classes/class_regex.html)).

Define a new 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 inline_function_regex = RegEx.new()
```

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

```
inline_function_regex.compile("<(?<function_call>.+?)>")
```

Our regex string, `<(?<function_call>.+?)>`, uses a Named Capture Group to extract everything between `<>` brackets. You'll see how we'll use it in a moment.

Note: there's no real reason why you can't use something other than `<>` as an identifier - it's just convenient because it doesn't clash with the format brackets `{}` or the bbcode brackets `[]`.

* * *

Tip: [https://regex101.com/](https://dev.to/scottw/-regex-101-ei9-temp-slug-1258619) is a great tool for learning how a particular expression works. I'm by no means an expert at RegEx and I used this tool to come up with the expression used here.

* * *

The idea is that we'll pre-process our string, save the function calls we need to make, remembering the index we need to call them at, and then remove them from the string.

To store the function calls, let's define a 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 dialogue_calls = {}
```

Define a helper function which we'll use for adding function calls to our dictionary in the required 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>

```
func add_dialogue_call(index, dialogue_call):
  var dialogue_calls_at_index = dialogue_calls.get(index, [])
  dialogue_calls_at_index.push_back(dialogue_call) 
  dialogue_calls[index] = dialogue_calls_at_index
```

We store the functions by their index for easy lookup when we're printing out the text. It's possible to have multiple function calls at the same location, so each value is actually an array of function calls that we'll need to iterate through.

We expect `dialogue_call` to be an array of strings containing each argument between the `<>` brackets (you'll see why in a minute).

For example, if we had dialogue 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>

```
"Hi <pause 2.0><animate player talk>, <animate player wave>"
```

we'd expect this in our `dialogue_calls` dictionary,

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

```
{
  3: [
    ["pause", "2.0"],
    ["animate", "player", "talk"]
  ],
  5: [
    ["animate", "player", "wave"]
  ]
}
```

Next, define a new function, `process_dialogue_inline_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 process_dialogue_inline_functions(dialogue):
  # Clear our existing function call list
  dialogue_calls = {}
  
  var visible_character_count = 0
  var in_bbcode = false

  var i = -1
  while i + 1 < dialogue.length():
    i += 1
    
    var character = dialogue[i]
    
    # Ignore bbcode tag sections
    if character == '[':
      in_bbcode = true
      continue
    elif character == ']':
      in_bbcode = false
      continue
    elif in_bbcode:
      continue
    
    # If this is the start of an inline function call, process it and strip it from the dialogue
    if character == '<':
      var result = inline_function_regex.search(dialogue, i)
      if result:
        add_dialogue_call(visible_character_count, result.get_string("function_call").split(" "))
        dialogue.erase(result.get_start(), result.get_end() - result.get_start())
        i -= 1
    else:
      visible_character_count += 1

  return dialogue
```

Now, this function might look a little complex, but there's really only a couple of things it's doing.

It iterates through our dialogue string, character by character, ignoring any bbcode tags, and checks for our special character `'<'`.

When it finds a `'<'` it uses the regex to search from the current position, extracts the `function_call` capture group, splits it into an array, and then passes it to `add_dialogue_call`. Having saved that function, it then erases it from the dialogue string.

While iterating through the dialogue, we keep track of the number of visible characters since that's what we iterate through when printing. It gives us the correct index at which to perform the function.

Finally, we return the modified `dialogue` with the function calls removed.

Also note that we use a `while` loop instead of a `for` loop. This is because the length of the dialogue can change during the loop (from the call to `erase`) and also we need to modify `i` when erasing a function call. It's therefore simpler to use a `while` loop and manage the iterator variable `i` ourselves.

Finally, we need to call this function at the start of `print_dialogue`. We want to do it after we've formatted the dialogue, but before we assign it to `bbcode_text`.

In `print_dialogue`, replace the line where we assign `bbcode_text` 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>

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

Our inline functions won't be getting called yet, but you can still test this out from here.

If you run the scene and start the conversation, you should see that the function calls have been removed from the dialogue.

Next, let's look at how we can actually call these functions while the dialogue is printing.

Let's first define our `pause` 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 pause(delay_string):
  var delay = float(delay_string)
  yield(get_tree().create_timer(delay), "timeout")
```

Pretty straightforward. All our arguments for these functions are going to be passed in as strings, so they'll need to be cast first. So, we cast the string to a float, then we create a one-shot timer and `yield`.

Now let's call our functions from `print_dialogue`.

Add this function to help us do that,

<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 call_dialogue_functions(index):
  var dialogue_calls_for_index = dialogue_calls.get(index)
  var results = []
  if dialogue_calls_for_index:
    for dialogue_call in dialogue_calls_for_index:
      var call_method = dialogue_call[0]
      dialogue_call.remove(0)

      results.push_back(callv(call_method, dialogue_call))
    
  return results
```

For the given index, get the array of dialogue calls to make, then iterate through, returning the results of each as an array. The first element of the array is the method name, and the remainder are the arguments.

Call the function from `print_dialogue`, before we start the timer or increment `visible_characters`,

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

```
for i in dialogue_node.get_total_character_count():
  # Process any function calls first, before showing the next character.
  var results = call_dialogue_functions(i)
  if results:
    for result in results:
      # This is what the function will return if it yields.
      # If it does yield, we also need to yield here to wait until that coroutine completes.
      if result is GDScriptFunctionState:
          yield(result, "completed")
```

In addition to calling `call_dialogue_functions` we also process the results here. In particular, we need to handle the case of when these functions `yield`. If they do, then we need to `yield` here too or else the `pause` will have no effect - it will run in a separate coroutine and not block our printing function.

That should be it! Try it out. When you run the scene, you should see that the dialogue pauses printing for the specified duration at the locations where we inserted those `<pause>` commands.

* * *

Ok, let's talk about scrolling next. There's a couple of things we need to do here. First, _detect_ when we need to scroll. Second, _perform_ the scroll at the correct time.

As mentioned earlier, the `RichTextLabel` node isn't good at telling us when text overflows horizontally and wraps around. Therefore, we're going to implement _our own text wrapping_. Given we're using a font where characters have a fixed width, this is relatively simple. We can define a constant number of characters per line, and then insert newlines anywhere a word would take a line over that number. We'll want to be a little strategic about where we place those newlines as well, so that we don't break any words in half.

To do this, I'm going to expand our call to `.format` and define a new function, `format_dialogue`, that will handle both the `.format` and insertion of any required newlines.

First, there's a couple of variables we'll 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>

```
export var characters_per_line = 21
export var max_lines_visible = 2
```

This is based on my own layout - yours might be different. Basically, this defines the size of the area we have that can display dialogue. Mine is 21 characters wide and 2 lines high.

_Note, you might need to increase the width of your dialogue box for this section. We want to actively avoiding lines wrapping by hitting the side of the dialogue box, and instead use our own newlines._

Next, define our new 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 format_dialogue(dialogue):
  # Replace any variables in {} brackets with their values
  var formatted_dialogue = dialogue.format(dialogue_variables())
  
  var characters_in_line_count = 0
  var line_count = 1
  var last_space_index = 0
  var ignore_stack = []
  # Everything between these brackets should be ignored.
  # It's formatted in a dictionary so we can easily fetch the corresponding close bracket for an open bracket.
  var ignore_bracket_pairs = { 
    "[": "]", 
    "<": ">" 
  }

  for i in formatted_dialogue.length():
    var character = formatted_dialogue[i]
    
    # Ignore everything between [] or <> brackets.
    # By using a stack, we can more easily support nested brackets, like [<>] or <[]>
    if character in ignore_bracket_pairs.keys():
      ignore_stack.push_back(character)
      continue
    elif character == ignore_bracket_pairs.get(ignore_stack.back()):
      ignore_stack.pop_back()
      continue
    elif not ignore_stack.empty():
      continue
    
    # Keep track of the last space we encounter. 
    # This will be where we want to insert a newline if the line overflows.
    if character == " ":
      last_space_index = i
      
    # If we've encountered a newline that's been manually inserted into the string, reset our counter
    if character == "\n":
      characters_in_line_count = 0
    elif characters_in_line_count > characters_per_line:
      # This character has caused on overflow and we need to insert a newline.
      # Insert it at the position of the last space so that we don't break up the work.
      formatted_dialogue[last_space_index] = "\n"
      # Since this word will now wrap, we'll be starting part way through the next line.
      # Our new character count is going to be the amount of characters between the current position
      # and the last space (where we inserted the newline)
      characters_in_line_count = i - last_space_index
    
    characters_in_line_count += 1
  
  return formatted_dialogue

func dialogue_variables():
  return {
    "title": title
  }
```

I've included a few comments in this function to give more specific explanations for some of the lines. The overview is, we're iterating through the dialogue string and counting the characters, ignoring bbcode tags `[]` and function call brackets `<>`. If the count goes over the maximum, we insert a newline at the start of the current word. If we encounter an existing newline (for example, if we've manually included one in our dialogue string), we reset our counter and start the new line.

I've also pulled `dialogue_variables()` out into a separate function so that it isn't buried inside `format_dialogue`.

Now all that's left to do is call our new function from `print_dialogue`. Replace the call to `.format` with this line,

<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 formatted_dialogue = format_dialogue(dialogue)
```

That's really all we need to do. Our dialogue should now have newlines inserted at appropriate places so we don't need to rely on the `RichTextLabel` to wrap our text any more.

The final thing we need to handle is scrolling. At the moment, if you play the scene, the dialogue will continue past two lines but the label won't scroll to keep up and the dialogue won't be visible.

As mentioned earlier, we're going to utilise our new inline function call feature to enable this by inserting a `"scroll"` function call at the same locations as our newlines.

First, we need to ensure that the scrollbar is disabled on our `Dialogue` node.

[![Screenshot of Dialogue node settings with Scroll Active disabled](https://www.alexpech.com/static/23bb6eeb4ad50f4296c63adc1ede2052/3cf3e/22a-disable-scroll.png "Screenshot of Dialogue node settings with Scroll Active disabled")](/static/23bb6eeb4ad50f4296c63adc1ede2052/3cf3e/22a-disable-scroll.png)

Now, let's define our `scroll` function. We also need a variable to keep track of our current scroll position (since `RichTextLabel` doesn't give it to us).

<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_scroll_position = 0

func scroll():
  current_scroll_position += 1
  dialogue_node.scroll_to_line(current_scroll_position)
```

Next, we need to insert some calls to `scroll` into our `dialogue_calls` dictionary at appropriate positions. Modify `process_dialogue_inline_functions` so that it looks 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_dialogue_inline_functions(dialogue):
  # Clear our existing function call list
  dialogue_calls = {}
  
  var visible_character_count = 0
  var line_count = 1
  var in_bbcode = false

  var i = -1
  while i + 1 < dialogue.length():
    i += 1
    
    var character = dialogue[i]
    
    # Ignore bbcode tag sections
    if character == '[':
      in_bbcode = true
      continue
    elif character == ']':
      in_bbcode = false
      continue
    elif in_bbcode:
      continue
    
    # If this is the start of an inline function call, process it and strip it from the dialogue
    if character == '<':
      var result = inline_function_regex.search(dialogue, i)
      if result:
        add_dialogue_call(visible_character_count, result.get_string("function_call").split(" "))
        dialogue.erase(result.get_start(), result.get_end() - result.get_start())
        i -= 1
    # Perform manual scrolling.
    # If this is a newline and we're above the maximum number of visible lines, insert a 'scroll' function
    elif character == "\n":
      line_count += 1
      if line_count > max_lines_visible:
        add_dialogue_call(visible_character_count, ["scroll"])
    else:
      visible_character_count += 1

  return dialogue
```

You can see that we've added a `line_count` variable and included an extra `elif` clause for detecting newlines. The logic is fairly simple. If we detect a newline and the current line count is greater than the maximum number of lines we can show, insert a call to `scroll` at that location.

Ok, we're ready to try it out! If you run the scene and start the conversation, you should now see that the dialogue scrolls down to the next line when it reaches the end of the last visible line.

[![Screenshot of scene with dialogue scrolled to the end](https://www.alexpech.com/static/02922e7e41d80e4d4213b4a7f0176b98/8c557/22-scrolled-dialogue.png "Screenshot of scene with dialogue scrolled to the end")](/static/02922e7e41d80e4d4213b4a7f0176b98/e51a6/22-scrolled-dialogue.png)

You might notice one issue though, if you have muliple pieces of dialogue in a row that require scrolling, the scroll position won't reset.

It's an easy fix - all we need to do is reset the scroll position when we start printing some new dialogue. Near the start of `print_dialogue`, where we set `visible_characters` to 0, edit your code 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>

```
  dialogue_node.visible_characters = 0
  dialogue_node.scroll_to_line(0)
  current_scroll_position = 0
```

And that should be all we need to fix the issue.

### [<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>](#fixing-the-text-skipping-feature)Fixing the text skipping feature

Ok, you may have noticed _one_ more, much more serious issue. Hitting Enter to skip to the end of the text now has a couple of problems.

1. We skip right to the very last line, so some text won't be shown at all. This most likely isn't desirable for a player.
2. Skipping to the end will also skip any inline function calls that we include. This might be ok for things like pauses, but it could cause problems for things like animations. Characters and objects could end up in the complete wrong spot!

While it would be _possible_ to skip over a chunk of text and play out all the functions we need to immediately, it would be very complicated, and not something I really feel like doing given what would be a fairly small pay-off.

So, instead of fixing our now-broken feature, I'm going to solve the problem in a different way.

The problem statement is: **players who have read the text before (or who are just fast readers) want to move through the dialogue as quickly as possible.**

Notice I haven't defined the problem as 'skip the dialogue completely'. This would be more like skipping an entire cutscene. It might be something your game needs, but it's not what I was intending for this feature.

So, let's look at the problem statement. In particular, where I said "as quickly as possible". Well, we have a limitation with our current implementation that we really only want to go one character at a time to ensure we don't miss any function calls. So, let's say "as quickly as possible" means "one character per frame" for our purposes. This means that, when the player presses and holds Enter to skip, we just want to show one character per frame, rather than waiting for the text timer.

The first thing I'm going to do is rename the `skip_text_printing` variable to `fast_text_printing`, so that we declare the variable 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 fast_text_printing = false
```

At this point, you should also rename all the other references to this variable in the file.

Next, I'm going to replace the `skip_test_printing()` function with these two 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 start_fast_text_printing():
  fast_text_printing = true
  text_timer_node.emit_signal("timeout")
  
func stop_fast_text_printing():
  fast_text_printing = false
```

Instead of this being a single event that occurs when a button is pressed, it will be something that continues while the button is held down. That means we need both a 'start' and a 'stop' event.

Inside our `_process` function, change the code block that previously started with `if skip_text_printing:` 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>

```
if text_in_progress:
  if Input.is_action_just_pressed("ui_accept"):
    start_fast_text_printing()
  elif Input.is_action_just_released("ui_accept"):
    stop_fast_text_printing() 

  return
```

Now these functions will get called on the press and release of the Enter key.

Finally, we need to change the logic in our `print_dialogue` function. At the end of the loop where we start the timer and show the next character, replace the existing code 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>

```
if fast_text_printing:
  dialogue_node.visible_characters += 1
  yield(get_tree(),"idle_frame")
else:
  text_timer_node.start()
  dialogue_node.visible_characters += 1
  yield(text_timer_node, "timeout")
```

What we're doing here is, if we're doing the fast text printing, instead of waiting for the timer we just wait for the next frame. This is pretty much as fast as we can go if we're only going one character at a time.

That's it! We're ready to try it out now. If you run the scene and start the conversation, you should be able to speed up the text by holding down the Enter key!

* * *

You'll notice that the pauses still take effect, which would be kind of annoying as a player if your intention was to skip forward as quickly as possible. Let's look next at how we can selectively skip over some of our inline functions.

Within the loop inside `print_dialogue`, we're going to expand our inline function-handling code a bit.

<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 results = call_dialogue_functions(i)
if results:
  for result in results:
    # This is what the function will return if it yields.
    if result is GDScriptFunctionState:
      # If we're trying to skip quickly through the text, complete this function immediately
      if fast_text_printing:
        while result is GDScriptFunctionState and result.is_valid():
          result = result.resume()
      else:
        # Otherwise, if the function has yielded, we need to yield here also to wait until that coroutine completes.
        yield(result, "completed")
```

We now have a conditional statement here checking our `fast_text_printing` variable. If the value is true, instead of yielding we call `resume()` on it. We do this multiple times in case the function yields multiple times (shown in an example in the GDScript docs [https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript\_basics.html#coroutines-with-yield](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html#coroutines-with-yield)).

This should work with what we've currently got. Try running the conversation. You should be able to skip quickly through the pauses just like with the other text.

The only thing we want to be careful of is if we have function calls that we shouldn't skip. For example, an animation that you want to complete before continuing.

If we want to do this selectively, we need the results returned from `call_dialogue_functions` to have a little more information. Let's modify that function so that it also returns the _name_ of the function the result is for.

In `call_dialogue_functions`, where we're pushing the result onto our results array, change the line 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>

```
results.push_back([call_method, callv(call_method, dialogue_call)])
```

See how we're now including the name of the method being called.

Let's also define a list of functions that are safe for us to skip.

<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 skippable_functions = ["pause"]
```

Now, let's modify our code in `print_dialogue` one more time to handle this new 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 results = call_dialogue_functions(i)
if results:
  for result in results:
    if not result:
      continue
      
    var dialogue_function = result[0]
    var result_value = result[1]
      
    # This is what the function will return if it yields.
    if result_value is GDScriptFunctionState:
      # If we're trying to skip quickly through the text, complete this function immediately if we're allowed to do so.
      if fast_text_printing and dialogue_function in skippable_functions:
        while result_value is GDScriptFunctionState and result_value.is_valid():
          result_value = result_value.resume()
      else:
        # Otherwise, if the function has yielded, we need to yield here also to wait until that coroutine completes.
        yield(result_value, "completed")
```

That should now put the final touches on that feature. We can now move quickly through the text and still prevent certain important functions from being skipped over.

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

### [<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>](#integrating-conversations-into-your-game)Integrating conversations into your game

Up until now we've been solely focussed on our dialogue scene. We're going to take a bit of a detour here and start thinking about how we can fit this into a larger project.

Let's imagine that this conversation is part of a cutscene in a 2D platformer. I'm going to create a new scene and, using some of the asset packs I've downloaded, I'm going to build a little scene with a couple of characters.

Here are the asset packs I've used:

- [https://ansimuz.itch.io/warped-city](https://ansimuz.itch.io/warped-city)
- [https://ansimuz.itch.io/warped-city-2](https://ansimuz.itch.io/warped-city-2)

I'm not going to give you my exact scene layout, so just have fun with this and build something cool! All you'll need is a node to represent your player, and another to represent an NPC (who we'll be having the conversation with).

Keep in mind how the scene is going to look with our dialogue UI at the bottom. Ensure the scene will still look ok with the bottom 64 pixels covered.

Here's what I've come up with.

[![Screenshot of a scene for a platformer-style game made with free asset packs](https://www.alexpech.com/static/a3defe046b0acb6c5409d54e40cc2353/8c557/16-level-scene.png "Screenshot of a scene for a platformer-style game made with free asset packs")](/static/a3defe046b0acb6c5409d54e40cc2353/9a86a/16-level-scene.png)

What you'll want to do now is drag in our DialogueUI scene. Position it at (0,0) so that it lines up with the bottom of the screen.

[![Screenshot of demo scene with UI node added](https://www.alexpech.com/static/547c6165588f91f80c80aa57789dc731/bca35/17-level-with-ui.png "Screenshot of demo scene with UI node added")](/static/547c6165588f91f80c80aa57789dc731/bca35/17-level-with-ui.png)

And you can see the basic structure of my scene here.

[![Screenshot of editor showing node hierarchy in scene](https://www.alexpech.com/static/848d9b72870f41d2bd118a31729e1f42/cccdc/18-level-scene-tree.png "Screenshot of editor showing node hierarchy in scene")](/static/848d9b72870f41d2bd118a31729e1f42/cccdc/18-level-scene-tree.png)

Basically, I've got a background and foreground layer, which contain all the static sprites for the environment. Then, I've got two `AnimatedSprites` to represent my player and my NPC. Finally, our `DialogueUI` node (which I've just name `UI`) sits at the bottom.

Now, what I want to do is to be able to walk my character up to the NPC and, when I get close enough, trigger the conversation from a button press.

This tutorial isn't about 2D character control so I'm just going to write a really basic script.

Add a new script to your Player node and add this code.

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

export var walk_speed = 40

var in_conversation = false

func _process(delta):
  if in_conversation:
    return

  if Input.is_action_pressed("ui_right"):
    translate(Vector2(walk_speed * delta,0))
    play("walk")
    flip_h = false
  elif Input.is_action_pressed("ui_left"):
    translate(Vector2(-walk_speed * delta,0))
    play("walk")
    flip_h = true
  else:
    play("idle")
```

I defined an `"idle"` and a `"walk"` animation on my sprite, so I'm playing the `"walk"` animation when my character is moving, and `"idle"` otherwise. Feel free to remove these lines if you haven't bothered to animate your character.

I've also anticipated that we'll want to prevent player movement while a conversation is active, so I've set up an `in_conversation` variable in preparation for that.

Now, we can move our character to the left and right, but what we want is for our dialogue UI to be hidden initially, and then appear and start the conversation when we get close to the NPC. There's some tweaks we'll need to make to our DialogueUI script so that we can start conversations from a script.

Change the `_ready` 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 _ready():
  visible = false
  inline_function_regex.compile("<(?<function_call>.+?)>")
```

and add a new function, `start_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 start_conversation():
  current_index = 0
  current_choice = 0
  print_dialogue(conversation[current_index]["dialogue"])
  visible = true
```

This means that our conversation won't start automatically when we run our scene. Instead, it will wait for `start_conversation()` to get called. When `start_conversation()` is called, we set up the variables for our conversation, start the first piece of dialogue, and make our UI visible.

We need to add one more thing at the start 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>

```
func _process(delta):
  if not visible:
    return
```

This prevents button presses from advancing the conversation until we've actually started it.

You won't see much yet if you run the scene. The dialogue UI will be hidden, but there's nothing to trigger the start of the conversation.

Let's add some code to our player script, so that it can trigger 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>

```
export var ui_path: NodePath
onready var ui = get_node(ui_path)

func _process(delta):
  if not in_conversation:
    ui.start_conversation()
    in_conversation = true
```

Now, assign the `ui_path` variable to the UI node in the scene.

If you run the scene now, you should again see that the conversation starts immediately.

Now that we know our `start_conversation` function works you can remove the code we just added to the top of `_process`. We're not going to need it.

Let's add a script to our NPC. The NPC is going to let the player know when it's within range, so the player knows when it can start a conversation.

Here's my NPC 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>

```
extends AnimatedSprite

export var player_path: NodePath
onready var player = get_node(player_path)

export var conversation_distance = 30

func _ready():
  $SpeechBubble.visible = false

func _process(delta):
  if abs(position.x - player.position.x) < conversation_distance:
    $SpeechBubble.visible = true
    player.npc_in_range()
  else:
    $SpeechBubble.visible = false
```

Notice my NPC includes a node called `SpeechBubble`. I've added this as a visual indicator for when the player is able to talk to the NPC. Feel free to add this in as well if you like.

[![Screenshot of characters in scene with speech bubble appearing above head of NPC](https://www.alexpech.com/static/44c8f087c7a478897d51d1212179beef/55fc0/19-speech-bubble.png "Screenshot of characters in scene with speech bubble appearing above head of NPC")](/static/44c8f087c7a478897d51d1212179beef/55fc0/19-speech-bubble.png)

This script also has a reference to the player (don't forget to assign it in the scene view). If the distance between the NPC and the player is less than `conversation_distance`, the speech bubble indicator will show, and the NPC calls a function on the player, `npc_in_range`, to let it know that they can start a conversation.

Let's define `npc_in_range` on our player now.

<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 npc_in_range():
  if not in_conversation and Input.is_action_just_pressed("ui_accept"):
    in_conversation = true
    ui.start_conversation()
```

We'll use the Enter key (`"ui_accept"`) to trigger our conversation. A conversation can only be triggered if we aren't already in a conversation.

So, essentially this function is triggered when our NPC is within range of the player, and if they are, we'll start a conversation when Enter is pressed, as long as a conversation isn't already in progress.

Run the scene to test out this code. You should be able to walk up to the NPC and press Enter to start the conversation.

[![Screenshot of scene showing UI visible after conversation is triggered](https://www.alexpech.com/static/b4bf8496f3c55ae5c66f5ab062e86d77/8c557/20-triggering-conversation.png "Screenshot of scene showing UI visible after conversation is triggered")](/static/b4bf8496f3c55ae5c66f5ab062e86d77/f0685/20-triggering-conversation.png)

The next thing we need to consider is how to _end_ the conversation. At the moment, we just get to the final piece of dialogue and end up stuck there. Let's fix that.

To allow the player to get notified at the end of the conversation, we're going to use a [Custom signal](https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html#custom-signals).

Add this to your Dialogue UI 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>

```
signal conversation_finished  
```

Next, define a `finish_conversation` 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 finish_conversation():
  visible = false
  emit_signal("conversation_finished")
```

Finally, add an `else` clause at the end of the `_process` function to call it at the end of 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>

```
  else:
    if Input.is_action_just_pressed("ui_accept"):
      finish_conversation()
```

That `else` block should match up with `if current_index < (conversation.size() - 1)`. If we're inside that `else` block, it means we're at the end of the conversation.

If the player presses Enter when at the end of the conversation, we're going to call our new function `finish_conversation`, which hides the UI and emits our new signal.

Next, with the UI selected in the scene view, select to Signals tab and connect `finish_conversation` to the Player node.

[![Screenshot of editor window to connect signal to method on Player](https://www.alexpech.com/static/4d70d2101bf9943bf2197c7b6fcf9ff0/17602/21-connect-signal.png "Screenshot of editor window to connect signal to method on Player")](/static/4d70d2101bf9943bf2197c7b6fcf9ff0/17602/21-connect-signal.png)

All we need to do in our Player script is to set `in_conversation` to `false` when this signal is emitted.

<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 _on_UI_conversation_finished():
  in_conversation = false
```

That's it! Test out your scene again. You should be able to run through the whole conversation with the NPC and then continue playing. The UI should hide once you're done, and then you should be able to trigger the conversation again and repeat it.

## [<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 this section we looked at how to trigger events during a conversation, and how to incorporate our script into a larger scene.

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