<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex Pech</title>
    <description>The latest articles on DEV Community by Alex Pech (@alexpech12).</description>
    <link>https://dev.to/alexpech12</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F886364%2F7f96d82f-71f9-4e17-bb14-47a18917856a.jpeg</url>
      <title>DEV Community: Alex Pech</title>
      <link>https://dev.to/alexpech12</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexpech12"/>
    <language>en</language>
    <item>
      <title>Building a Retro Dialogue plugin for Godot, Part 3</title>
      <dc:creator>Alex Pech</dc:creator>
      <pubDate>Sat, 28 Jan 2023 11:22:00 +0000</pubDate>
      <link>https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-3-2pnf</link>
      <guid>https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-3-2pnf</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Part 1/2 Recap&lt;/li&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Triggering conversation events and scrolling text&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Conclusion&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Part 1/2 Recap
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-1-3jb9"&gt;Part 1&lt;/a&gt;, we set up our project and scripted a basic conversation with branching options.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-2-imo"&gt;Part 2&lt;/a&gt;, we added character swapping, text formatting using BBCode, and handling variables within the conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Afterwards, we're also going to look at how to integrate our conversation node into a larger game scene.&lt;/p&gt;

&lt;h2&gt;
  
  
  Triggering conversation events and scrolling text
&lt;/h2&gt;

&lt;p&gt;There are two features we're going to implement in this section:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The ability to call arbitrary functions at any point during a conversation.&lt;/li&gt;
&lt;li&gt;Allow longer sections of text, and scroll the dialogue box when overflow occurs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What I mean by the first point is something like this:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"dialogue": "Hi. Um...&amp;lt;pause 2.0&amp;gt; who are you?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The section between the angle brackets &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt; is a function call. It won't print out, but it will call a &lt;code&gt;pause&lt;/code&gt; function that stops text printing for 2.0 seconds.&lt;/p&gt;

&lt;p&gt;You could use the same concept to, for example, trigger an animation. Something like this:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"dialogue": "Let's go!&amp;lt;move player left 10.0&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second point, scrolling the dialogue, may sound trivial, but it's deceptively complex. The &lt;code&gt;RichTextLabel&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;To give a brief overview of a couple of useful functions the RichTextLabel node &lt;em&gt;does&lt;/em&gt; give us:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://docs.godotengine.org/en/stable/classes/class_richtextlabel.html#class-richtextlabel-method-get-total-character-count"&gt;&lt;code&gt;get_total_character_count()&lt;/code&gt;&lt;/a&gt; - Gives us the total number of characters in the text. &lt;strong&gt;Does not include BBCodes.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.godotengine.org/en/stable/classes/class_richtextlabel.html#class-richtextlabel-method-scroll-to-line"&gt;&lt;code&gt;scroll_to_line(int line)&lt;/code&gt;&lt;/a&gt; - Allows us to scroll the top of the window to match &lt;code&gt;line&lt;/code&gt;. &lt;strong&gt;Wrapped text counts as one line.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In a nutshell, our strategy for correctly scrolling our text is going to be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define a constant &lt;code&gt;characters_per_line&lt;/code&gt; variable. For me, this is 21. &lt;strong&gt;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.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Define a constant &lt;code&gt;max_visible_lines&lt;/code&gt; variable. For me, this is two. If we go over this, it means we need to start scrolling.&lt;/li&gt;
&lt;li&gt;Detect where in the dialogue a word will take a line over &lt;code&gt;characters_per_line&lt;/code&gt; and then insert a newline (&lt;code&gt;\n&lt;/code&gt;) appropriately (ensuring &lt;code&gt;scroll_to_line&lt;/code&gt; will work for us)&lt;/li&gt;
&lt;li&gt;Using the same principle as the inline function calls, insert a &lt;code&gt;&amp;lt;scroll&amp;gt;&lt;/code&gt; after these newlines where vertical overflow will occur. Our &lt;code&gt;scroll&lt;/code&gt; method will use &lt;code&gt;scroll_to_line&lt;/code&gt; to increment the scroll position by one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"dialogue": "Hi, I'm ALEX. &amp;lt;pause 0.5&amp;gt;.&amp;lt;pause 0.5&amp;gt;.&amp;lt;pause 0.5&amp;gt;. [color=yellow]It's nice to meet you![/color] Uh...&amp;lt;pause 2.0&amp;gt; How should I refer to you?",
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that I've included several calls to a &lt;code&gt;pause&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;First, let's figure out how we can interpret these inline function call tags within the &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt; brackets.&lt;/p&gt;

&lt;p&gt;For the string parsing, we're going to use a &lt;a href="https://www.regular-expressions.info/"&gt;Regular Expression&lt;/a&gt; (GDScript class &lt;a href="https://docs.godotengine.org/en/stable/classes/class_regex.html"&gt;RegEx&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Define a new variable,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var inline_function_regex = RegEx.new()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and in &lt;code&gt;_ready&lt;/code&gt;,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inline_function_regex.compile("&amp;lt;(?&amp;lt;function_call&amp;gt;.+?)&amp;gt;")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our regex string, &lt;code&gt;&amp;lt;(?&amp;lt;function_call&amp;gt;.+?)&amp;gt;&lt;/code&gt;, uses a Named Capture Group to extract everything between &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt; brackets. You'll see how we'll use it in a moment.&lt;/p&gt;

&lt;p&gt;Note: there's no real reason why you can't use something other than &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt; as an identifier - it's just convenient because it doesn't clash with the format brackets &lt;code&gt;{}&lt;/code&gt; or the bbcode brackets &lt;code&gt;[]&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;Tip: &lt;a href="https://dev.to/scottw/-regex-101-ei9-temp-slug-1258619"&gt;https://regex101.com/&lt;/a&gt; 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.&lt;/p&gt;




&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;To store the function calls, let's define a variable.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var dialogue_calls = {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define a helper function which we'll use for adding function calls to our dictionary in the required format.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;We expect &lt;code&gt;dialogue_call&lt;/code&gt; to be an array of strings containing each argument between the &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt; brackets (you'll see why in a minute).&lt;/p&gt;

&lt;p&gt;For example, if we had dialogue like this,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Hi &amp;lt;pause 2.0&amp;gt;&amp;lt;animate player talk&amp;gt;, &amp;lt;animate player wave&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we'd expect this in our &lt;code&gt;dialogue_calls&lt;/code&gt; dictionary,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  3: [
    ["pause", "2.0"],
    ["animate", "player", "talk"]
  ],
  5: [
    ["animate", "player", "wave"]
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, define a new function, &lt;code&gt;process_dialogue_inline_functions&lt;/code&gt;,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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 &amp;lt; 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 == '&amp;lt;':
      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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, this function might look a little complex, but there's really only a couple of things it's doing.&lt;/p&gt;

&lt;p&gt;It iterates through our dialogue string, character by character, ignoring any bbcode tags, and checks for our special character &lt;code&gt;'&amp;lt;'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When it finds a &lt;code&gt;'&amp;lt;'&lt;/code&gt; it uses the regex to search from the current position, extracts the &lt;code&gt;function_call&lt;/code&gt; capture group, splits it into an array, and then passes it to &lt;code&gt;add_dialogue_call&lt;/code&gt;. Having saved that function, it then erases it from the dialogue string.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Finally, we return the modified &lt;code&gt;dialogue&lt;/code&gt; with the function calls removed.&lt;/p&gt;

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

&lt;p&gt;Finally, we need to call this function at the start of &lt;code&gt;print_dialogue&lt;/code&gt;. We want to do it after we've formatted the dialogue, but before we assign it to &lt;code&gt;bbcode_text&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;print_dialogue&lt;/code&gt;, replace the line where we assign &lt;code&gt;bbcode_text&lt;/code&gt; with this:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var formatted_dialogue = dialogue.format({ "title": title })
dialogue_node.bbcode_text = process_dialogue_inline_functions(formatted_dialogue)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our inline functions won't be getting called yet, but you can still test this out from here.&lt;/p&gt;

&lt;p&gt;If you run the scene and start the conversation, you should see that the function calls have been removed from the dialogue.&lt;/p&gt;

&lt;p&gt;Next, let's look at how we can actually call these functions while the dialogue is printing.&lt;/p&gt;

&lt;p&gt;Let's first define our &lt;code&gt;pause&lt;/code&gt; function.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func pause(delay_string):
  var delay = float(delay_string)
  yield(get_tree().create_timer(delay), "timeout")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;yield&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now let's call our functions from &lt;code&gt;print_dialogue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Add this function to help us do that,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Call the function from &lt;code&gt;print_dialogue&lt;/code&gt;, before we start the timer or increment &lt;code&gt;visible_characters&lt;/code&gt;,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;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 &lt;code&gt;&amp;lt;pause&amp;gt;&lt;/code&gt; commands.&lt;/p&gt;




&lt;p&gt;Ok, let's talk about scrolling next. There's a couple of things we need to do here. First, &lt;em&gt;detect&lt;/em&gt; when we need to scroll. Second, &lt;em&gt;perform&lt;/em&gt; the scroll at the correct time.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, the &lt;code&gt;RichTextLabel&lt;/code&gt; node isn't good at telling us when text overflows horizontally and wraps around. Therefore, we're going to implement &lt;em&gt;our own text wrapping&lt;/em&gt;. 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.&lt;/p&gt;

&lt;p&gt;To do this, I'm going to expand our call to &lt;code&gt;.format&lt;/code&gt; and define a new function, &lt;code&gt;format_dialogue&lt;/code&gt;, that will handle both the &lt;code&gt;.format&lt;/code&gt; and insertion of any required newlines.&lt;/p&gt;

&lt;p&gt;First, there's a couple of variables we'll need:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export var characters_per_line = 21
export var max_lines_visible = 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;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.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next, define our new function,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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 = { 
    "[": "]", 
    "&amp;lt;": "&amp;gt;" 
  }

  for i in formatted_dialogue.length():
    var character = formatted_dialogue[i]

    # Ignore everything between [] or &amp;lt;&amp;gt; brackets.
    # By using a stack, we can more easily support nested brackets, like [&amp;lt;&amp;gt;] or &amp;lt;[]&amp;gt;
    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 &amp;gt; 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
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;[]&lt;/code&gt; and function call brackets &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;I've also pulled &lt;code&gt;dialogue_variables()&lt;/code&gt; out into a separate function so that it isn't buried inside &lt;code&gt;format_dialogue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now all that's left to do is call our new function from &lt;code&gt;print_dialogue&lt;/code&gt;. Replace the call to &lt;code&gt;.format&lt;/code&gt; with this line,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var formatted_dialogue = format_dialogue(dialogue)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;RichTextLabel&lt;/code&gt; to wrap our text any more.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, we're going to utilise our new inline function call feature to enable this by inserting a &lt;code&gt;"scroll"&lt;/code&gt; function call at the same locations as our newlines.&lt;/p&gt;

&lt;p&gt;First, we need to ensure that the scrollbar is disabled on our &lt;code&gt;Dialogue&lt;/code&gt; node.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/23bb6eeb4ad50f4296c63adc1ede2052/3cf3e/22a-disable-scroll.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BS3Ti8bt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/23bb6eeb4ad50f4296c63adc1ede2052/3cf3e/22a-disable-scroll.png" alt="Screenshot of Dialogue node settings with Scroll Active disabled" title="Screenshot of Dialogue node settings with Scroll Active disabled" width="293" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's define our &lt;code&gt;scroll&lt;/code&gt; function. We also need a variable to keep track of our current scroll position (since &lt;code&gt;RichTextLabel&lt;/code&gt; doesn't give it to us).&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var current_scroll_position = 0

func scroll():
  current_scroll_position += 1
  dialogue_node.scroll_to_line(current_scroll_position)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to insert some calls to &lt;code&gt;scroll&lt;/code&gt; into our &lt;code&gt;dialogue_calls&lt;/code&gt; dictionary at appropriate positions. Modify &lt;code&gt;process_dialogue_inline_functions&lt;/code&gt; so that it looks like this,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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 &amp;lt; 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 == '&amp;lt;':
      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 &amp;gt; max_lines_visible:
        add_dialogue_call(visible_character_count, ["scroll"])
    else:
      visible_character_count += 1

  return dialogue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that we've added a &lt;code&gt;line_count&lt;/code&gt; variable and included an extra &lt;code&gt;elif&lt;/code&gt; 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 &lt;code&gt;scroll&lt;/code&gt; at that location.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/02922e7e41d80e4d4213b4a7f0176b98/e51a6/22-scrolled-dialogue.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DLDyGGpj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/02922e7e41d80e4d4213b4a7f0176b98/8c557/22-scrolled-dialogue.png" alt="Screenshot of scene with dialogue scrolled to the end" title="Screenshot of scene with dialogue scrolled to the end" width="700" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;print_dialogue&lt;/code&gt;, where we set &lt;code&gt;visible_characters&lt;/code&gt; to 0, edit your code to look like this,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  dialogue_node.visible_characters = 0
  dialogue_node.scroll_to_line(0)
  current_scroll_position = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that should be all we need to fix the issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing the text skipping feature
&lt;/h3&gt;

&lt;p&gt;Ok, you may have noticed &lt;em&gt;one&lt;/em&gt; more, much more serious issue. Hitting Enter to skip to the end of the text now has a couple of problems.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;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!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While it would be &lt;em&gt;possible&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;So, instead of fixing our now-broken feature, I'm going to solve the problem in a different way.&lt;/p&gt;

&lt;p&gt;The problem statement is: &lt;strong&gt;players who have read the text before (or who are just fast readers) want to move through the dialogue as quickly as possible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The first thing I'm going to do is rename the &lt;code&gt;skip_text_printing&lt;/code&gt; variable to &lt;code&gt;fast_text_printing&lt;/code&gt;, so that we declare the variable like this.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var fast_text_printing = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you should also rename all the other references to this variable in the file.&lt;/p&gt;

&lt;p&gt;Next, I'm going to replace the &lt;code&gt;skip_test_printing()&lt;/code&gt; function with these two functions,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func start_fast_text_printing():
  fast_text_printing = true
  text_timer_node.emit_signal("timeout")

func stop_fast_text_printing():
  fast_text_printing = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Inside our &lt;code&gt;_process&lt;/code&gt; function, change the code block that previously started with &lt;code&gt;if skip_text_printing:&lt;/code&gt; to this,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now these functions will get called on the press and release of the Enter key.&lt;/p&gt;

&lt;p&gt;Finally, we need to change the logic in our &lt;code&gt;print_dialogue&lt;/code&gt; function. At the end of the loop where we start the timer and show the next character, replace the existing code with this,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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!&lt;/p&gt;




&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Within the loop inside &lt;code&gt;print_dialogue&lt;/code&gt;, we're going to expand our inline function-handling code a bit.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;If we want to do this selectively, we need the results returned from &lt;code&gt;call_dialogue_functions&lt;/code&gt; to have a little more information. Let's modify that function so that it also returns the &lt;em&gt;name&lt;/em&gt; of the function the result is for.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;call_dialogue_functions&lt;/code&gt;, where we're pushing the result onto our results array, change the line to this,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;results.push_back([call_method, callv(call_method, dialogue_call)])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See how we're now including the name of the method being called.&lt;/p&gt;

&lt;p&gt;Let's also define a list of functions that are safe for us to skip.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var skippable_functions = ["pause"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's modify our code in &lt;code&gt;print_dialogue&lt;/code&gt; one more time to handle this new data structure.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="///tutorials/building-a-retro-dialogue-plugin-for-godot/godot-demo/godot-retro-dialogue-part-3a.html"&gt;Try it out in your browser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/alexpech12/godot-retro-dialogue/tree/v0.3"&gt;View and download the project on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Integrating conversations into your game
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Here are the asset packs I've used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ansimuz.itch.io/warped-city"&gt;https://ansimuz.itch.io/warped-city&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ansimuz.itch.io/warped-city-2"&gt;https://ansimuz.itch.io/warped-city-2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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).&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Here's what I've come up with.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/a3defe046b0acb6c5409d54e40cc2353/9a86a/16-level-scene.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KVhZKSPq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/a3defe046b0acb6c5409d54e40cc2353/8c557/16-level-scene.png" alt="Screenshot of a scene for a platformer-style game made with free asset packs" title="Screenshot of a scene for a platformer-style game made with free asset packs" width="700" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/547c6165588f91f80c80aa57789dc731/bca35/17-level-with-ui.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BK_-kXhr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/547c6165588f91f80c80aa57789dc731/bca35/17-level-with-ui.png" alt="Screenshot of demo scene with UI node added" title="Screenshot of demo scene with UI node added" width="683" height="591"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And you can see the basic structure of my scene here.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/848d9b72870f41d2bd118a31729e1f42/cccdc/18-level-scene-tree.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yIs_drmD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/848d9b72870f41d2bd118a31729e1f42/cccdc/18-level-scene-tree.png" alt="Screenshot of editor showing node hierarchy in scene" title="Screenshot of editor showing node hierarchy in scene" width="221" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;This tutorial isn't about 2D character control so I'm just going to write a really basic script.&lt;/p&gt;

&lt;p&gt;Add a new script to your Player node and add this code.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;I've also anticipated that we'll want to prevent player movement while a conversation is active, so I've set up an &lt;code&gt;in_conversation&lt;/code&gt; variable in preparation for that.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Change the &lt;code&gt;_ready&lt;/code&gt; function to look like this,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func _ready():
  visible = false
  inline_function_regex.compile("&amp;lt;(?&amp;lt;function_call&amp;gt;.+?)&amp;gt;")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and add a new function, &lt;code&gt;start_conversation()&lt;/code&gt;,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func start_conversation():
  current_index = 0
  current_choice = 0
  print_dialogue(conversation[current_index]["dialogue"])
  visible = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;We need to add one more thing at the start of &lt;code&gt;_process&lt;/code&gt;.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func _process(delta):
  if not visible:
    return
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents button presses from advancing the conversation until we've actually started it.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Let's add some code to our player script, so that it can trigger the conversation.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, assign the &lt;code&gt;ui_path&lt;/code&gt; variable to the UI node in the scene.&lt;/p&gt;

&lt;p&gt;If you run the scene now, you should again see that the conversation starts immediately.&lt;/p&gt;

&lt;p&gt;Now that we know our &lt;code&gt;start_conversation&lt;/code&gt; function works you can remove the code we just added to the top of &lt;code&gt;_process&lt;/code&gt;. We're not going to need it.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Here's my NPC script,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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) &amp;lt; conversation_distance:
    $SpeechBubble.visible = true
    player.npc_in_range()
  else:
    $SpeechBubble.visible = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice my NPC includes a node called &lt;code&gt;SpeechBubble&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/44c8f087c7a478897d51d1212179beef/55fc0/19-speech-bubble.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uCxwE5wC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/44c8f087c7a478897d51d1212179beef/55fc0/19-speech-bubble.png" alt="Screenshot of characters in scene with speech bubble appearing above head of NPC" title="Screenshot of characters in scene with speech bubble appearing above head of NPC" width="433" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;conversation_distance&lt;/code&gt;, the speech bubble indicator will show, and the NPC calls a function on the player, &lt;code&gt;npc_in_range&lt;/code&gt;, to let it know that they can start a conversation.&lt;/p&gt;

&lt;p&gt;Let's define &lt;code&gt;npc_in_range&lt;/code&gt; on our player now.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func npc_in_range():
  if not in_conversation and Input.is_action_just_pressed("ui_accept"):
    in_conversation = true
    ui.start_conversation()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll use the Enter key (&lt;code&gt;"ui_accept"&lt;/code&gt;) to trigger our conversation. A conversation can only be triggered if we aren't already in a conversation.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/b4bf8496f3c55ae5c66f5ab062e86d77/f0685/20-triggering-conversation.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JxpZi3bg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/b4bf8496f3c55ae5c66f5ab062e86d77/8c557/20-triggering-conversation.png" alt="Screenshot of scene showing UI visible after conversation is triggered" title="Screenshot of scene showing UI visible after conversation is triggered" width="700" height="613"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next thing we need to consider is how to &lt;em&gt;end&lt;/em&gt; the conversation. At the moment, we just get to the final piece of dialogue and end up stuck there. Let's fix that.&lt;/p&gt;

&lt;p&gt;To allow the player to get notified at the end of the conversation, we're going to use a &lt;a href="https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html#custom-signals"&gt;Custom signal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Add this to your Dialogue UI script:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signal conversation_finished  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, define a &lt;code&gt;finish_conversation&lt;/code&gt; function.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func finish_conversation():
  visible = false
  emit_signal("conversation_finished")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, add an &lt;code&gt;else&lt;/code&gt; clause at the end of the &lt;code&gt;_process&lt;/code&gt; function to call it at the end of the conversation.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  else:
    if Input.is_action_just_pressed("ui_accept"):
      finish_conversation()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;else&lt;/code&gt; block should match up with &lt;code&gt;if current_index &amp;lt; (conversation.size() - 1)&lt;/code&gt;. If we're inside that &lt;code&gt;else&lt;/code&gt; block, it means we're at the end of the conversation.&lt;/p&gt;

&lt;p&gt;If the player presses Enter when at the end of the conversation, we're going to call our new function &lt;code&gt;finish_conversation&lt;/code&gt;, which hides the UI and emits our new signal.&lt;/p&gt;

&lt;p&gt;Next, with the UI selected in the scene view, select to Signals tab and connect &lt;code&gt;finish_conversation&lt;/code&gt; to the Player node.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/4d70d2101bf9943bf2197c7b6fcf9ff0/17602/21-connect-signal.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EBmC15BC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/4d70d2101bf9943bf2197c7b6fcf9ff0/17602/21-connect-signal.png" alt="Screenshot of editor window to connect signal to method on Player" title="Screenshot of editor window to connect signal to method on Player" width="597" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All we need to do in our Player script is to set &lt;code&gt;in_conversation&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; when this signal is emitted.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func _on_UI_conversation_finished():
  in_conversation = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this section we looked at how to trigger events during a conversation, and how to incorporate our script into a larger scene.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="///tutorials/building-a-retro-dialogue-plugin-for-godot/godot-demo/godot-retro-dialogue-part-3b.html"&gt;Try out the finished product in your browser here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/alexpech12/godot-retro-dialogue/tree/v0.3.1"&gt;View and download the project on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>godot</category>
      <category>tutorial</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Building a Retro Dialogue plugin for Godot, Part 2</title>
      <dc:creator>Alex Pech</dc:creator>
      <pubDate>Sat, 28 Jan 2023 11:21:00 +0000</pubDate>
      <link>https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-2-imo</link>
      <guid>https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-2-imo</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Part 1 Recap&lt;/li&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Swapping characters&lt;/li&gt;
&lt;li&gt;Animating the text, printing character by character&lt;/li&gt;
&lt;li&gt;Adding effects using bbcode&lt;/li&gt;
&lt;li&gt;Variables&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Part 1 Recap
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-1-3jb9"&gt;Part 1&lt;/a&gt;, we looked at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project setup for a low-res 'SNES-style' game&lt;/li&gt;
&lt;li&gt;UI layout for our dialogue system&lt;/li&gt;
&lt;li&gt;Scripting a basic, branching conversation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In Part 2, we're going to get a lot deeper into scripting.&lt;/p&gt;

&lt;p&gt;We'll be making the conversation more dynamic, adding the ability to swap characters and insert variables into the text.&lt;/p&gt;

&lt;p&gt;We're also going to add some animation and enhanced visuals to our text by printing out characters and using &lt;a href="https://en.wikipedia.org/wiki/BBCode"&gt;BBCode&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Swapping characters
&lt;/h2&gt;

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

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

&lt;p&gt;Let's get started by making our characters configurable on the node.&lt;/p&gt;

&lt;p&gt;Export a couple of variables for the character name and portrait.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export var character_name = "ALEX"
export var character_portrait: Texture
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to let our script know about the Portrait node.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;onready var portrait_node = get_node("PortraitRect/Portrait")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's set the name and portrait in our &lt;code&gt;_ready&lt;/code&gt; function.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;portrait_node.texture = character_portrait
name_node.text = character_name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;a href="///static/a153c4af6a52cdf542ce30ae9516fd6b/2b727/7-change-character.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qMdphWqr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/a153c4af6a52cdf542ce30ae9516fd6b/2b727/7-change-character.png" alt="Screenshot of editor showing custom name and portrait set in inspector" title="Screenshot of editor showing custom name and portrait set in inspector" width="368" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run the scene. Easy as that, a configurable character name and portrait!&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/7cdd90b37cc5d6af1eedca4bff6a0a9e/01dae/7-new-character-in-scene.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GSa6XXHD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/7cdd90b37cc5d6af1eedca4bff6a0a9e/8c557/7-new-character-in-scene.png" alt="Screenshot of running scene showing configured character name and portrait" title="Screenshot of running scene showing configured character name and portrait" width="700" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

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



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

&lt;/div&gt;



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

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export var characters = {
  "alex": {
    "name": "ALEX",
    "portrait": preload("res://images/Pixel Portraits/female_10_t.png")
  },
  "jupiter": {
    "name": "JUPITER",
    "portrait": preload("res://images/Pixel Portraits/female_11_t.png")
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should now look like this in the editor:&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/3ddabd11d5c39b17dcc7196f12689abe/f0991/8-expanded-character-inspector.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SipBUHHF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/3ddabd11d5c39b17dcc7196f12689abe/f0991/8-expanded-character-inspector.png" alt="Screenshot of editor showing new exported character variables" title="Screenshot of editor showing new exported character variables" width="378" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func update_character():
  var current_character = conversation[current_index].get("character")

  portrait_node.texture = characters[current_character]["portrait"]
  name_node.text = characters[current_character]["name"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let's call this function whenever the conversation progresses.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;_ready&lt;/code&gt;,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func _ready():
  update_text_labels()
  update_select_indicators()
  update_character()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and in &lt;code&gt;_process&lt;/code&gt;,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if current_index != previous_index:
  update_text_labels()
  update_character()
  reset_selection()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;a href="///static/7bfc5790845890f9b15153cc95bf1c41/d2a60/9-multi-character-convo-1.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xCUoZZuG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/7bfc5790845890f9b15153cc95bf1c41/8c557/9-multi-character-convo-1.png" alt="Screenshot of scene running new character code" title="Screenshot of scene running new character code" width="700" height="180"&gt;&lt;/a&gt;&lt;a href="///static/fc58adaa40cb426828bbf8bd0ad04a05/d2a60/9-multi-character-convo-2.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ANP1liy7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/fc58adaa40cb426828bbf8bd0ad04a05/8c557/9-multi-character-convo-2.png" alt="Screenshot of running scene showing second character in conversation" title="Screenshot of running scene showing second character in conversation" width="700" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Animating the text, printing character by character
&lt;/h2&gt;

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

&lt;ol&gt;
&lt;li&gt;When &lt;code&gt;text_in_progress&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;, we're in the process of printing out text. Our current choices should be hidden.&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;text_in_progress&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt;, our full text is displayed and the current choices are shown.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We progress from 1) to 2) automatically when the text is complete.&lt;/p&gt;

&lt;p&gt;We progress from 2) back to 1) (incrementing the dialogue index) when the player makes a choice.&lt;/p&gt;

&lt;p&gt;To control the printing animation of the text, we're going to use a Timer node.&lt;/p&gt;

&lt;p&gt;I've based this section on an existing example here &lt;a href="https://www.codegrepper.com/code-examples/go/TypeWriter+Text+Godot"&gt;https://www.codegrepper.com/code-examples/go/TypeWriter+Text+Godot&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a &lt;code&gt;Timer&lt;/code&gt; node to scene, called &lt;code&gt;TextTimer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/a11e63096b7f86b140f70375a93790ee/008e2/10-text-timer.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xi5w3gfk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/a11e63096b7f86b140f70375a93790ee/008e2/10-text-timer.png" alt="Screenshot of scene hierarchy with TextTimer node added" title="Screenshot of scene hierarchy with TextTimer node added" width="237" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Text speed can be modified by changing the Wait time of the Timer.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/06caff410f4af19330c57586abf7374f/50ac3/10-text-timer-2.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mjAcBgQ7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/06caff410f4af19330c57586abf7374f/50ac3/10-text-timer-2.png" alt="Screenshot of Timer settings with Wait Time set to 0.1 seconds" title="Screenshot of Timer settings with Wait Time set to 0.1 seconds" width="356" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make the timer node available in our script.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;onready var text_timer_node = get_node("TextTimer")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add in a variable to represent the new state we need.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var text_in_progress = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func show_choices():
  set_choices_visible(true)
  reset_selection()
  choice_a_node.text = get_current_choice(0).get("dialogue", "...")
  choice_b_node.text = get_current_choice(1).get("dialogue", "")

func hide_choices():
  set_choices_visible(false)

func set_choices_visible(visible):
  var nodes = [
    select_a_node,
    select_b_node,
    choice_a_node,
    choice_b_node
  ]
  for node in nodes:
    node.visible = visible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add this function. It will handle the printing of the dialogue one character at a time.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func print_dialogue( dialogue ):
  text_in_progress = true
  update_character()
  hide_choices()
  dialogue_node.text = ""

  for letter in dialogue:
    text_timer_node.start()
    dialogue_node.add_text(letter)
    yield(text_timer_node, "timeout")

  show_choices()
  text_in_progress = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;In &lt;code&gt;_process&lt;/code&gt;, start with,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if text_in_progress:
  return
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents user action during the printing state.&lt;/p&gt;

&lt;p&gt;To transition to the next piece of dialogue, we now simply call &lt;code&gt;print_dialogue&lt;/code&gt; after a choice is made. Like this,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if current_index != previous_index:
  print_dialogue(conversation[current_index]["dialogue"])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the required updates are now handled with &lt;code&gt;print_dialogue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, in &lt;code&gt;_ready&lt;/code&gt;, we just need to kick off the initial bit of dialogue,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func _ready():
  print_dialogue(conversation[current_index]["dialogue"])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We no longer need the &lt;code&gt;update_text_labels&lt;/code&gt; function. It can be removed.&lt;/p&gt;

&lt;p&gt;You can now run the scene!&lt;/p&gt;

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

&lt;p&gt;Define a boolean variable to flag when we should skip the rest of the printing of the text:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var skip_text_printing = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During &lt;code&gt;_process&lt;/code&gt;, if we're currently printing out text and Enter is pressed, then skip the text printing:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func _process(delta):
  if text_in_progress:
    if Input.is_action_just_pressed("ui_accept"):
        skip_text_printing()

    return
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define &lt;code&gt;skip_text_printing()&lt;/code&gt;. This is going to set our variable and stop our text timer.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func skip_text_printing():
  skip_text_printing = true
  text_timer_node.emit_signal("timeout")
  text_timer_node.stop()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also force the timer to fire immediately, so that we don't wave to wait when using slow text speeds.&lt;/p&gt;

&lt;p&gt;Then, to skip the rest of the text inside &lt;code&gt;print_dialogue()&lt;/code&gt;, put this after the call to &lt;code&gt;yield&lt;/code&gt;,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if skip_text_printing:
  skip_text_printing = false
  dialogue_node.text = dialogue
  break
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will immediately complete the text and break out of our letter printing loop the next time our timer fires.&lt;/p&gt;

&lt;p&gt;Run the scene. You should be able to skip through the conversation by pressing the Enter key to skip the letter-by-letter printing.&lt;/p&gt;

&lt;p&gt;Here's where your script should be at:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extends Control

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

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

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

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

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

func _process(delta):
  if text_in_progress:
    if Input.is_action_just_pressed("ui_accept"):
      skip_text_printing = true
      text_timer_node.emit_signal("timeout")
      text_timer_node.stop()

    return

  if current_index &amp;lt; (conversation.size() - 1):
      var previous_index = current_index

      if Input.is_action_just_pressed("ui_up"):
        safe_select_previous_choice()

      if Input.is_action_just_pressed("ui_down"):
        safe_select_next_choice()

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

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

func get_index_of_label(label):
  for i in range(conversation.size()):
    if conversation[i].get("label") == label:
      return i

  assert(false, "Label %s does not exist in this conversation!" % label)

func get_next_index():
  var destination = null
  if conversation[current_index].has("choices"):
    var choice = conversation[current_index]["choices"][current_choice]
    destination = choice.get("destination")
  else:
    destination = conversation[current_index].get("destination")

  if destination:
    return get_index_of_label(destination)
  else:
    return current_index + 1

func get_current_choice(choice_index):
  var choices = conversation[current_index].get("choices", [])
  if choice_index &amp;lt; choices.size():
    return choices[choice_index]
  else:
    return {}

func update_select_indicators():
  var select_nodes = [
    select_a_node,
    select_b_node
  ]
  for node in select_nodes:
    node.visible = false

  select_nodes[current_choice].visible = true

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

func safe_select_previous_choice():
  current_choice = clamp(current_choice - 1, 0, get_current_choice_count() - 1)
  update_select_indicators()

func safe_select_next_choice():
  current_choice = clamp(current_choice + 1, 0, get_current_choice_count() - 1)
  update_select_indicators()

func reset_selection():
  current_choice = 0
  update_select_indicators()

func update_character():
  var current_character = conversation[current_index].get("character")

  portrait_node.texture = characters[current_character]["portrait"]
  name_node.text = characters[current_character]["name"]

func show_choices():
  set_choices_visible(true)
  reset_selection()
  choice_a_node.text = get_current_choice(0).get("dialogue", "...")
  choice_b_node.text = get_current_choice(1).get("dialogue", "")

func hide_choices():
  set_choices_visible(false)

func set_choices_visible(visible):
  var nodes = [
    select_a_node,
    select_b_node,
    choice_a_node,
    choice_b_node
  ]
  for node in nodes:
    node.visible = visible

func print_dialogue( dialogue ):
  text_in_progress = true
  update_character()
  hide_choices()
  dialogue_node.text = ""

  for letter in dialogue:
    text_timer_node.start()
    dialogue_node.add_text(letter)
    yield(text_timer_node, "timeout")

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

  show_choices()
  text_in_progress = false

func skip_text_printing():
  skip_text_printing = true
  text_timer_node.emit_signal("timeout")
  text_timer_node.stop()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding effects using bbcode
&lt;/h2&gt;

&lt;p&gt;What is BBCode? Basically, it allows as to apply formatting to our text by using special BBCode 'tags'. We can do things like &lt;strong&gt;bold&lt;/strong&gt; or &lt;u&gt;underline&lt;/u&gt; text. We can also set the color, and even add other special effects. The Godot docs for RichTextLabel have a lot of good examples for what's available: &lt;a href="https://docs.godotengine.org/en/stable/tutorials/ui/bbcode_in_richtextlabel.html"&gt;https://docs.godotengine.org/en/stable/tutorials/ui/bbcode_in_richtextlabel.html&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;a href="///static/1a8df394b9c289560477e804d9626840/39e45/11-enable-bbcode.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8310DeEg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/1a8df394b9c289560477e804d9626840/39e45/11-enable-bbcode.png" alt="Screenshot of RichTextLabel inspector with Bb Code enabled" title="Screenshot of RichTextLabel inspector with Bb Code enabled" width="347" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everywhere in the script we call &lt;code&gt;.text&lt;/code&gt; on one of our RichTextLabel nodes, we need to replace it with &lt;code&gt;.bbcode_text&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once you've done that, run the scene. Everything should still work as it did before.&lt;/p&gt;

&lt;p&gt;Now, let's try out some BBCode tags. Replace the first part of the conversation with this:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "character": "alex",
  "dialogue": "Hey there.\nDo you like [wave amp=10 freq=-10][color=green]apples[/color][/wave]?",
  "choices": [
    {
      "dialogue": "[wave amp=10 freq=10]Sure do![/wave]",
      "destination": "apples_good"
    },
    {
      "dialogue": "No way, [color=grey][shake rate=10 level=10]gross![/shake][/color]",
      "destination": "apples_bad"
    }
  ]
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="///static/486033d0e377c427cb691e88d46af997/394f7/13-bbcode-error.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s56tlAF3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/486033d0e377c427cb691e88d46af997/8c557/13-bbcode-error.png" alt="Screenshot showing BBCode tags within the printed out dialogue" title="Screenshot showing BBCode tags within the printed out dialogue" width="700" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Except... oh dear! Our BBCode tags are showing up in our dialogue!&lt;/p&gt;

&lt;p&gt;So, what's going on here? Well, recall that we're printing out the dialogue by adding the text character by character. Since our dialogue now needs to be interpreted as BBCode text, the &lt;code&gt;RichTextLabel&lt;/code&gt; needs to be aware of the &lt;em&gt;whole&lt;/em&gt; dialogue chunk for it to render it.&lt;/p&gt;

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

&lt;p&gt;Let's modify our &lt;code&gt;print_dialogue&lt;/code&gt; function to use &lt;code&gt;visible_characters&lt;/code&gt; instead,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func print_dialogue( dialogue ):
  text_in_progress = true
  update_character()
  hide_choices()

  dialogue_node.bbcode_text = dialogue
  dialogue_node.visible_characters = 0

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

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

  show_choices()
  text_in_progress = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

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

&lt;p&gt;Add this line just before our for loop:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yield(get_tree(),"idle_frame")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we're doing here is yielding until we receive the &lt;code&gt;"idle_frame"&lt;/code&gt; signal from the SceneTree, which is emitted right before &lt;code&gt;Node._process&lt;/code&gt;. Check out the Godot Docs for more info &lt;a href="https://docs.godotengine.org/en/stable/classes/class_scenetree.html#class-scenetree-signal-idle-frame"&gt;https://docs.godotengine.org/en/stable/classes/class_scenetree.html#class-scenetree-signal-idle-frame&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run the scene now, you should see that the BBCode text prints our correctly, character by character.&lt;/p&gt;

&lt;h2&gt;
  
  
  Variables
&lt;/h2&gt;

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

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

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

&lt;p&gt;To begin, let's modify our dialogue to define how we want to express our variables. I'm going to call mine &lt;code&gt;title&lt;/code&gt;, like this:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"dialogue": "Hey {title}.\nDo you like [wave amp=10 freq=-10][color=green]apples[/color][/wave]?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when we set the &lt;code&gt;bbcode_text&lt;/code&gt; property on the RichTextLabel, we first need to format our string.&lt;/p&gt;

&lt;p&gt;Modify the line where we set &lt;code&gt;bbcode_text&lt;/code&gt; in &lt;code&gt;print_dialogue&lt;/code&gt; to this:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dialogue_node.bbcode_text = dialogue.format({ "title": "STRANGER" })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;a href="///static/e76840e8b5f23a0be2abcc127e039528/c483d/14-insert-variable.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2F2pWqYb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/e76840e8b5f23a0be2abcc127e039528/8c557/14-insert-variable.png" alt="Screenshot of scene showing character referring to player as STRANGER" title="Screenshot of scene showing character referring to player as STRANGER" width="700" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, inserting variables is easy, but what about setting them?&lt;/p&gt;

&lt;p&gt;Let's say we want the character to start the conversation by asking the player how they should be referred to.&lt;/p&gt;

&lt;p&gt;Add this to the start of our conversation array.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "character": "alex",
  "dialogue": "Hi, I'm ALEX. How should I refer to you?",
  "choices": [
    {
      "dialogue": "Call me FRIEND"
    },
    {
      "dialogue": "It's CAPTAIN to you"
    }
  ]
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alex is now going to start by introducing themselves to you, and ask how they should refer to you.&lt;/p&gt;

&lt;p&gt;Obviously, this won't work yet. What we need is for each choice to modify the title variable that gets passed into our call to &lt;code&gt;format&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, we need a title variable, so let's define one:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var title = "STRANGER"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to use this in our call to &lt;code&gt;format&lt;/code&gt;:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dialogue_node.bbcode_text = dialogue.format({ "title": title })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now need to figure out how to actually set this variable when each of our options are chosen.&lt;/p&gt;

&lt;p&gt;For this, I'm going to introduce another option to our &lt;code&gt;"choices"&lt;/code&gt; dictionary called &lt;code&gt;"call"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's going to look something like this,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"choices": [
  {
    "dialogue": "Call me FRIEND",
    "call": ["set", "title", "FRIEND"]
  },
  {
    "dialogue": "It's CAPTAIN to you",
    "call": ["set", "title", "CAPTAIN"]
  }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Here's how we can handle this in the code. Add this function.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func execute_current_choice():
  var call_array = get_current_choice(current_choice).get("call")

  if call_array:
    var call_method = call_array[0]
    var call_args = call_array.slice(1, call_array.size())
    callv(call_method, call_args)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if Input.is_action_just_pressed("ui_accept"):
    execute_current_choice()
    current_index = get_next_index()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;p&gt;&lt;a href="///static/f262565e17b6ac21befbd4e863353e9f/9f21b/15-title-captain.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MWfnUhfN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.alexpech.com/static/f262565e17b6ac21befbd4e863353e9f/8c557/15-title-captain.png" alt="Screenshot of scene with dialogue with ALEX referring to the player as CAPTAIN" title="Screenshot of scene with dialogue with ALEX referring to the player as CAPTAIN" width="700" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In Part 2 we added several features to make our conversations more dynamic and visually interesting.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="///tutorials/building-a-retro-dialogue-plugin-for-godot/godot-demo/godot-retro-dialogue-part-2.html"&gt;Try it out in your browser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/alexpech12/godot-retro-dialogue/tree/v0.2"&gt;View and download the project on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-3-2pnf"&gt;Continue to Part 3&lt;/a&gt;, where we look at integrating the conversation into the context of a larger game, triggering events, and handling larger sections of dialogue.&lt;/p&gt;

</description>
      <category>godot</category>
      <category>tutorial</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Building a Retro Dialogue plugin for Godot, Part 1</title>
      <dc:creator>Alex Pech</dc:creator>
      <pubDate>Sat, 28 Jan 2023 11:20:00 +0000</pubDate>
      <link>https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-1-3jb9</link>
      <guid>https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-1-3jb9</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Project setup&lt;/li&gt;
&lt;li&gt;Defining dialogue via a script&lt;/li&gt;
&lt;li&gt;Introducing branching conversation options&lt;/li&gt;
&lt;li&gt;Making defining dialogue choices easier&lt;/li&gt;
&lt;li&gt;Scripting the UI&lt;/li&gt;
&lt;li&gt;Refining the UI controls&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cleaning up our &lt;code&gt;_process&lt;/code&gt; function&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Conclusion&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="/static/571251a39ce4987c097f486e1ac2e45a/21335/title-image.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F571251a39ce4987c097f486e1ac2e45a%2F8c557%2Ftitle-image.png" title="Don't worry, yours doesn't need to be about apples..." alt="Cyberpunk scene in SNES pixel art style with dialogue box and options" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Don't worry, yours doesn't need to be about apples...&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;This is Part 1, which covers,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The initial Godot scene setup, and any assets you'll need.&lt;/li&gt;
&lt;li&gt;Scripting a conversation with branching dialogue options&lt;/li&gt;
&lt;li&gt;Scripting UI controls to navigate the conversation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's an example of where we'll get to at the end of Part 1.&lt;/p&gt;

&lt;p&gt;&lt;a href="/static/c16b9044b5ba7e5fe9f16e0e5e1cbb35/20c85/title-image-part-1.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2Fc16b9044b5ba7e5fe9f16e0e5e1cbb35%2F8c557%2Ftitle-image-part-1.png" title="Screenshot of dialogue box showing character image and two dialogue options" alt="Screenshot of dialogue box showing character image and two dialogue options" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.alexpech.com/tutorials/building-a-retro-dialogue-plugin-for-godot/godot-demo/godot-retro-dialogue-part-1.html" rel="noopener noreferrer"&gt;Click here to play it in your browser&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is also a &lt;a href="https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-2-imo"&gt;Part 2&lt;/a&gt; and a &lt;a href="https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-3-2pnf"&gt;Part 3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Part 2 covers,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conversations with multiple characters&lt;/li&gt;
&lt;li&gt;Advanced text effects and animation&lt;/li&gt;
&lt;li&gt;Using variables to dynamically modify the dialogue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Part 3 covers,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Triggering events during conversation&lt;/li&gt;
&lt;li&gt;Handling large blocks of text&lt;/li&gt;
&lt;li&gt;Incorporating conversations into the context of a larger game&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To see where we'll end up at the end of Part 3, &lt;a href="https://www.alexpech.com/tutorials/building-a-retro-dialogue-plugin-for-godot/godot-demo/godot-retro-dialogue-part-1.html" rel="noopener noreferrer"&gt;click here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Part 4 is coming soon, and will cover how we can turn this into a reusable plugin for Godot.&lt;/p&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="/static/9e1c4b8531be0b5cce123b20116ac85e/2bef9/chrono-trigger-2.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F9e1c4b8531be0b5cce123b20116ac85e%2F8c557%2Fchrono-trigger-2.png" title="Screenshot from Chrono Trigger showing character dialogue box" alt="Screenshot from Chrono Trigger showing character dialogue box" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F0dc9513698636f8d7b06047da356a2c6%2F8c557%2Fchrono-trigger-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F0dc9513698636f8d7b06047da356a2c6%2F8c557%2Fchrono-trigger-1.png" title="Screenshot from Chrono Trigger showing dialogue selection" alt="Screenshot from Chrono Trigger showing dialogue selection" width="800" height="400"&gt;&lt;/a&gt;These screenshots from &lt;a href="https://en.wikipedia.org/wiki/Chrono_Trigger" rel="noopener noreferrer"&gt;Chrono Trigger (1995)&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F2015eef21d8b086a68260d69d7cb91cf%2F29d31%2Fducktales-2-nes.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F2015eef21d8b086a68260d69d7cb91cf%2F29d31%2Fducktales-2-nes.jpg" title="Screenshot from Ducktales 2 (1993, NES) showing dialogue window with character portraits" alt="Screenshot from Ducktales 2 (1993, NES) showing dialogue window with character portraits" width="800" height="400"&gt;&lt;/a&gt;&lt;a href="https://en.wikipedia.org/wiki/DuckTales_2" rel="noopener noreferrer"&gt;Ducktales 2 (1993, NES)&lt;/a&gt; has these character portraits beside the dialogue window to show who's speaking.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F9887ceeadadb3411620324e56bc91088%2Fe85cb%2Fgolden-sun-dialogue-screenshot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F9887ceeadadb3411620324e56bc91088%2Fe85cb%2Fgolden-sun-dialogue-screenshot.png" title="Screenshot from Golden Sun (2001, GBA) showing dialogue options with character" alt="Screenshot from Golden Sun (2001, GBA) showing dialogue options with character" width="800" height="400"&gt;&lt;/a&gt;&lt;a href="https://en.wikipedia.org/wiki/Golden_Sun_(video_game)" rel="noopener noreferrer"&gt;Golden Sun (2001, GBA)&lt;/a&gt; included a dialogue window and portrait window positioned dynamically on the screen, with simple yes/no options suitable for the smaller screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2Fc47723b858419c17cd4e362b96d63054%2Ff4e34%2Fdonkey-kong-country-3-funky.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2Fc47723b858419c17cd4e362b96d63054%2Ff4e34%2Fdonkey-kong-country-3-funky.webp" title="Screenshot from Donkey Kong Country 3 (1996, SNES)" alt="Screenshot from Donkey Kong Country 3 (1996, SNES)" width="800" height="400"&gt;&lt;/a&gt;&lt;a href="https://en.wikipedia.org/wiki/Donkey_Kong_Country_3:_Dixie_Kong%27s_Double_Trouble!" rel="noopener noreferrer"&gt;Donkey Kong Country 3 (1996, SNES)&lt;/a&gt;. This game also used a dialogue window at the bottom of the screen, with selectable options built into the game scene itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2Ff7e7b7f20efd230381dfe04924c22171%2Fcb69c%2Fdune.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2Ff7e7b7f20efd230381dfe04924c22171%2Fcb69c%2Fdune.jpg" title="Screenshot from Dune (1992, PC) showing dialogue options with character image" alt="Screenshot from Dune (1992, PC) showing dialogue options with character image" width="800" height="400"&gt;&lt;/a&gt;&lt;a href="https://en.wikipedia.org/wiki/Dune_(video_game)" rel="noopener noreferrer"&gt;Dune (1992, PC)&lt;/a&gt;. 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2Fe77fe1396a63f422686ceac737e3e09d%2Fc08c5%2F329669-banjo-kazooie-nintendo-64-screenshot-mumbo-jumbo-the-shaman.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2Fe77fe1396a63f422686ceac737e3e09d%2Fc08c5%2F329669-banjo-kazooie-nintendo-64-screenshot-mumbo-jumbo-the-shaman.jpg" title="Screenshot from Banjo Kazooie on N64" alt="Screenshot from Banjo Kazooie on N64" width="800" height="400"&gt;&lt;/a&gt;&lt;a href="https://en.wikipedia.org/wiki/Banjo-Kazooie_(video_game)" rel="noopener noreferrer"&gt;Banjo Kazooie (1998, N64)&lt;/a&gt;, 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.&lt;/p&gt;
&lt;h2&gt;
  
  
  Project setup
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I plan on using the dialogue section to also include dialogue options underneath.&lt;/p&gt;

&lt;p&gt;&lt;a href="/static/72f69766f4319fac700670a8800f542f/6f3f2/layout-mockup.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F72f69766f4319fac700670a8800f542f%2F6f3f2%2Flayout-mockup.png" title="Scene layout with pixel dimensions" alt="Scene layout with pixel dimensions" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;



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

&lt;p&gt;Assets I’ll be using:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;This 24x24 sprite for the NinePatchRect (I quickly whipped this up using &lt;a href="https://www.piskelapp.com/" rel="noopener noreferrer"&gt;Piskel&lt;/a&gt;. 
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F34ff8734ddb8107aeeb24dbe10da15af%2F8f029%2Fui_border.png" title="ui border" alt="ui border" width="800" height="400"&gt;
&lt;/li&gt;
&lt;li&gt;zx_spectrum font &lt;a href="https://www.dafont.com/zx-spectrum-7.font" rel="noopener noreferrer"&gt;https://www.dafont.com/zx-spectrum-7.font&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;These 32x32 portrait images &lt;a href="https://emily2.itch.io/pixel-portraits-32x32" rel="noopener noreferrer"&gt;https://emily2.itch.io/pixel-portraits-32x32&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;These great cyberpunk sprite sets &lt;a href="https://ansimuz.itch.io/warped-city" rel="noopener noreferrer"&gt;https://ansimuz.itch.io/warped-city&lt;/a&gt; and &lt;a href="https://ansimuz.itch.io/warped-city-2" rel="noopener noreferrer"&gt;https://ansimuz.itch.io/warped-city-2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;This 32x32 speech bubble sprite. Another asset I quickly draw in Piskel. 
&lt;a href="/static/a93b6a3d3eb8f70b6370a8f537fea0cc/103b3/speech_bubble.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2Fa93b6a3d3eb8f70b6370a8f537fea0cc%2F103b3%2Fspeech_bubble.png" title="speech bubble" alt="speech bubble" width="800" height="400"&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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!&lt;/p&gt;



&lt;p&gt;First, we have some settings to change.&lt;/p&gt;

&lt;p&gt;Open your Project Settings (Project -&amp;gt; Project Settings)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rendering -&amp;gt; 2d -&amp;gt; Snapping -&amp;gt; Use Gpu Pixel Snap &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2Ff442f4605391dcb4e3962a84458b3051%2F8c557%2F0-turn-on-gpu-pixel-snap.png" title="2D Rendering Settings - Turning on GPU pixel snap" alt="2D Rendering Settings - Turning on GPU pixel snap" width="800" height="400"&gt; (If you're using an older version of Godot, you might instead find this setting under Rendering -&amp;gt; Quality -&amp;gt; Turn on pixel snap. You can check out the &lt;a href="https://github.com/godotengine/godot/pull/46552" rel="noopener noreferrer"&gt;pull request&lt;/a&gt; for the change in GitHub if you're interested.)&lt;/li&gt;
&lt;li&gt;Display -&amp;gt; Window -&amp;gt; Resolution to 256x224, set stretch mode to 2D &lt;a href="/static/9416486e1406b530327794804d3e6807/f4b77/0a-window-display-settings.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F9416486e1406b530327794804d3e6807%2F8c557%2F0a-window-display-settings.png" title="Window Display Settings - Setting resolution and stretch mode" alt="Window Display Settings - Setting resolution and stretch mode" width="800" height="400"&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our scene is going to be made up of these nodes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Control, the base node. This will contain the script for our DialogueBox&lt;/li&gt;
&lt;li&gt;NinePatchRect, for the dialogue box. This will have four RichTextLabel children: Character name, dialogue, Choice A and Choice B&lt;/li&gt;
&lt;li&gt;NinePatchRect, for the portrait box. This will have a Sprite for the character portrait&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new scene. Choose 'Other node' and choose Control. Name it 'DialogueUI'.&lt;/li&gt;
&lt;li&gt;Add a NinePatchRect and call it PortraitRect&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;Set Patch Margin to 8 pixels for each side.&lt;/li&gt;
&lt;li&gt;Add another NinePatchRect and call it DialogueRect&lt;/li&gt;
&lt;li&gt;Set position to 64, 160. Set size to 192x64. Update the Patch Margin to 8 pixels each side.&lt;/li&gt;
&lt;li&gt;Set its texture to ui_border.png as well. Your viewport should be looking something like this: &lt;a href="/static/26f8679046ad716d44c4314090abd4c2/fbfd6/1-viewport-with-panels.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F26f8679046ad716d44c4314090abd4c2%2F8c557%2F1-viewport-with-panels.png" title="Viewport with NinePatchRect textures laid out" alt="Viewport with NinePatchRect textures laid out" width="800" height="400"&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;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. &lt;a href="/static/1ee227cd070dd3dbece8cdf3490fe475/ef6b9/2-adding-portrait.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F1ee227cd070dd3dbece8cdf3490fe475%2F8c557%2F2-adding-portrait.png" title="Adding a portrait image" alt="Adding a portrait image" width="800" height="400"&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Creating the font resource:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new resource of type DynamicFont. Call it &lt;code&gt;zx_spectrum.tres&lt;/code&gt;. Drag the imported &lt;code&gt;zx_spectrum.ttf&lt;/code&gt; file into the Font attribute of your new resource. Set the size of the font to 7.&lt;a href="/static/5729336f29c4c076a78d38d8b07d08ef/6bdcf/3a-adding-name-font.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F5729336f29c4c076a78d38d8b07d08ef%2F6bdcf%2F3a-adding-name-font.png" title="Creating the font resource" alt="Creating the font resource" width="800" height="400"&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, let’s start adding our text:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a RichTextLabel as a child of the DialogueRect and position it at the top of the dialogue box. Call it 'CharacterName'. &lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At this point, it should look something like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="/static/648fd257ad073932312ce825ca0c29c0/c929c/5-adding-choices-text.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F648fd257ad073932312ce825ca0c29c0%2F8c557%2F5-adding-choices-text.png" title="Viewport layout with text labels" alt="Viewport layout with text labels" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And your scene hierarchy should look something like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="/static/5afb5634ce9c28e32f2d0bb4f94d1ccf/5f7d4/5-scene-heirarchy.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F5afb5634ce9c28e32f2d0bb4f94d1ccf%2F5f7d4%2F5-scene-heirarchy.png" title="Scene hierarchy with portrait and text labels added" alt="Scene hierarchy with portrait and text labels added" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Defining dialogue via a script
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So, what sorts of things do we want our dialogue box to be able to do?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Follow a predefined text sequence, with player choices determining the path through the conversation.&lt;/li&gt;
&lt;li&gt;Show the name and portrait of the character currently speaking.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Let’s get stuck into some scripting!&lt;/p&gt;

&lt;p&gt;Add a script to our base Control node and call it &lt;code&gt;DialogueUI&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Playing the scene gives us this:&lt;/p&gt;

&lt;p&gt;&lt;a href="/static/96f13e9d2f72928630c89e003ef4506e/160a3/6-scripted-text.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.alexpech.com%2Fstatic%2F96f13e9d2f72928630c89e003ef4506e%2F160a3%2F6-scripted-text.png" title="Scene view with text displayed via script" alt="Scene view with text displayed via script" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s great, but it still doesn’t actually &lt;em&gt;do&lt;/em&gt; 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:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func _process(delta):
  if Input.is_action_just_pressed("ui_accept"):
    dialogue_node.text = "This is what I'll say next."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if you play the scene, the dialogue text will be replaced with this new text when you hit Enter.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Add this to the top of your script:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var dialogue = [
  "Hey there.\nDo you like apples?",
  "I like other fruits too.",
  "Bananas are my favourite!"
]

var current_index = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, change our &lt;code&gt;_process&lt;/code&gt; function to look like this:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func _process(delta):
  if Input.is_action_just_pressed("ui_accept"):
    current_index += 1
    dialogue_node.text = dialogue[current_index]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to modify our _ready function to set the dialogue initially.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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!&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;_process&lt;/code&gt; function so prevent that from happening.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func _process(delta):
  if (Input.is_action_just_pressed("ui_accept")
      and current_index &amp;lt; (dialogue.size() - 1)):
    current_index += 1
    dialogue_node.text = dialogue[current_index]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will now only progress to the next part of the conversation if the next index won’t overrun the size of the array.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing branching conversation options
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgefbi25g4pjumc0sxmg6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgefbi25g4pjumc0sxmg6.png" width="280" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;em&gt;choice&lt;/em&gt; the player makes to define the next index?&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var conversation = [
  {
    "dialogue": "Hey there.\nDo you like apples?"
  },
  {
    "dialogue": "I like other fruits too."
  },
  {
    "dialogue": "Bananas are my favourite!"
  }
  ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;conversation&lt;/code&gt; in preparation for this holding additional data for our conversation, other than just dialogue.&lt;/p&gt;

&lt;p&gt;Let’s fix up our &lt;code&gt;_ready&lt;/code&gt; and &lt;code&gt;_process&lt;/code&gt; functions to use this new structure:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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 &amp;lt; (conversation.size() - 1)):
    current_index += 1
    dialogue_node.text = conversation[current_index]["dialogue"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the scene should still allow us to step through the conversation exactly as before.&lt;/p&gt;

&lt;p&gt;Now, let’s modify our conversation array to include a choice:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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!"
  }
  ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func _process(delta):
  if current_index &amp;lt; (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"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s a couple of things we’ve done here.&lt;/p&gt;

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

&lt;p&gt;Second, we’ve introduced a local variable, &lt;code&gt;previous_index&lt;/code&gt;. This is how we’re going to detect changes in the &lt;code&gt;current_index&lt;/code&gt; so that we know when we need to update the text label.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;First, add a destination to this piece of dialogue.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "dialogue": "You like apples? Me too!",
  "destination": 3
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let’s handle that when calculating our update to &lt;code&gt;current_index&lt;/code&gt;.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if Input.is_action_just_pressed("ui_accept"):
  current_index = conversation[current_index].get(
    "destination", current_index + 1
  )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making defining dialogue choices easier
&lt;/h2&gt;

&lt;p&gt;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 &lt;em&gt;all&lt;/em&gt; the destinations afterwards.&lt;/p&gt;

&lt;p&gt;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”.&lt;/p&gt;

&lt;p&gt;Sounds much more convenient. Let’s do it!&lt;/p&gt;

&lt;p&gt;Let’s update our conversation to look like this:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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!"
  }
  ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Next, we’re going to define a helper function for us to find the index of a dictionary with a given label:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ve added an &lt;code&gt;assert&lt;/code&gt; 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!&lt;/p&gt;

&lt;p&gt;Finally, let’s modify our &lt;code&gt;_process&lt;/code&gt; function to use labels to calculate updates to current_index.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func _process(delta):
  if current_index &amp;lt; (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"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of setting the &lt;code&gt;current_index&lt;/code&gt; directly, we now set a destination variable instead, and then calculate the &lt;code&gt;current_index&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Notice that we default &lt;code&gt;destination&lt;/code&gt; to &lt;code&gt;null&lt;/code&gt;, and then set it to &lt;code&gt;false&lt;/code&gt; if we haven’t got a label as a destination. We need to distinguish between these two states because &lt;code&gt;null&lt;/code&gt; means we shouldn’t progress through the conversation at all, while &lt;code&gt;false&lt;/code&gt; means we should step forward one. Using &lt;code&gt;null&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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!&lt;/p&gt;

&lt;h2&gt;
  
  
  Scripting the UI
&lt;/h2&gt;

&lt;p&gt;Now that we can make choices, let’s hook up our UI so that we can select them from our dialogue box.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func get_current_choice(choice_index):
  var choices = conversation[current_index].get("choices", [])
  if choice_index &amp;lt; 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", "")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;get_current_choice&lt;/code&gt; function is a helper that let’s us safely fetch choices and return convenient defaults if none exist.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;update_text_labels&lt;/code&gt; wraps up the updates for all our RichTextLabels into one place. You can see here how we’re using the &lt;code&gt;get_current_choice&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;We can now update our script to use &lt;code&gt;update_text_labels&lt;/code&gt; wherever we need them to update.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;_ready&lt;/code&gt; function,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func _ready():
  update_text_labels()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, in &lt;code&gt;_process&lt;/code&gt;,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if current_index != previous_index:
  update_text_labels()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when we run the scene, we should see the Choice A and Choice B labels updating as we move through the conversation!&lt;/p&gt;

&lt;h2&gt;
  
  
  Refining the UI controls
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Add a &lt;code&gt;current_choice&lt;/code&gt; variable under our existing &lt;code&gt;current_index&lt;/code&gt; state variable.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var current_index = 0
var current_choice = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change the code that runs when we push the Up and Down arrows, to change our current choice.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      if Input.is_action_just_pressed("ui_up"):
        current_choice -= 1

      if Input.is_action_just_pressed("ui_down"):
        current_choice += 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No &lt;code&gt;choices&lt;/code&gt; or &lt;code&gt;destination&lt;/code&gt;. Result: just go to next conversation piece (increment index)&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;choices&lt;/code&gt;, but &lt;code&gt;destination&lt;/code&gt; defined. Result: find the next index based on the destination label.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;choices&lt;/code&gt; is defined, and current choice has &lt;code&gt;destination&lt;/code&gt;. Result: find the next index based on the destination label of the current choice.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;choices&lt;/code&gt; is defined, but current choice has no &lt;code&gt;destination&lt;/code&gt;. Result: just go to next conversation piece (increment index)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Currently, when we press Enter, all we do is set the destination by fetching it from our current conversation piece:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;destination = conversation[current_index].get("destination", false)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  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)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning up our &lt;code&gt;_process&lt;/code&gt; function
&lt;/h2&gt;

&lt;p&gt;Let’s make one more improvement before moving on. We can make our &lt;code&gt;_process&lt;/code&gt; function a lot simpler by pulling the logic to calculate our next index out into a separate function.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, in &lt;code&gt;_process&lt;/code&gt;, we only need to call this, and then check if the &lt;code&gt;current_index&lt;/code&gt; is going to change.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    if Input.is_action_just_pressed("ui_accept"):
      current_index = get_next_index()

    if current_index != previous_index:
      update_text_labels()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of the code to calculate the &lt;code&gt;destination&lt;/code&gt; and the &lt;code&gt;current_index&lt;/code&gt; can now be removed from &lt;code&gt;_process&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Aside: Design notes
&lt;/h3&gt;

&lt;p&gt;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,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "label": "apples_good",
  "dialogue": "You like apples? Me too!",
  "choices": [
    {
      "dialogue": "...",
      "destination": "part_2"
    }
  ]
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of this,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "label": "apples_good",
  "dialogue": "You like apples? Me too!",
  "destination": "part_2"
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;em&gt;once&lt;/em&gt; and then we benefit from then on, so it’s a good payoff!&lt;/p&gt;




&lt;p&gt;There’s still a couple of pretty obvious issues that we should fix:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There’s nothing to indicate to the player which option they’ve chosen.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Add this to our &lt;code&gt;_process&lt;/code&gt; function, just under the part where we update our &lt;code&gt;current_choice&lt;/code&gt;.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This implementation works for my example, but you can change it to fit whatever your needs.&lt;/p&gt;

&lt;p&gt;We also need to call the function when we update our current choice.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    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()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now remove our &lt;code&gt;if current_choice == 0:...&lt;/code&gt; code block.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;_ready&lt;/code&gt; function so that we do an initial update of the select indicators.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func _ready():
  update_text_labels()
  update_select_indicators()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try running the scene again to make sure everything is still working.&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we use the &lt;code&gt;safe_&lt;/code&gt; functions, there’s no way we can move past the first or last valid choice index. Notice in &lt;code&gt;get_current_choice_count&lt;/code&gt; 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).&lt;/p&gt;

&lt;p&gt;We also call &lt;code&gt;update_select_indicators()&lt;/code&gt; inside these functions. We want this to happen whenever &lt;code&gt;current_choice&lt;/code&gt; gets updated, and this means we don’t need to remember to call it from within &lt;code&gt;_process&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All that’s left is to use these in our &lt;code&gt;_process&lt;/code&gt; function:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  if current_index &amp;lt; (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()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Let’s define a function for that as well, again also calling &lt;code&gt;update_select_indicators&lt;/code&gt; since &lt;code&gt;current_choice&lt;/code&gt; has changed.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func reset_selection():
  current_choice = 0
  update_select_indicators()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And at the end of &lt;code&gt;_process&lt;/code&gt;,&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    if current_index != previous_index:
      update_text_labels()
      reset_selection()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Here’s the final script for reference:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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 &amp;lt; (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 &amp;lt; 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()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's the end of Part 1!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.alexpech.com/tutorials/building-a-retro-dialogue-plugin-for-godot/godot-demo/godot-retro-dialogue-part-1.html" rel="noopener noreferrer"&gt;Try out the finished product in your browser here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/alexpech12/godot-retro-dialogue/tree/v0.1" rel="noopener noreferrer"&gt;View and download the project on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="https://dev.to/alexpech12/building-a-retro-dialogue-plugin-for-godot-part-2-imo"&gt;Continue to Part 2&lt;/a&gt;, where we look at adding multiple characters, text animation, and how to insert variables.&lt;/p&gt;

</description>
      <category>godot</category>
      <category>tutorial</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
