Hello and welcome to lesson 6 of the articy:draft Importer for Unity tutorial series.
Recap lesson 5
In the previous lesson we have started to set up branching functionality for the dialogue navigation buttons. We can now instantiate and show the correct number of buttons required for the amount of following branches in the dialogue UI.
This lesson
In this lesson we will add text to buttons if there is more than one option for the player to choose from. Furthermore, buttons will get their functionality back. At the end of the lesson we should be able to start, properly display, and conclude dialogues.
We start by creating a new C# script called BranchChoice
and add it to the dialogueBranch
prefab. This script will be responsible for the UI control of a single option in a multiple choice dialogue.
First thing we can do in the script is to remove Start
and Update
and add a couple of namespaces: Articy.Unity
, Articy.Unity.Interfaces
, and UnityEngine.UI
. Then we create two public methods. The first one we call AssignBranch
. This method will be called each time a button is created to represent a single branch in the flow. The second method’s name is going to be OnBranchSelected
. Within this method we will control what happens when a button is clicked.
using UnityEngine.UI; using Articy.Unity; using Articy.Unity.Interfaces; public class BranchChoice : MonoBehaviour { public void AssignBranch() { } public void OnBranchSelected() { } }
Next we declare some private variables: one of type Branch
, we are going to call branch
, one of type ArticyFlowPlayer
called flowPlayer
, and one of type Text
called buttonText
, which we open to the editor via the SerializeField
attribute.
private Branch branch; private ArticyFlowPlayer flowPlayer; [SerializeField] Text buttonText;
We switch to the Unity editor, open the dialogueBranch
prefab and select the Text
child object of dialogueBranch
as the target for the buttonText
reference we just created. Then we can get back to the BranchChoice
script.
First we add two parameters to AssignBranch
: ArticyFlowPlayer aFlowPlayer
, and Branch aBranch
. Within the method we assign branch
the value of aBranch
and flowPlayer
the value of aFlowPlayer
. Then we declare a variable target
of type IFlowObject
and initialize it to the value of aBranch.Target
. Followed by assigning buttonText.text
the value of an empty string.
public void AssignBranch(ArticyFlowPlayer aFlowPlayer, Branch aBranch) { branch = aBranch; flowPlayer = aFlowPlayer; IFlowObject target = aBranch.Target; buttonText.text = string.Empty; }
If we look at the dialogue in articy, we can see that for player choices menu text was used. This menu text is supposed to be displayed as the button text. For one it is usually phrased shorter than the actual response, so the risk of overlapping text or cut-offs in the UI is lower. And secondly, I feel it is kind of redundant to show the entire dialogue line already in the button and then show it once again when selected, maybe even in combination with voice over. So this is more of a design decision. To show menu text where it is used in branching situations, and for a linear conversation instead of the text we just display an arrow or something as a signal for the player to click to continue.
For our script that means we first check if we have any menu text that we could display. We do this by declaring a variable objectWithMenuText
and initializing it with target
cast to IObjectWithMenuText
. Next we check if this object contains the menu text property. If the statement returns true, then we assign buttonText.text
the value of objectWithMenuText.MenuText
.
var objectwithMenuText = target as IObjectWithMenuText; if (objectWithMenuText != null) { buttonText.text = objectwithMenuText.MenuText; }
Okay, so if we have menu text then we display it. Now we have to cover the case if there is no menu text. Let’s say you wanted to display the entire dialogue line if there’s no menu text. In that case, we start with another if-statement checking if the buttonText.text
string currently is empty or null. Then we declare a variable objectWithText
and initialize it with target
cast to IObjectWithText
. Next comes our usual safety check to make sure the object is not null. If it is not null, so if there is any text property, we assign buttonText.text
the value of objectwithText.Text
.
if (string.IsNullOrEmpty(buttonText.text)) { var objectWithText = target as IObjectWithText; if (objectWithText != null) { buttonText.text = objectWithText.Text; } }
However, like I mentioned before, for this project I just want to display menu text in the dialogue UI, and if there is none, then we display an arrow or something similar, so the player knows they can continue the dialogue. That means I’ll remove the content of the last if-statement we added, and replace it by simply assigning buttonText.text
the value of the string we want to display. That’s it.
if (string.IsNullOrEmpty(buttonText.text)) { buttonText.text = ">>>";var objectWithText = target as IObjectWithText;if (objectWithText != null){buttonText.text = objectWithText.Text;}}
One thing left to do in this script: We need to define what should happen when a button gets clicked. Do you remember what we did before, for our linear dialogue solution?
We told the Flow Player to get going again. Exactly this is what we are going to do within the OnBranchSelected
method as well, with one added detail. For the linear dialogue it was absolutely sufficient to start up the Flow Player again with flowPlayer.Play()
, without passing any argument. Without an argument the Flow Player always chooses the first branch, which in a linear flow is fine, because there is only one branch. But now we need to tell the Flow Player which route it needs to take, therefore we pass the argument branch
.
public void OnBranchSelected() { flowPlayer.Play(branch); }
We are almost ready to test our solution, just two small steps to go. First we switch into the DialogueManager
script. Here we already create the buttons for all current branches, what we still need to do is provide them with the functionality we just added in the BranchChoice
script. In the OnBranchesUpdated
method within the second foreach loop we add one line of code: We get the component BranchChoice
for the button object and call the AssignBranch
method from it. As arguments, we pass the flowPlayer
and the current branch
.
if (!dialogueIsFinished) { foreach (var branch in aBranches) { GameObject btn = Instantiate(branchPrefab, branchLayoutPanel); btn.GetComponent<BranchChoice>().AssignBranch(flowPlayer, branch); } }
Now we go to the Unity editor and return to the dialogueBranch
prefab. In here we go to On Click and select dialogueBranch
as the referenced object. In the function drop down select BranchChoice
and then choose OnBranchSelected
. Now we have enough functionality up and running that we can do another test.
I go straight for the red NPC with the branching dialogue. Very curious what will happen. Buttons display properly and for choices we get presented the menu text as button text. After selecting an option we see the complete dialogue line. To just move the dialogue along, we see the continue symbol we opted for.
Only thing missing is the button to end the dialogue, but it will not be missing for much longer, as we will take care of it right away! In the DialogueManager
script we add a GameObject
called closePrefab
, and open it to the editor via the SerializeField
attribute.
[SerializeField] GameObject closePrefab;
In the Unity editor we fill this reference with the dialogueBranchClose prefab. Again, please make sure you are using the prefab, otherwise it will not work later.
Back in the DialogueManager
script we are basically going to do the same thing as before with the navigational button, only this one gets another listener. In OnBranchesUpdated
we add an else
clause, we are now covering the case when the dialogue is over. We can copy the code line regarding the button instantiation from above, only thing we need to change is that we instantiate closePrefab
instead of branchPrefab
. With that we create the close button, now we only need to give it functionality by adding a listener to on click. We declare a variable btnComp
and initialize it to getting the Button
component of btn
. Then we add a listener to on click for btn
component and call CloseDialogueBox
.
else { GameObject btn = Instantiate(closePrefab, branchLayoutPanel); var btnComp = btn.GetComponent<Button>(); btnComp.onClick.AddListener(CloseDialogueBox); }
Let’s run a test in Unity. The linear test dialogue is displayed correctly and ends with the deactivation of the dialogue UI. Same for the branching test.
Recap lesson 6
Awesome! With that we have arrived at the end of this lesson and almost at the end of this tutorial series.We added text to choice buttons and implemented button functionality to navigate and close dialogues. We can now start, display, and end linear and branching dialogues.
Next lesson
In the next lesson, which will be the final one in this series, we will learn what is necessary to handle more complex dialogues, which use some scripting logic and global variables. Then we conclude the series with some tips on how you can continue building upon this foundation we have created for the articy Importer for Unity.
See you there!
Current state of C# scripts we have worked on this lesson:
BranchChoice.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using Articy.Unity; using Articy.Unity.Interfaces; using UnityEngine.UI; public class BranchChoice : MonoBehaviour { private Branch branch; private ArticyFlowPlayer flowPlayer; [SerializeField] Text buttonText; // Called when a button is created to represent a single branch public void AssignBranch(ArticyFlowPlayer aFlowPlayer, Branch aBranch) { branch = aBranch; flowPlayer = aFlowPlayer; IFlowObject target = aBranch.Target; buttonText.text = string.Empty; // Check if the object has a "MenuText" property. // If yes, set the button text to that "MenuText" var objectWithMenuText = target as IObjectWithMenuText; if (objectWithMenuText != null) { buttonText.text = objectWithMenuText.MenuText; } // If there is no text from a "MenuText" property set, // we place ">>>" on the button as a "Continue" symbol if (string.IsNullOrEmpty(buttonText.text)) { buttonText.text = ">>>"; } } // What happens when button is clicked public void OnBranchSelected() { flowPlayer.Play(branch); } }
DialogueManager.cs
using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using Articy.Unity; using Articy.Unity.Interfaces; using Articy.UnityImporterTutorial; public class DialogueManager : MonoBehaviour, IArticyFlowPlayerCallbacks { [Header("UI")] // Reference to Dialog UI [SerializeField] GameObject dialogueWidget; // Reference to dialogue text [SerializeField] Text dialogueText; // Reference to speaker [SerializeField] Text dialogueSpeaker; // Reference to button layout [SerializeField] RectTransform branchLayoutPanel; // Reference to navigation button prefab [SerializeField] GameObject branchPrefab; // Reference to close button prefab [SerializeField] GameObject closePrefab; // To check if we are currently showing the dialog ui interface public bool DialogueActive { get; set; } private ArticyFlowPlayer flowPlayer; void Start() { flowPlayer = GetComponent<ArticyFlowPlayer>(); } public void StartDialogue(IArticyObject aObject) { DialogueActive = true; dialogueWidget.SetActive(DialogueActive); flowPlayer.StartOn = aObject; } public void CloseDialogueBox() { DialogueActive = false; dialogueWidget.SetActive(DialogueActive); // Completely process current object before we end dialogue flowPlayer.FinishCurrentPausedObject(); } // This is called every time the flow player reaches an object of interest public void OnFlowPlayerPaused(IFlowObject aObject) { //Clear data dialogueText.text = string.Empty; dialogueSpeaker.text = string.Empty; // If we paused on an object that has a "Text" property fetch this text and present it var objectWithText = aObject as IObjectWithText; if (objectWithText != null) { dialogueText.text = objectWithText.Text; } // If the object has a "Speaker" property try to fetch the speaker var objectWithSpeaker = aObject as IObjectWithSpeaker; if (objectWithSpeaker != null) { // If the object has a "Speaker" property, fetch the reference // and ensure it is really set to an "Entity" object to get its "DisplayName" var speakerEntity = objectWithSpeaker.Speaker as Entity; if (speakerEntity != null) { dialogueSpeaker.text = speakerEntity.DisplayName; } } } // Called every time the flow player encounters multiple branches, // or is paused on a node and wants to tell us how to continue public void OnBranchesUpdated(IList<Branch> aBranches) { // Destroy buttons from previous use, will create new ones here ClearAllBranches(); // Check if any branch leads to a DialogueFragment target // If so, the dialogue is not yet finished bool dialogueIsFinished = true; foreach (var branch in aBranches) { if (branch.Target is IDialogueFragment) { dialogueIsFinished = false; } } if (!dialogueIsFinished) { // If we have branches, create a button for each of them foreach (var branch in aBranches) { // Instantiate a button in the Dialogue UI GameObject btn = Instantiate(branchPrefab, branchLayoutPanel); // Let the BranchChoice component fill the button content btn.GetComponent<BranchChoice>().AssignBranch(flowPlayer, branch); } } else { // Dialogue is finished, instantiate a close button GameObject btn = Instantiate(closePrefab, branchLayoutPanel); // Clicking this button will close the Dialogue UI var btnComp = btn.GetComponent<Button>(); btnComp.onClick.AddListener(CloseDialogueBox); } } // Delete buttons from previous branches void ClearAllBranches() { foreach (Transform child in branchLayoutPanel) { Destroy(child.gameObject); } } }
Don’t have articy:draft X yet? Get the free version now!
Get articy:draft X FREE
*No Payment information required
Follow us on Twitter, Facebook and LinkedIn to keep yourself up to date and informed. To exchange ideas and interact with other articy:draft users, join our communities on reddit and discord.