Hello and welcome to lesson 2 of the articy:draft Importer for Unity tutorial series.
Recap lesson 1
In the first lesson we defined our goal for this tutorial series, which is to display branching dialogue imported from articy within custom UI in our Unity project. We took a first look at both Unity and articy projects, installed the articy Importer for Unity from the asset store, and traversed through the first imported dialogue with the ArticyDebugFlowPlayer
.
This lesson
In this lesson we will add the ArticyFlowPlayer
component to our Dialogue Manager game object and update the DialogueManager
and PlayerController
C# scripts to be able to interact with an NPC to start a linear dialogue and display the first dialogue line in our UI.
Setting foundations to display dialogue
First we remove the Articy Debug Flow Player game object, we do not need it anymore. Our solution will be more streamlined towards this specific use case. A lot will still be based on functionality of the Debug Flow Player, so I invite you to take a look at its C# script at some point. If you have finished this introductory tutorial series, it will look a lot clearer and you should be able to grasp the additional elements used in there.
Now comes a small, but very important step: we add an ArticyFlowPlayer
component to the Dialogue Manager. Without this component none of the functionality we want to achieve would work. Don’t mind the yellow warning signs for the time being!
Now we open the DialogueManager
script in our IDE.
We already have a couple of variable declarations in here, creating references to the UI elements. The entire Dialogue Widget, a dialogue line, and the current speaker. Plus a bool property DialogueActive
which we use for displaying or hiding the UI elements. The StartDialogue
method activates the Dialogue Widget and displays the strings it received as arguments. The CloseDialogueBox
method hides the dialogue UI again.
using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class DialogueManager : MonoBehaviour { [Header("UI")] // Reference to Dialog UI [SerializeField] GameObject dialogueWidget; // Reference to dialogue text [SerializeField] Text dialogueText; // Reference to speaker [SerializeField] Text dialogueSpeaker; // To check if we are currently showing the dialog ui interface public bool DialogueActive { get; set; } void Start() { } public void StartDialogue(string aDialogueLine, string aSpeaker) { DialogueActive = true; dialogueWidget.SetActive(DialogueActive); dialogueText.text = aDialogueLine; dialogueSpeaker.text = aSpeaker; } public void CloseDialogueBox() { DialogueActive = false; dialogueWidget.SetActive(DialogueActive); } }
First order of business is setting up the ArticyFlowPlayer
component for use in this script. To do that we add the Articy.Unity
namespace. And because we will need it in a few short moments, we can also already add Articy.Unity.Interfaces
while we are here. Next we’ll add a private variable of type ArticyFlowPlayer
which I will call flowPlayer
. In the Start method we initialize flowPlayer
to the return value of GetComponent
using Articy.Unity; using Articy.Unity.Interfaces; private ArticyFlowPlayer flowPlayer; void Start() { flowPlayer = GetComponent<ArticyFlowPlayer>(); }
The Flow Player does most of the work for us in the background. It traverses through the flow, handles branching, evaluates conditions, executes instructions, and will make callbacks to our game code.
However, for the Flow Player to be able to properly do this job, we need to set up a few more things. I add IArticyFlowPlayerCallbacks
as a type to the class and implement it by pressing Alt-Enter and selecting ‘Implement interface’. Hotkeys mentioned are for Visual Studio 2019. With the implementation two new methods have been added to the script: OnFlowPlayerPaused
and OnBranchesUpdated
. When the Flow Player gets to a pause or arrives at a branching point somewhere along its way, we extract information from the node it paused on and tell the Flow Player what to do next with the help of these methods.
public class DialogueManager : MonoBehaviour, IArticyFlowPlayerCallbacks public void OnFlowPlayerPaused(IFlowObject aObject) { throw new System.NotImplementedException(); } Add public void OnBranchesUpdated(IListaBranches) { throw new System.NotImplementedException(); }
With these preparations done we are getting closer to actually seeing some text in our little game scene. Let’s switch back to the Unity editor for a bit. We select the Dialogue Manager object and look for the ArticyFlowPlayer
component in the Inspector. On the ‘Start On’ line click the search widget and select a starting point. I will select the ‘Linear Test’ node. In ‘Pause On’ I select only Dialogue Fragments. That means the Flow Player will pause traversing the flow on Dialogue Fragments and wait for an action from our side to continue.
When we take a quick look at the dialogue in articy:draft, we can see that it is composed out of Dialogue Fragments which are nested inside of a Dialogue node named ‘Linear Test’. This is the node we selected as a starting point for the Flow Player in Unity.
Let’s see what happens when we hit Play. A lot of error messages in the console.
Let’s take a look in the DialogueManager script. Because we set a ‘Start On’ value in the Flow Player component, as soon as we hit Play the dialogue starts. We instructed the Flow Player to pause on Dialogue Fragments, so immediately when it arrives at the first line of dialogue it has no idea what to do, as the corresponding method is empty besides the throw command for the exception.
I will remove the throw command from both methods, as we are now aware that we will have to do some work in here. Although, we will leave the branching situation for later, first we cover the pause.
public void OnFlowPlayerPaused(IFlowObject aObject) {throw new System.NotImplementedException();} public void OnBranchesUpdated(IListaBranches) { throw new System.NotImplementedException();}
What do we want to do? For now, just to have a starting point, let’s have the Flow Player check if there is any text in the object it is paused on and if there is, display that text in the dialogue UI.
We start by declaring a variable objectWithText
and initialize it to aObject
, which is the argument passed into the method, cast to IObjectWithText
. We get aObject
passed as an IFlowObject
, which is an interface that all traversable objects in the flow implement. As we are specifically looking for objects with text we cast it into IObjectWithText
. Click here if you want to learn more about the available interfaces the articy Importer provides.
Next we check if the object has the “text” property, and if yes assign its value to the text property of our dialogueText
variable to have the text displayed in the dialogue UI. If objectWithText
is not equal to null, then assign dialogueText.text
the value of objectWithText.text
. To make sure to not display the same text multiple times as a potential issue when an object contains no text property, we assign an empty string to dialogueText.text
at the top of the method.
public void OnFlowPlayerPaused(IFlowObject aObject) { dialogueText.text = string.Empty; var objectWithText = aObject as IObjectWithText; if (objectWithText != null) { dialogueText.text = objectWithText.Text; } }
Save the script and go back to Unity. Set the Dialogue Widget object active in the inspector and hit Play.
We can see the first line of the dialogue imported from articy displayed. We cannot continue through the dialogue yet and interacting with an NPC will overwrite the dialogue with its old text. Still, working as intended: the Flow Player pauses on the first Dialogue Fragment and its text gets displayed in our UI.
What we are going to do now is to set up the StartDialogue
method in a way that allows us to actually trigger the dialogue by interacting with an NPC. As part of this we will also remove the old dialogue data from our NPCs.
Exit Play mode and set the Dialogue Widget to inactive again. We then clear the Start On reference from the Articy Flow Player component on the Dialogue Manager, as we want to have control over starting a dialogue.
Next we go to the NPC prefab and remove the DialogueHolder
component. In its stead we add an ArticyReference
component. This component holds a single ArticyRef
variable. ArticyRef
variables are used to expose articy objects safely in unity components. You can use ArticyRef
variables in your own components. Here we use the convenient ArticyReference
component as a quick solution to store a reference to an articy dialogue object on an NPC. You can find links to more information on Articy References here.
Exit the prefab and select the NPC1 object. Here we set ‘Linear Test’ as the target for the Articy Reference component.
Now we switch back to the DialogueManager C# script. First we update StartDialogue
to use a parameter of type IArticyObject
with the name aObject
. We want to get away from the single strings that got passed into this method towards an articy object. This we do by using the IArticyObject
interface. Then we assign flowPlayer.StartOn
the value of aObject
. The method now sets the Dialogue Widget active and starts the Flow Player on the object we received as an argument. We can also remove the lines referring to the no longer existing arguments within the method.
public void StartDialogue(IArticyObject aObject)public void StartDialogue(string aDialogueLine, string aSpeaker){ dialogueActive = true; dialogueWidget.SetActive(dialogueActive); flowPlayer.StartOn = aObject;dialogueText.text = aDialogueLine;dialogueSpeaker.text = aSpeaker;}
After saving the DialogueManager
script you might have noticed an error showing up in the Unity console. This happens because we still pass two arguments when calling StartDialogue
in the PlayerController
script.
Go to the PlayerController
script. First we add the Articy.Unity
namespace here as well. Next we declare a private variable of type ArticyObject
which we call articyObject
. In the PlayerInteraction
method we use articyObject
as the argument for calling StartDialogue
.
using Articy.Unity; private ArticyObject articyObject; void PlayerInteraction() { if (Input.GetKeyDown(KeyCode.Space) && isNearNPC) { dialogueManager.StartDialogue(articyObject);dialogueManager.StartDialogue(dialogue, speaker);}
What we are still missing is actually getting the reference to the dialogue we want to trigger and then pass it to StartDialogue
. We can accomplish that in a way similar to the current solution. When the player entered the trigger zone of an NPC, we got its dialogue and name from its DialogueHolder
component. As we put an ArticyReference
component with a reference to a dialogue on the NPC, we can access that component now and get the object that is referenced there. We assign our articyObject
variable the value of the articy object reference that the aOther
ArticyReference
component holds.
To clean up a bit, we can now remove the dialogue and speaker variables from the declaration area at the top and the OnTriggerEnter
method. They are no longer needed.
private string dialogue;private string speaker;void OnTriggerEnter(Collider aOther) { isNearNPC = true; articyObject = aOther.GetComponent<ArticyReference>().reference.GetObject();dialogue = aOther.GetComponent<DialogueHolder>().dialogueLine;speaker = aOther.GetComponent<DialogueHolder>().speakerName;}
We can go back to the Unity editor now and test our solution. If we move up to NPC 1 and press Space we see the first line of the test dialogue. To make sure the dialogue is actually triggered at the moment we talk to the NPC and not right from the start as before, we can set the Dialogue Widget to active and then play again. Remember to set it back to inactive after testing.
Recap lesson 2
Very nice. Today, we managed to lay down a lot of the foundation work for the functionality we want to achieve at the end of this series. We added the ArticyFlowPlayer
component to our Dialogue Manager and worked on both PlayerController
and DialogueManager
scripts to create the basic functionality with which we can access articy data in our Unity game project. When playing through the scene we can now interact with an NPC to display the first line of a dialogue referenced to this object.
Next lesson
In the next lesson we will refactor some code to improve on some functionality and add the speaker name for the current dialogue line.
See you there!
Current state of C# scripts we have worked on this lesson:
DialogueManager.cs
using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using Articy.Unity; using Articy.Unity.Interfaces; 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; // 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); } // This is called every time the flow player reaches an object of interest public void OnFlowPlayerPaused(IFlowObject aObject) { //Clear data dialogueText.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; } } public void OnBranchesUpdated(IList<Branch> aBranches) { } }
PlayerController.cs
using UnityEngine; using UnityEngine.SceneManagement; using Articy.Unity; public class PlayerController : MonoBehaviour { private float speed = 15f; private bool isNearNPC = false; private Rigidbody playerRB; private DialogueManager dialogueManager; private ArticyObject articyObject; void Start() { playerRB = gameObject.GetComponent<Rigidbody>(); dialogueManager = FindObjectOfType<DialogueManager>(); } void Update() { PlayerInteraction(); } private void FixedUpdate() { PlayerMovement(); } // Simple player movement void PlayerMovement() { // Remove movement control while in dialogue if (dialogueManager.DialogueActive) return; playerRB.velocity = new Vector3(Input.GetAxis("Horizontal") * speed, 0, Input.GetAxis("Vertical") * speed); } // All interactions and key inputs player can use void PlayerInteraction() { // Key option to start dialogue when near NPC if (Input.GetKeyDown(KeyCode.Space) && isNearNPC) { dialogueManager.StartDialogue(articyObject); } // Key option to abort dialogue if (dialogueManager.DialogueActive && Input.GetKeyDown(KeyCode.Escape)) { dialogueManager.CloseDialogueBox(); } // Key option to reset entire scene if (Input.GetKeyDown(KeyCode.R)) { RestartScene(); } } // Simple scene restart for testing purposes void RestartScene() { SceneManager.LoadScene(SceneManager.GetActiveScene().name); } // Trigger Enter/Exit used to determine if interaction with NPC is possible void OnTriggerEnter(Collider aOther) { isNearNPC = true; articyObject = aOther.GetComponent<ArticyReference>().reference.GetObject(); } void OnTriggerExit(Collider aOther) { isNearNPC = false; } }
Useful links:
Complete namespace reference
Object handling with ArticyRef
ArticyReference Class
ArticyRef Class
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.