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.

Recap lesson 1

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.

Content lesson 2

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!

Adding ArticyFlowPlayer component to Dialogue Manager

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(IList aBranches)
{
	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.

Set Start On element for ArticyFlowPlayer component

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.

articy:draft linear test dialogue in Flow view

Let’s see what happens when we hit Play. A lot of error messages in the console.

Error messages in 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(IList aBranches)
{
	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.

Setting Dialogue Widget to active

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.

First line of test dialogue gets displayed in 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.

Clearing Start On reference from ArticyFlowPlayer component

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.

ArticyReference component on NPC prefab

Exit the prefab and select the NPC1 object. Here we set ‘Linear Test’ as the target for the Articy Reference component.

Setting target for ArticyReference component on NPC1 object

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.

First line of dialogue is displayed after interacting with NPC1

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.

Recap lesson 2

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.

Whats's next?

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

GO TO LESSON 3

Useful links:

Complete namespace reference
Object handling with ArticyRef
ArticyReference Class
ArticyRef Class

Don’t have articy:draft 3 yet? Get the free version now!
Get articy:draft 3 FREE
*No payment information required

Follow us on Twitter, Facebook and LinkedIn to keep yourself up to date and informed.