Click or drag to resize

Dialogues and Flow Traversal

articy:draft enables you to create flows using different node types and connecting them. You can use this to create your dialogues; your menu structure; your tech trees even your AI behaviour or anything else you can imagine. But creating your flows is only one part of the work, in articy:draft you can execute those flows using the Presentation view. The presentation view will follow your flow, guide by pins and connections and showing you relevant nodes in a PowerPoint®-like view.

If you need something similar to the presentation view that walks automatically through your flow charts in unity you use the ArticyFlowPlayer component.

Basic introduction to flow traversal

Before we look at how you can work with the ArticyFlowPlayer we have to explain some fundamental concepts and clarify some wordings.

flow parts

The process of flow traversal has a few parts that are important to keep in mind:

  • Nodes: Every articy:draft object that can be placed in the flow. e.g FlowFragment, Dialogue, Hub etc.
  • Pin: Most flow objects have 2 predefined or dynamic list of pins. Pins on the left side of the node are called InputPins while pins on the right side of a node are called OutputPins.
  • Connections: The cable between two nodes. A Connection always is connected via pins and always from one output pin to one input pin. A pin can have more than one connection.
  • Scripts: Every pin and the two specialized nodes Condition and Instruction are capable of containing script code, that the designer can use to manipulate the flow execution.

What does traversal mean?

When we talk about traversal we mean the semi-automatic process of visiting each object in our flow guided by the structure and connection of our objects.

Think of a dialogue between two people. Each Person saying one sentence, first person A says something then person B. The sentences in this case would be diagrammed using DialogueFragments. The order of who speaks first; who second is built using pins and connections. The process of traversal now just needs a start node and automatically flows along the nodes, pins and connections. Imagining this for a simple flow like this is easy, especially considering linear flows. But games are interactive which brings us to another important concept:

Branching

Branching itself is nothing special: Its a fork in the flow, where two or more potential routes could be taken. When you are building a non-linear game, where the player has alot of different choices to make you will naturally have a lot of these so called branches. We will talk about branches more because they play an important part in the way how to work with the ArticyFlowPlayer.

Setup ArticyFlowPlayer component

The ArticyFlowPlayer component is built to do all the heavy lifting for us in regards of flow traversal. Lets see how we can use it:

Like every other unity component we have to attach it first to one of our game objects in the scene

flow setup component
Once attached, we have a couple of options but we don't bother just yet, instead we pay attention to the "Implementation Warnings" area, notifying us about a missing callback interface.
flow setup warning

While the flow player tries to do as much as possible automatically, we want it to notify us about certain things. For example when we are building a dialog, it is of no use to us if the flow player just walks the flow and passing every dialogue fragment without telling us about it. So lets talk about a new concept called pause.

Pause

A pause target defines what objects we are interested in which means we want the flow player to interrupt its automatic traversal and notify us. For example: If we are building a system that shows dialogues in our UI we have to get notified about encountered DialogueFragments along our chosen flow.

flow setup pauseon
Using the PauseOn property on the flow player, we can fine tune which object types we want to get notified about.

As a sidenote: When the flow player pauses, it will wait indefinitely until we use one of the Play methods. More on that later.

But how do we get notified about a pause?

Execution Callbacks

If you have worked with C# interfaces before or are used to unitys new Event system, this part is easy, if you don't: Don't worry!

First lets attach a new script to the same game object with our flow player component.

flow setup createhandlerscript

Depending on how you setup unity your favorite IDE should open up, showing our new script file:

C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MyDialogueHandler : MonoBehaviour 
{
    // Use this for initialization
    void Start () 
    {
    }

    // Update is called once per frame
    void Update () 
    {
    }
}
Now we will modify this script to handle all the things the flow player has to tell us. But how do we know what these different things that the flow player can notify us about are? Thats what the IArticyFlowPlayerCallbacks interface is for. So lets modify our code to make our script class implement this interface.
C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Articy.Unity;

public class MyDialogueHandler : MonoBehaviour, IArticyFlowPlayerCallbacks 
{
    // Use this for initialization
    void Start ()
    {
    }

    // Update is called once per frame
    void Update () 
    {
    }

    public void OnFlowPlayerPaused(IFlowObject aObject)
    {
    }

    public void OnBranchesUpdated(IList<Branch> aBranches)
    {
    }
}
As you can see we have two new methods in our class. Both of these methods are basically events the flow player could encounter and wants to notify us. So everytime one of these methods are called it is the flow player of the same game object, pausing the automatic traversal and waiting till we have processed what we wanted to do.

And in fact, this is everything you need to setup your flow traversal in unity. Of course nothing is happening just yet, because we only built the foundation. It is now up to you and your goal with the flow player how you implement those two methods. In the next section we will give you some ideas and guidelines how to do it.

How to implement OnFlowPlayerPaused()

As mentioned before, the purpose of OnFlowPlayerPaused(IFlowObject) is for the ArticyFlowPlayer to notify us about something in the flow that was marked as important via the pauseOn setting.

Lets see what we can do with this method:

C#
public void OnFlowPlayerPaused(IFlowObject aObject)
{
}

The most important thing about this method is the argument aObject passed into it. Not suprisingly it is the object that the flow player paused on, but its type IFlowObject is something new. To put it simple: IFlowObject is the basic type of anything the FlowPlayer can encounter while traversing.. That means every node, every pin and every connection is also an IFlowObject. If for some reason the passed aObject is null, we have encountered a Dead End.

But how can we distinguish between the different types? By writing code that will test if the passed object is actually of the type we are interested in. Lets see how to do this:

C#
public void OnFlowPlayerPaused(IFlowObject aObject)
{
    var dlgFragment = aObject as DialogueFragment;
    if(dlgFragment != null)
    {
        // it is a DialogueFragment!
    }
}
Here we use the C# as operator to perform a cast into the type DialogueFragment. The good thing about the as operator is, that if the cast fails, i.e. the aObject is not of the type DialogueFragment, no exception will be thrown instead our variable will just contain null. As you can see with the following if statement we verify that our dlgFragment variable is not null. At that point, if it is not null, we know it is a DialogueFragment and we can work with it.

As a small example, lets update a Unity UI text fields with the spoken text of a DialogueFragment.

C#
public UnityEngine.UI.Text descriptionLabel;

public void OnFlowPlayerPaused(IFlowObject aObject)
{
    var dlgFragment = aObject as DialogueFragment;
    if(dlgFragment != null)
    {
        descriptionLabel.text = dlgFragment.Text;
    }
}
Here we have the text label, set in the inspector, and after the cast as shown above, we just access the Text property of our DialogueFragment and assign it to the text property of the unity text control.

Some properties like Color, DisplayName, Text etc. appear on multiple types. Using what we have learned till now, we would have to write code like this

C#
public UnityEngine.UI.Text descriptionLabel;

public void OnFlowPlayerPaused(IFlowObject aObject)
{
    var dlgFragment = aObject as DialogueFragment;
    if(dlgFragment != null)
    {
        descriptionLabel.text = dlgFragment.Text;
    }

    var flwFragment = aObject as FlowFragment;
    if(flwFragment != null)
    {
        descriptionLabel.text = flwFragment.Text;
    }
}
Please don't do it like that.

This would increase our code by a lot of copy and paste and creates difficult to maintain code. For that reason all of those shared properties have a special interface that every type shares that has this property. We call these object property interfaces. Lets fix the previous example:

C#
public UnityEngine.UI.Text descriptionLabel;

public void OnFlowPlayerPaused(IFlowObject aObject)
{
    var objWithText = aObject as IObjectWithLocalizableText;
    if(objWithText != null)
    {
        descriptionLabel.text = objWithText.Text;
    }
}
Here we use the object property interface IObjectWithLocalizableText to refere to all object that have the property text. Our code now works for FlowFragments, DialogueFragments, Dialogues etc.

Note Note
If your text property is not marked as localizable, you need to use the property interface IObjectWithText instead of IObjectWithLocalizableText.

Now as mentioned above how you implement this method is highly up to you and your use case.

Here are some ideas to get you going:

  • Access the Speaker asset of a DialogueFragment to update an image in your UI of the current speaker.
  • Create a special DialogueFragment template in articy draft. Create new Feature and place a Slot property in it, constrainted to only allow assets. Use this property to attach sound files to your DialogueFragment. In unity you can access these Templates, access the sound asset attached and play it. To synchronize the dialogue with the traversal you can use Invoke() passing a delay as long as the audio assets length and calling the appropriate Play method or presenting the different branches to the user.
  • The FlowPlayer can also be used for non dialogues. You could let it traverse the flow, pause on nodes, do something and immediately call Play again without the need for the user to interact. A primitive AI could be built like this.

How to implement OnBranchesUpdated()

The method OnBranchesUpdated(IListBranch) is always called after OnFlowPlayerPaused(IFlowObject) to tell us about the potential paths to continue the traversal.

Lets first look at the method itself:

C#
public void OnBranchesUpdated(IList<Branch> aBranches)
{
}
Similar to OnFlowPlayerPaused(IFlowObject) we have only one argument passed into it and in this case its a list of branches. But what is a Branch?

Branch

When the flow player pauses it will gather every upcoming path, put them into a list and pass this list into OnBranchesUpdated. So in other words Branches are a look into the future showing us potential routes the flow player could continue the flow.

To give a clearer example, consider this small flow of 3 DialogueFragments.

flow branching example
Lets imagine that our flow player is paused on the first DialogueFragment"Unknown Voice: Unfortunately ...". Once paused the flow player will call our OnFlowPlayerPaused() passing in the current DialogueFragment and as mentioned before will now wait until we call one of the Play methods. Directly following the OnFlowPlayerPaused() call it will also call the OnBranchesUpdated(IListBranch) method. To prepare the argument for the method, the flow player went and checked via forecasting which branches are available from the current pause.

In the example above we get 2 branches one branch to the upper DialogueFragment"Manfred: I don't know..." and another to the lower DialogueFragment"Manfred: I'll get you out,...". But now that we have these branches, what are they used for?

There are 2 main purposes for a Branch: The first and most important is a way to unpause the flow:

How to unpause the flow player using a Branch.

As mentioned before, to unpause the flow player you have to call one of the Play methods. But lets look closer at one specific overload of the Play method as the other overloads are just convenience and in fact use this one under the hood.

Play(Branch): This method will unpause the flow player but needs a specificBranch as an argument to know where the flow player will continue the automatic traversal. So in other words we have to tell the flow player which branch to play.

Now that we know that we need our Branches to choose where to continue the flow, we look at the second purpose of branches: Holding information where that branch leads to.

The Branch object contains informations about the next pause when traversing this route called Target. You can use this to build an UI showing the MenuTexts of all upcoming DialogueFragment for example. You also have access to all objects that the flow player would encounter following this Path. And of course a flag telling us if this branch is actually valid to traverse, more on that later.

Similar to the OnFlowPlayerPaused() method it is highly dependant on what you want to achieve with it. While difficult to show in a small example, lets check this piece of code as a proof of concept both callbacks work together.

C#
using Articy.Unity;
using Articy.Unity.Interfaces;
using System.Collections.Generic;
using UnityEngine;

public class MyDialogueHandler : MonoBehaviour, IArticyFlowPlayerCallbacks
{
    // every pause we remember the very first branch, so we can continue the flow using it
    private Branch firstBranch;

    public void OnFlowPlayerPaused(IFlowObject aObject)
    {
        // we just print every text to the console
        var textObject = aObject as IObjectWithLocalizableText;
        if (textObject != null)
        {
            Debug.Log(textObject.Text);
        }
    }

    public void OnBranchesUpdated(IList<Branch> aBranches)
    {
        // just store the first branch on every pause
        if (aBranches.Count > 0)
            firstBranch = aBranches[0];
    }

    // Update is called once per frame
    void Update()
    {
        // Once paused the flow player waits until we call Play() to let it continue, that way we can finish reading the text we
        // printed to the console, and when we are done, we hit Space to finally let the flow player resume.
        if (Input.GetKeyUp(KeyCode.Space))
        {
            if (firstBranch != null)
                GetComponent<ArticyFlowPlayer>().Play(firstBranch);
        }
    }
}

So to summarize:

  • When the flow player pauses, it will always call OnBranchesUpdated() directly after OnFlowPlayerPaused()
  • A Branch is one potential route from the current pause.
  • Use a Branch to unpause the flow player by passing it into Play(Branch).
  • Use the Branches properties to present each branch to the user when building multiple choice dialogs.

FlowPlayer and Scripting

Lucky for us, the flow player will take care of executing all the conditions and instructions encountered while traversing the flow. But how exactly does this work?

There are 2 distinct script elements with different purposes

  • Input Pins or Conditions are used to test if a Branch is valid while the flow player forecasts it.
  • Output Pins or Instructions to change global variables or call script methods in response to passing this object.

Input Pins and Conditions

When working with scripts in the flow, you usually want to ignore branches where the condition to follow that branch is false. The flow player will call all the necessary scripts and will store the result in the IsValid property of every branch. You just have to make sure to only work with branches where IsValid is true.

C#
public void OnBranchesUpdated(IList<Branch> aBranches)
{
    foreach(var branch in aBranches)
    {
        // we only want branches that are valid
        if(!branch.IsValid) continue;

        // ... work with our valid branch
    }
}

Output Pins or Instructions

Instruction are even easier, because we have to do nothing. We only have to remember that instructions are only called when the object is passed. This means if we have paused on an OutputPin/Instruction the underlying script is not yet executed, we have to continue once and leave the pause to get it executed.

When working with GlobalVariables the flow player component has a handy view of all global variables. When you expand the foldout Variable default values you will find all your namespaces and their variables. You can even modify their values here for debug purposes.

flow script variableview
Note Note

Multiple ArticyFlowPlayer will share the same default global variables instance. You can uncheck the "Use default global variables" option and provide a new GlobalVariables instance.

See Also