Hello and welcome to lesson 7 of the articy:draft Importer for Unity tutorial series.

Recap lesson 6

In the last lesson 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.

Recap lesson 6

This lesson

With this lesson we have arrived at the end of this tutorial series. We will see what is necessary to handle more complex dialogues, which use some scripting logic and global variables. We will conclude the lesson with some tips on how you can continue building upon this foundation we have built throughout the series.

Lesson 7 content

Displaying complex dialogues

Both our test dialogues run flawlessly, but how does it look like if the dialogue gets bigger, and if some story logic gets added to the mix? Let’s take a look, shall we? We have a good overview over the dialogues in articy. Both use a bool to check if the player triggered the dialogue already and if yes starts it with a slight variation.

Bool checks if dialogue has been triggered before

In addition to that we have a branch option in dialogue 2 that only is available if we followed a certain path in dialogue 1, this we also check with a Boolean.

Extra branch option in dialogue 2 if bool was set in dialogue 1

Let’s set up the final dialogues in Unity. All we need to do is to select the NPC object and change the reference target in the Articy Reference component. Dialogue 1 for NPC 1 and Dialogue 2 for NPC 2.

Setting ArticyReference targets for NPC and NPC2

Okay, and what do we now have to do to get this story logic working in the Unity project? Not much! The Flow Player takes care of most of that in the background out of the box. Besides changing the reference for which dialogue to start we haven’t made a single change since last lesson. So let’s run a test and see what happens.

In Play mode I select the articy tab and click on ‘Show default global variables’. With that we can monitor how variable values change while playing. In case you don’t have the articy tab, you can activate it via Tools / articy:draft importer / Show database panel.

Show default global variables in Articy tab

Current variable values displayed in Articy tab

If you have articy:draft and Unity side by side and want to follow simultaneously, you can press “attach to articy:draft” in the articy flow player component. The current object in Unity will be selected in articy as well. As I can record only one screen, I will simulate this behavior, by showing articy and Unity side by side for the playthrough.

Select Attach to articy:draft to have current node highlighted in articy

When interacting with the green NPC the new dialogue gets displayed. Right before the hub we have an instruction in the output pin of the node, in which we change the value of dialogue1Visited to true. In the database panel you will notice that this change only happens when we actually choose one of the options. This is because the Flow Player is pausing on the Dialogue Fragment where Kirian asks if he can help us, until we select an option and with that start the Flow Player again. Only then the instruction gets processed. This is why we added a command to the CloseDialogueBox method to finish processing the current node before ending the dialogue, to not lose potentially important information.

Dialogue1Visited variable gets set to true

If during our conversation with Kirian we select the ‘no idea’ branch, we get a tip what we could ask the Oracle later, and with that assigning a true value to the gotTip bool.

gotTip bool gets set

The dialogue ends and we can go and talk to the red cube, the Oracle. Again we are setting a bool in an output pin to be able to check for a repeated triggering of the dialogue later. If we are about to end the dialogue we included a condition that checks if the player received Kirian’s tip in dialogue 1. If you skipped the branch or maybe talking to him entirely the dialogue ends, if we got the tip then we get some additional information from the Oracle.

If gotTip returns true, extra dialogue option is available

If we interact with either NPC again, we automatically follow the second branch of the dialogue flow, skipping the introductory chit-chat. Because the value of the dialogueVisited variable is now true, this makes the second branch the only valid option to follow.

Second flow branch gets followed in 2nd playthrough

All this happens without us having to write a single line of additional code! Is this cool or what?

We achieved the goal we set for ourselves at the start of this tutorial series. We have set up a workflow to properly display dialogue we imported from articy:draft in the custom dialogue UI within our Unity project. Now writers and designers can make changes in articy and are able to check out these changes in the engine within moments.

articy:draft improves workflow

I can do things like updating some text, adding another branch to the dialogue, or renaming an entity, and with – literally – a few clicks this updated data gets imported to our Unity project where it can be tested and verified.

Adding another dialogue option

New dialogue option is visible in Unity right after data import

New dialogue gets displayed in Unity

Checking for end of dialogue

There is one thing I like to revisit. In lesson 4 we started to add button functionality and set up a check to see whether the dialogue continues or ends. This we did by looking if the target of the current branch is going to be a Dialogue Fragment.

But if we look at our current dialogue, we have elements in there that are clearly not Dialogue Fragments, like a jump, a hub, or a condition node. Still the dialogue does not end when getting to these points. Why not?

Non Dialogue Fragment nodes in dialogue

In lesson 4 I stated: “we know that the dialogue is over when no Dialogue Fragment can be found as the following branch target.” However this branch target does not necessarily have to be the very next element, like we see it in the Flow. It depends on the On Pause settings we’ve done in the ArticyFlowPlayer component. We have only selected Dialogue Fragments for the Flow Player to pause on, that means in this dialogue only Dialogue Fragments will appear as valid stop targets.

if (branch.Target is IDialogueFragment)
{
	dialogueIsFinished = false;
}

If we are at this position, the condition node would be processed to determine which branch to follow, but as we don’t stop on these nodes the forecasting would continue and depending on variable value either arrive at the next Dialogue Fragment, which would set dialogueIsFinished to a false value, and therefore continue the dialogue.

Forecasting finds Dialogue Fragment -> dialogue continues

Or we get to the end of the Dialogue node which is not connected to any other node, therefore making it impossible to find a DialogueFragment as a target, thus ending the dialogue.

Forecasting does not find Dialogue Fragment -> dialogue ends

Generally speaking, it makes sense to have the flow player stop only on elements that have data we need to fetch, or that we want to display. That can vary greatly depending on your project. If you know where you need to pause, you can properly set up your checks. For example, if we would add Flow Fragments as an additional element to pause on, we would need to update our check for the end of a dialogue accordingly.

Resetting variable values

There is one last thing I want to add to the code. If we reset the scene with the R key, the variable values we changed through articy are not reset. If we want to do that, we need to add one additional line of code to the RestartScene method in the PlayerController script: ArticyGlobalVariables.Default.ResetVariables. And as highlighted by the squiggly red line, we need to add the required namespace to the script as well.

using Articy.UnityImporterTutorial.GlobalVariables;	

void RestartScene()
{
	ArticyGlobalVariables.Default.ResetVariables();
}

Adding required namespace to PlayerController.cs

Additional resources

What else can we do with articy and Unity? A lot more! You could take care of items, use articy location data, access templates, employ localization support… Basically all data you create in articy, you can bring over to Unity and make use of it there.

I highly recommend the technical documentation for the articy Importer for Unity. In there you find guides how to accomplish certain tasks and it also contains the complete reference information.

Technical documentation for articy:draft Importer for Unity

For a more hands-on experience you can take a look at the Maniac Manfred demo project. It consists of projects for both articy and Unity and shows more advanced use cases for the Importer.

Advanced demo project Maniac Manfred

To stay informed in all topics related to articy, for news about the games industry, or to get in touch with us, follow us on Twitter. We love to hear from our users!

All the best and see you around!

Final versions of all C# scripts:

BranchChoice.cs

using UnityEngine;
using Articy.Unity;
using Articy.Unity.Interfaces;
using UnityEngine.UI;

// This is the UI control for a single option within a dialogue
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);
        }
    }
}

PlayerController.cs

using UnityEngine;
using UnityEngine.SceneManagement;
using Articy.Unity;
using Articy.UnityImporterTutorial.GlobalVariables;

public class PlayerController : MonoBehaviour
{
    private float speed = 15f;
    
    private Rigidbody playerRB;
    private DialogueManager dialogueManager;
    private ArticyObject availableDialogue;
 
    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) && availableDialogue)
        {
            dialogueManager.StartDialogue(availableDialogue);
        }

        // 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()
    {
        ArticyGlobalVariables.Default.ResetVariables();
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }

    // Trigger Enter/Exit used to determine if interaction with NPC is possible
    void OnTriggerEnter(Collider aOther)
    {
        var articyReferenceComp = aOther.GetComponent<ArticyReference>();
        if (articyReferenceComp)
        {
            availableDialogue = articyReferenceComp.reference.GetObject();
        }
    }

    void OnTriggerExit(Collider aOther)
    {
        if (aOther.GetComponent<ArticyReference>() != null)
        {
            availableDialogue = null;
        }
    }
}

Useful links:

Technical documentation
Maniac Manfred project
Articy Twitter channel
Articy Reddit

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.