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

In this lesson we will start integrating the articy Importer functionality, to be able to interact with an NPC to start a linear dialogue, display the first dialogue line in our UI, and also the current speaker’s name.

The ArticyFlowPlayer component

First order of business is to add an ArticyFlowPlayer component (1) to the Dialogue Manager game object. This is the core component that is needed to unlock the functionality of the articy Importer for Unity.

Articy Flow Player component added to DialogueManager game object

Don’t mind the yellow warning signs for the time being!

DialogueManager.cs

Now we open the DialogueManager script in our IDE.

There are a number of field declarations in here already, that hold references to the UI elements. A GameObject dialogueWidget and TMP_Text dialogueText as well as dialogueSpeaker.

Next is a bool property DialogueActive which is used for displaying or hiding the UI elements.

The StartDialogue method activates the Dialogue Widget, the EndDialogue method hides the dialogue UI again.

When you look at the script in Unity, you will notice some declarations here regarding cameras and for the interaction prompt, which are then used within the two methods. However, these elements are part of the base functionality of the project and will not be touched or changed during our work to display narrative content. For that reason, and to keep the excerpts nice and short, elements might be omitted when showing part of the code here, to focus on areas that actively get worked on currently.

[DialogueManager.cs]

[...]

public class DialogueManager : MonoBehaviour
{
    [Header("UI")]
    // Reference to Dialogue UI
    [SerializeField]
    GameObject dialogueWidget;
    // Reference to Dialogue text
    [SerializeField]
    TMP_Text dialogueText;
    // Reference to speaker
    [SerializeField]
    TMP_Text dialogueSpeaker;

    [...]
    		
    public bool DialogueActive { get; set; }

    [...]

    public void StartDialogue(InteractPrompt aPrompt)
    {            
        DialogueActive = true;
        dialogueWidget.SetActive(DialogueActive);
    
        [...]
    }

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

        [...]      
    }

}

You will always find a complete version of each script used during a lesson in its current state at the end of each blog post.

We need to set up the ArticyFlowPlayer component to use it in this script.

For that I add a private field of type ArticyFlowPlayer which I will call flowPlayer.

We need the Articy.Unity namespace. Depending on your IDE that might have been added automatically when declaring flowPlayer, otherwise please add it manually now.

In the Start method we initialize flowPlayer by getting its component.

[DialogueManager.cs]

using Articy.Unity;

[...]

private ArticyFlowPlayer flowPlayer;

[...]

private void Start()
{
       	flowPlayer = GetComponent<ArticyFlowPlayer>();
}

[...]

The articy Flow Player does most of its magic in the background. It traverses through the Flow, handles branching, evaluates conditions, executes instructions, and will make callbacks to our game code.

However, there are still a few things that need to be set up first. We need to implement IArticyFlowPlayerCallbacks. To get rid of the red squiggly lines its methods have to be implemented as well. In my IDE (Visual Studio 2026), I use Alt+Enter and select ‘Implement Interface’ from the drop-down menu.

With the implementation two new methods have been added to the script: OnFlowPlayerPaused and OnBranchesUpdated.

[DialogueManager.cs]

public class DialogueManager : MonoBehaviour, IArticyFlowPlayerCallbacks
{
        [...]

	public void OnFlowPlayerPaused(IFlowObject aObject)
	{
    		throw new System.NotImplementedException();
	}

	public void OnBranchesUpdated(IList aBranches)
	{
 		throw new System.NotImplementedException();
	}
}

When the Flow Player gets to a pause or arrives at a branching point somewhere along its way, we can extract information from the node it paused on and tell the Flow Player what to do next, with the help of these methods.

What does ‘pausing’ mean in this context? Let’s switch over to the Unity editor for a minute.

Select the DialogueManager game object (2) and look for the ArticyFlowPlayer component in the Inspector (3). There we have the Start On (4) and Pause On (5) options.

Start On and Pause On

Start On sets the object the Flow Player starts its execution on. In a short time, we will set it dynamically, depending on the dialogue an NPC has. Now, for the first step of implementation, I will click the object picker (6) and select Linear Test Dialogue (7)

Set Start On property

Pause On allows us to set on which articy node types the Flow Player will pause its traversal, allowing us to get information from them. For this project I will set the option to only pause on Dialogue Fragments.

Set Pause On property

Displaying a first dialogue line

Let’s go back to the DialogueManager.cs script.

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 on the object it is paused on and if there is, display that text in the dialogue UI.

That we will do inside the OnFlowPlayerPaused method.

First, we remove the throw keyword. While we are at it, let’s also remove the throw from OnBranchesUpdated, because otherwise it is going to be annoying when the console gets flooded with exceptions from that.

[DialogueManager.cs]

[...]

public void OnFlowPlayerPaused(IFlowObject aObject)
{
    throw new System.NotImplementedException();
}

public void OnBranchesUpdated(IList aBranches)
{
    throw new System.NotImplementedException();
}

Then we declare a variable var objectWithText in OnFlowPlayerPaused and initialize it to aObject, which is the argument passed into the method, cast to IObjectWithLocalizableText.

aObject is 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 to IObjectWithLocalizableText. The cast requires the namespace Articy.Unity.Interfaces. Please check if your IDE added it automatically, otherwise add it manually now. Check the technical documentation if you want to learn more about the interfaces the articy Importer provides.

Next, we check if the object has the Text property. If true, assign its value to the text property of the 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.

[DialogueManager.cs]

[...]

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

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

[...]

Now we can save the script and return to the Unity editor.

Set the Dialogue Widget (8) on the Canvas to active (9) and hit Play (10).

Set Dialogue Widget to active for test

We can see the first line of the dialogue imported from articy displayed. We cannot continue through the dialogue as the buttons are not functional yet. Still, working as intended: The Flow Player pauses on the first Dialogue Fragment and its text gets displayed in our UI.

We display first line of dialogue from articy data

Trigger dialogue by interaction with NPC

Next order of business is to set up the StartDialogue method in a way that allows us to start a dialogue by interacting with an NPC.

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 DialogueManager game object, as we want to have control over starting a dialogue from now on.

Remove Start On reference form Flow Player component

Next, we go to the Prefab folder (11) and select the NPC prefab (12) to add an Articy Reference component (13).

Adding an Articy Reference to the NPC prefab

This component holds a single ArticyRef field. ArticyRef is a weak reference to an articy object. ArticyRef fields can be used to expose articy objects safely in Unity components. Here, we use the convenient ArticyReference component as a quick solution to store a reference to an articy dialogue object on an NPC.

Articy Reference component

You can also use ArticyRef variables in your own components. Find more information on Articy References here.

Save and exit the Prefab and go to the NPC 1 game object (14). Here, we select Linear Test Dialogue as the target of the Articy Reference component (15).

Setting a dialogue reference for NPC 1

Now, back to some coding in the DialogueManager.cs script. We expand StartDialogue with a second parameter of type ArticyObject with the name aObject.

We use flowPlayer, our stored reference to the Flow Player component, with the property StartOn and assign the value of aObject to it.

[DialogueManager.cs]

[...]

public void StartDialogue(InteractPrompt aPrompt, ArticyObject aObject)
{            
    
    [...]

    flowPlayer.StartOn = aObject;
}

In addition to the existing functionality of setting the Dialogue Widget active, hiding the Interact prompt, and adjusting the camera, the StartDialogue method now also starts the Flow Player on the object received as an argument.

After saving the DialogueManager.cs script you might have noticed an error showing up in the Unity console. This happens, because we only pass one argument when calling StartDialogue in the PlayerController.cs script and the method now expects two.

Script error

Open PlayerController.cs. We start by declaring a private field of type ArticyRef with the name availableDialogue. Same as in the DialogueManager.cs, the Articy.Unity namespace is needed now. Add it to the script, in case your IDE did not automatically do it when declaring the ArticyRef field.

This articyRef we add as the second argument to the call of StartDialogue in the PlayerInteractions method.

In the PlayerInteractions method, for the call of StartDialogue, we add availableDialogue as the second argument and call GetObject on it, to receive the object from the reference.

[PlayerController.cs]

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

public class PlayerController : MonoBehaviour
{
    [...]	    

    private ArticyRef availableDialogue;

    [...]
    
    private void PlayerInteractions()
    {
        if (playerControls.Player.Interact.WasPerformedThisFrame() && isNearNPC)
        {
            dialogueManager.StartDialogue(interactPrompt, availableDialogue.GetObject());          
        }

        [...]
    }

[...]

}

What we are still missing is getting the actual dialogue reference from the NPC object so it will actually be passed to StartDialogue.

In the OnTriggerEnter method we already check if the player collided with an object with the NPC tag and if that NPC contains the InteractPrompt component.

For now, we can add getting the ArticyReference inside the first if-clause. As we put an ArticyReference component with a reference to a dialogue on NPC 1, we can access that component now. We assign our availableDialogue field the value of the articy object reference that the aOther ArticyReference component holds.

[PlayerController.cs]

[...]

private void OnTriggerEnter(Collider aOther)
{
    if (aOther.CompareTag(IS_NPC_TAG))
    {            
        availableDialogue = aOther.GetComponent<ArticyReference>().reference;
        
        [...]                           
    }        
}

Let’s jump over to the Unity editor for a quick test, before I would like to make another small change with help of availableDialogue.

Please accept marketing cookies to watch this video.

If we move up to NPC 1 and use the interact key ‘E’ 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. Just remember to set it back to inactive after testing.

Please accept marketing cookies to watch this video.

It works, but if you move up to NPC 2, who has no reference to a dialogue yet, we still can interact and get the dialogue window. Not a huge issue, as NPCs you can interact with should have dialogue at some point, but we can improve it with only minimal changes to the code.

In PlayerController.cs in PlayerInteractions, I will replace isNearNPC with a check for availableDialogue and its object not being null.

Now we can remove the isNearNPC field declaration from the class and from the OnTriggerEnter and OnTriggerExit methods.

[PlayerController.cs]

[...]

private bool isNearNPC = false;

[...]

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

    [...] 
}

[...]

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

        interactPrompt = aOther.GetComponent();        
        if (interactPrompt != null)
        {
            interactPrompt.ShowInteractPrompt(true);

            isNearNPC = true;
        }            
    }        
}

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

[...]

We are half-way there. When we go to NPC 2 in Play Mode now, we can no longer get the Dialogue Widget to appear. Although, we still see an Interact prompt. Let’s get rid of that as well, just to be thorough.

In the second if-clause in OnTriggerEnter, where we check for interactPrompt, we add a check for availableDialogue and its object not being null.

[PlayerController.cs]

[...]

private void OnTriggerEnter(Collider aOther)
{
    [...]

        if (interactPrompt != null && availableDialogue?.GetObject() != null)
        {
            interactPrompt.ShowInteractPrompt(true);
        }
    }
}

Please accept marketing cookies to watch this video.

Now the Interact prompt is only shown when the NPC has a reference to a dialogue.

To add another layer of protection against exceptions, we can add another check around the assignment of availableDialogue in OnTriggerEnter.

We start by declaring a variable var articyReferenceComp and initialize it to the ArticyReference component of the object that caused the trigger event. Then we create an if-statement to check if this component was found (i.e., if it is not equal to null). If there is an ArticyReference on the object we assign availableDialogue this object reference.

[PlayerController.cs]

[...]

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

        availableDialogue = aOther.GetComponent<ArticyReference>().reference

        [...]
    }
}

[...]

Speaker name

There is one more thing, I’d like to accomplish in this lesson and that is to add the name of the currently speaking character to the dialogue UI as well.

We already have the necessary data in our DialogueManager.cs script, exposed to the editor, and filled with a reference to the corresponding element from the Dialogue Widget (16). All that is missing is to grab the speaker from the articy data and display it.

[DialogueManager.cs]

[...]

[SerializeField]
private TMP_Text dialogueSpeaker;

[...]

Speaker name reference

This we will do in the OnFlowPlayerPaused method in the DialogueManager.cs script.

Below the code for the dialogue line, we declare a variable var objectWithSpeaker and initialize it to aObject cast to IObjectWithSpeaker. With that we want to find out if the object has a Speaker property and try to fetch it.

With aObject we access the same argument passed into the method as for the dialogue text, but we cast it into a different interface, to access different parts of object data without making assumptions about its exact type.

Now, in a similar fashion to the dialogue line, we check if objectWithSpeaker is not equal to null. If the object has a Speaker property we will fetch the reference and cast it to an Entity to be able to access its Display Name. For that we declare a second variable var speakerEntity and initialize it to objectWithSpeaker.Speaker cast to Entity.

Completing the last line adds another namespace to this class. It is derived from the technical name of the articy project and in this case using Articy.Adx_unitytutorial. Same as before, if your IDE does not add the namespace automatically, please add it manually now.

[DialogueManager.cs]

using Articy.Adx_unitytutorial;

[...]

public void OnFlowPlayerPaused(IFlowObject aObject)
{    
    [...]
    
    var objectWithSpeaker = aObject as IObjectWithSpeaker;
    if (objectWithSpeaker != null)
    {
        var speakerEntity = objectWithSpeaker.Speaker as Entity;
    }
}

[...]

When adding the namespace manually, if you start typing using Articy. the available namespaces should be listed so you can select the one derived from your project name. In case you like to change this name, here is how to find the technical name of your articy project:
Select the topmost entry in the Navigator (17), right-click and select Open Properties from the context menu (18). In the properties window, you can find the technical name of your project (19).

Locate Technical Name of articy:draft X project

Back to getting the speaker name. Next is another check: If speakerEntity is not equal to null, we assign the value of speakerEntity.DisplayName to dialogueSpeaker.text.

And lastly, to avoid displaying a wrong speaker name in case an object does not contain a speaker, we set dialogueSpeaker.text to an empty string at the top of the method.

[DialogueManager.cs]

[...]

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

    [...]

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

[...]

Save the script, switch to the Unity editor and enter Play Mode. When talking to NPC 1, the speaker name now appears as ‘Player’, which is indeed the correct name, as we can verify in the articy:draft project.

Speaker name in Unity and articy

Next lesson

We have laid a solid foundation on which we can build. In the next lesson we will add some functionality to the buttons of the dialogue UI, so that we can finally see more than only the first line of the conversation.

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;

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;

    [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>();
    }

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

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

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

        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)
    {

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

Useful links:

Technical documentation Unity Importer – Interfaces
Technical documentation Unity Importer – Articy References

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.