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.
This topic contains the following sections:
Before we look at how you can work with the ArticyFlowPlayer we have to explain some fundamental concepts and clarify some wordings.
The process of flow traversal has a few parts that are important to keep in mind:
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.
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
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.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.
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.
Depending on how you setup unity your favorite IDE should open up, showing our new script file:
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 () { } }
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) { } }
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.
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:
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:
public void OnFlowPlayerPaused(IFlowObject aObject) { var dlgFragment = aObject as DialogueFragment; if(dlgFragment != null) { // it is a DialogueFragment! } }
As a small example, lets update a Unity UI text fields with the spoken text of a DialogueFragment.
public UnityEngine.UI.Text descriptionLabel; public void OnFlowPlayerPaused(IFlowObject aObject) { var dlgFragment = aObject as DialogueFragment; if(dlgFragment != null) { descriptionLabel.text = dlgFragment.Text; } }
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
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; } }
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:
public UnityEngine.UI.Text descriptionLabel; public void OnFlowPlayerPaused(IFlowObject aObject) { var objWithText = aObject as IObjectWithLocalizableText; if(objWithText != null) { descriptionLabel.text = objWithText.Text; } }
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:
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:
public void OnBranchesUpdated(IList<Branch> aBranches) { }
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.
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.
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:
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
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.
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 } }
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.
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. |