Hello and welcome to the articy:draft X Importer for Unity tutorial series, lesson 3.

In this lesson we will add functionality to our dialogue UI buttons, allowing us to traverse through the entire linear test dialogue.

The Dialogue Widget

First, let’s take a look at how the Dialogue Widget (1) is set up in Unity.

We have an empty game object Dialogue Box (2) that contains the box’s background image and a Text Mesh Pro text field for the dialogue line. The speaker (3) is a separate Text Mesh Pro object, positioned above the dialogue line. The Branches object (4) has a Vertical Layout Group component and is parent of two button objects (5). One button to move forward in a dialogue and one button to end it.

Elements of the Dialogue Widget

Button functionality

Now let’s give these buttons some functionality. In the DialogueManager.cs script, under the UI header, we add a field of type Button with the name dialogueButton and open it to the editor with the SerializeField attribute. We might need to add the UnityEngine.UI namespace as well.

[DialogueManager.cs]
    
using UnityEngine.UI;

[...]

[SerializeField]
private Button dialogueButton;

[...]

Switch to the Unity editor, select the DialogueManager game object, and drag the dialogueBranch object to fill the reference, we just created.

Creating the reference for the dialogueButton

Back in DialogueManager.cs, we create a new public method with the signature void ContinueDialogue.

The method name should be an indicator of what we want to do here. In StartDialogue we start the Flow Player on the object we passed when calling the method. The Flow Player arrives at the first Dialogue Fragment and, because of our settings, pauses there. It then provides us with the data we request to show dialogue text and speaker name via OnFlowPlayerPaused.

But it is still pausing, so we need to tell it to get going again. This we do inside ContinueDialogue with the Play method of the FlowPlayer.

We want to call this method when clicking the Continue button, so now add a listener to a button click in the Start method using dialogueButton.onClick.AddListener. Here we are going to call ContinueDialogue on a click.

[DialogueManager.cs]    

[...]

private void Start()
{    
    flowPlayer = GetComponent<ArticyFlowPlayer>();
    dialogueButton.onClick.AddListener(ContinueDialogue);
}

[...]

public void ContinueDialogue()
{
    flowPlayer.Play();
}

[...]

Let’s save the script, switch to Unity and test the scene.

It works. We can traverse through the entire linear test dialogue. But we are only half way there, as we cannot conclude the dialogue yet. This we will accomplish next.

Please accept marketing cookies to watch this video.

In DialogueManager.cs we add another SerializedField Button field with the name endDialogueButton.

[DialogueManager.cs]    

[...]

[SerializeField]
private Button endDialogueButton;

[...]

In Unity we assign this reference the closeDialogueBranch game object.

Creating the reference for the endDialogueButton

To implement a button to close the dialogue, we first need to know when the dialogue ends. In the DialogueManager.cs script in OnBranchesUpdated we receive a list of all branches following the current node we pause on. This information we can use to check if there is still dialogue text coming, or not.

Within OnBranchesUpdated we declare a bool isDialogueFinished and initialize it to true. Next, we set up a foreach loop, checking all var branch in aBranches.

Because of the way we set up our dialogues in articy, with Dialogue Fragments nested inside of Dialogue nodes, we know that the dialogue is over when no Dialogue Fragment can be found as the following branch target.

We check if any branch leads to an IDialogueFragment target. If this is the case, the current dialogue is not yet over, so we need to set isDialogueFinished to false. When we have the confirmation that the dialogue is not over yet, we do not need to run any remaining iterations of the loop and can use break to end it.

[DialogueManager.cs]    

[...]

public void OnBranchesUpdated(IList<Branch> aBranches)
{
    bool isDialogueFinished = true;

    foreach (var branch in aBranches)
    {
        if (branch.Target is IDialogueFragment)
        {
            isDialogueFinished = false;
            break;
        }
    }
}

The dialogue continues as long as we have another Dialogue Fragment following the current node. However, what is supposed to happen if this is no longer the case?

The Continue button needs to be hidden and the Close button has to appear. This we do below the foreach loop, by checking if isDialogueFinished equals true and if that is the case, set dialogueButton to inactive and endDialogueButton to active.

To give the Close button functionality, we add a listener to it in Start, similar to how we did earlier for the other button. Only this time we have it call EndDialogue.

[DialogueManager.cs]    

[...]
   
private void Start()
{
    flowPlayer = GetComponent<ArticyFlowPlayer>();
    dialogueButton.onClick.AddListener(ContinueDialogue);
    endDialogueButton.onClick.AddListener(EndDialogue);
}    

[...]

public void OnBranchesUpdated(IList<Branch> aBranches)
{    
    [...]

    if (isDialogueFinished)
    {
        dialogueButton.gameObject.SetActive(false);
        endDialogueButton.gameObject.SetActive(true);
    }
}

We are not done 100% yet, but as we already did a couple of steps, let’s do a quick test in Unity of where we are with our button functionality.

The dialogue in articy:draft X

When we get to the end of a dialogue, as we can verify with a quick glance to the articy:draft project, the Continue button disappears and we can conclude the dialogue and close the UI with the Close button.

However, there are still a few small issues. For one, the Close button currently is visible all the time, and secondly, if we end a dialogue and interact with the NPC again, we only see the Close button.

Please accept marketing cookies to watch this video.

Let’s take care of these issues with some small adjustments to the code and within Unity.

In the DialogueManager.cs script we start by adding one new line to the EndDialogue method. When we hide the dialogue UI, we also want to set the Close button to an inactive state.

While we are here in EndDialogue, there is one more thing we can do, which is not tied to the buttons, but a part of the general functionality. Currently, we show the text of the last Dialogue Fragment and then end the dialogue. But on this last object there might be an output pin containing script information, which we may want to process as well. This we can do by telling the flowPlayer to finish the current paused object.

[DialogueManager.cs]    

[...]

public void EndDialogue()
{     
    
    [...]

    endDialogueButton.gameObject.SetActive(false);

    flowPlayer.FinishCurrentPausedObject();

    [...]
}

[...]

We hide the Continue button, when we arrive at the end of a dialogue, in OnBranchesUpdated. So far, we have no way to get it back. By setting the dialogueButton game object to active in the StartDialogue method, we can easily fix this issue.

[DialogueManager.cs]    

[...]

public void StartDialogue(InteractPrompt aPrompt, ArticyObject aObject)
{       
    [...]
    
    dialogueButton.gameObject.SetActive(true);        
}

[...]

In Unity the last thing we need to do is to set both dialogueBranch (6) and closeDialogueBranch (7) to inactive (8), as we now control by code when which button is supposed to appear.

Setting buttons to inactive

That’s it. We can traverse through Green’s dialogue with the buttons behaving as expected.

Please accept marketing cookies to watch this video.

If all you want is to display linear dialogue, you can basically orient yourself at the current project structure and are good to go.

Next lesson

If you want to offer choices to your players, read on, as we are starting to set up branching functionality in the next lesson.

Current state of C# scripts from this lesson

DialogueManager.cs

using Articy.Adx_unitytutorial;
using Articy.Unity;
using Articy.Unity.Interfaces;
using System.Collections.Generic;
using TMPro;
using Unity.Cinemachine;
using UnityEngine;
using UnityEngine.UI;

public class DialogueManager : MonoBehaviour, IArticyFlowPlayerCallbacks
{
    [Header(“UI”)]
    // Reference to Dialogue UI
    [SerializeField]
    private GameObject dialogueWidget;
    // Reference to Dialogue text
    [SerializeField]
    private TMP_Text dialogueText;
    // Reference to speaker
    [SerializeField]
    private TMP_Text dialogueSpeaker;
    [SerializeField]
    private Button dialogueButton;
    [SerializeField]
    private Button endDialogueButton;

    [Header(“Cameras”)]
    [SerializeField]
    private CinemachineCamera movementCam;
    [SerializeField]
    private CinemachineCamera dialogueCam;

    private InteractPrompt prompt;
    private ArticyFlowPlayer flowPlayer;

    // To check if we are currently showing the Dialogue UI interface
    public bool DialogueActive { get; set; }

    private void Awake()
    {
        //Default camera setting
        movementCam.enabled = true;
        dialogueCam.enabled = false;
    }

    private void Start()
    {
        flowPlayer = GetComponent<ArticyFlowPlayer>();
        dialogueButton.onClick.AddListener(ContinueDialogue);
        endDialogueButton.onClick.AddListener(EndDialogue);
    }

    public void StartDialogue(InteractPrompt aPrompt, ArticyObject aObject)
    {
        DialogueActive = true;
        dialogueWidget.SetActive(DialogueActive);

        movementCam.enabled = false;
        dialogueCam.enabled = true;

        prompt = aPrompt;
        prompt.ShowInteractPrompt(false);

        flowPlayer.StartOn = aObject;

        dialogueButton.gameObject.SetActive(true);
    }

    public void ContinueDialogue()
    {
        flowPlayer.Play();
    }

    public void EndDialogue()
    {
        DialogueActive = false;
        dialogueWidget.SetActive(DialogueActive);

        movementCam.enabled = true;
        dialogueCam.enabled = false;

        endDialogueButton.gameObject.SetActive(false);

        flowPlayer.FinishCurrentPausedObject();

        if (prompt != null)
        {
            prompt.ShowInteractPrompt(true);
        }
    }

    public void OnFlowPlayerPaused(IFlowObject aObject)
    {
        dialogueText.text = string.Empty;
        dialogueSpeaker.text = string.Empty;   

        var objectWithText = aObject as IObjectWithLocalizableText;
        if (objectWithText != null)
        {
            dialogueText.text = objectWithText.Text;
        }

        var objectWithSpeaker = aObject as IObjectWithSpeaker;
        if (objectWithSpeaker != null)
        {
            var speakerEntity = objectWithSpeaker.Speaker as Entity;
            if (speakerEntity != null)
            {
                dialogueSpeaker.text = speakerEntity.DisplayName;
            }
        }
    }

    public void OnBranchesUpdated(IList<Branch> aBranches)
    {
        bool isDialogueFinished = true;

        foreach (var branch in aBranches)
        {
            if (branch.Target is IDialogueFragment)
            {
                isDialogueFinished = false;
                break;
            }
        }

        if (isDialogueFinished)
        {
            dialogueButton.gameObject.SetActive(false);
            endDialogueButton.gameObject.SetActive(true);
        }
    }
}
PlayerController.cs

using Articy.Unity;
using UnityEngine;
using UnityEngine.SceneManagement;

public class PlayerController : MonoBehaviour
{
    [SerializeField]
    private int speed;
    [SerializeField]
    private Animator animator;
    [SerializeField]
    private SpriteRenderer playerSprite;

    private PlayerControls playerControls;
    private Rigidbody rb;
    private DialogueManager dialogueManager;

    private Vector3 movement;
    private InteractPrompt interactPrompt;
    private ArticyRef availableDialogue;    

    //Animation Control
    private const string IS_WALKING_PARAM = “IsWalking”;
    private const string IS_DIALOGUE_PARAM = “IsDialogue”;

    //Tags
    private const string IS_NPC_TAG = “NPC”;

    private void Awake()
    {
        playerControls = new PlayerControls();
    }

    private void OnEnable()
    {
        playerControls.Player.Enable();
    }

    private void Start()
    {
        rb = GetComponent<Rigidbody>();
        dialogueManager = FindFirstObjectByType<DialogueManager>();
    }

    private void Update()
    {
        GetPlayerPosition();
        AnimationControl();
        PlayerInteractions();
    }

    private void FixedUpdate()
    {
        MovePlayer();
    }

    private void GetPlayerPosition()
    {
        float x = playerControls.Player.Move.ReadValue<Vector2>().x;
        float z = playerControls.Player.Move.ReadValue<Vector2>().y;

        movement = new Vector3(x, 0, z).normalized;

        SpriteDirection(x);
    }

    private void MovePlayer()
    {
        // Remove movement control from player while in dialogue
        if (dialogueManager.DialogueActive)
            return;

        rb.MovePosition(transform.position + movement * speed * Time.fixedDeltaTime);
    }

    private void PlayerInteractions()
    {
        if (playerControls.Player.Interact.WasPerformedThisFrame() && availableDialogue?.GetObject() != null)
        {
            dialogueManager.StartDialogue(interactPrompt, availableDialogue.GetObject() );
        }

        // Debug functionality to easy get out of a dialogue and restart the scene
        if (playerControls.Player.Quit.WasPerformedThisFrame() && dialogueManager.DialogueActive)
        {
            dialogueManager.EndDialogue();
        }

        if (playerControls.Player.Restart.WasPerformedThisFrame())
        {
            RestartScene();
        }
    }

    private void AnimationControl()
    {
        //Idle to Walking
        animator.SetBool(IS_WALKING_PARAM, movement != Vector3.zero);
        //Idle to still when in dialogue
        animator.SetBool(IS_DIALOGUE_PARAM, dialogueManager.DialogueActive);
    }

    private void SpriteDirection(float x)
    {
        if (x != 0 && x < 0)
        {
            playerSprite.flipX = true;
        }

        if (x != 0 && x > 0)
        {
            playerSprite.flipX = false;
        }

    }

    private void OnTriggerEnter(Collider aOther)
    {
        if (aOther.CompareTag(IS_NPC_TAG))
        {
            var articyReferenceComp = aOther.GetComponent<ArticyReference>();
            if (articyReferenceComp != null)
            {
                availableDialogue = articyReferenceComp.reference;
            }

            interactPrompt = aOther.GetComponent<InteractPrompt>();
            if (interactPrompt != null && availableDialogue?.GetObject() != null)
            {
                interactPrompt.ShowInteractPrompt(true);
            }
        }
    }

    private void OnTriggerExit(Collider aOther)
    {
        if (aOther.CompareTag(IS_NPC_TAG))
        {
            if (aOther.GetComponent<InteractPrompt>() != null)
            {
                interactPrompt.ShowInteractPrompt(false);
            }
        }
    }

    private void RestartScene()
    {
        playerControls.Player.Disable();
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }
}

GO TO LESSON 4

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 community on discord.