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

Please accept marketing cookies to watch this video.

Recap lesson 2

In the last episode we added the ArticyFlowPlayer component to our Dialogue Manager and worked on both PlayerController and DialogueManager scripts to create the basic functionality with which we can access articy data in our Unity game project. When playing through the scene we can now interact with an NPC to display the first line of a dialogue referenced to this object.

Recap lesson 2

This lesson

In this lesson we will refactor some code in the PlayerController script to tweak and improve some functionality and secondly add the speaker name to the dialogue UI.

Content lesson 3

Refactoring code

The PlayerController script works, but I am not entirely happy with the way it is set up currently. I’d like to use the ArticyObject, which we use to pass the dialogue to StartDialogue, to also make the check if the player is able to initiate a conversation.

I will rename articyObject to availableDialogue to make this double usage clearer. I click the variable name and then press F2 to rename it throughout this script. Again this is for Visual Studio 2019 on PC, your IDE might have different hotkeys. In the PlayerInteraction method we now have to update the if-statement from isNearNPC to availableDialogue. Then we can remove isNearNPC from the declaration area and the OnTriggerEnter and OnTriggerExit methods.

private ArticyObject availableDialogue;
private ArticyObject articyObject;
private bool isNearNPC = false;
	
void PlayerInteraction()
{
        if (Input.GetKeyDown(KeyCode.Space) && availableDialogue)
	if (Input.GetKeyDown(KeyCode.Space) && isNearNPC)
	
	
void OnTriggerEnter(Collider aOther)
{
	isNearNPC = true;
	availableDialogue = aOther.GetComponent<ArticyReference>().reference.GetObject();
}

void OnTriggerExit(Collider aOther)
{
	isNearNPC = false;    
}

I will save the script and do a quick test in Unity, if everything still works as expected. When I press the Space bar away from an NPC nothing happens, when pressing Space near the green NPC our dialogue UI opens like it is supposed to. And we even have an improvement in that nothing happens when we press Space near the red NPC. It has no dialogue referenced yet, and therefore it is absolutely correct that the dialogue UI is not activated here. Before, we checked only if the player was in the vicinity of an NPC object, not if this object had something to say or not.

Improved functionality due to refactoring

Nice. We got rid of an unnecessary variable and improved the functionality with this small refactoring. While we are here in Unity, we can also delete the DialogueHolder script, as it will no longer be used anywhere in this project.

Deleting DialogueHolder.cs

I’d like to do another tweak to the PlayController though. It works fine for the current situation, but if we start to include trigger elements without an ArticyReference we run into problems. If I remove the ArticyReference from NPC2 and test it, we get a Null Reference exception as soon as we enter the object’s trigger zone.

Removing ArticyReference component for test

Errors in console

To fix this we need to add a check to the OnTriggerEnter method in the PlayerController script.
I start by declaring a variable 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 the value of this object reference. This replaces the assignment we did previously for the availableDialogue variable.

void OnTriggerEnter(Collider aOther)
{
	var articyReferenceComp = aOther.GetComponent<ArticyReference>();
      	if (articyReferenceComp)
        {
		availableDialogue = articyReferenceComp.reference.GetObject();
		availableDialogue = aOther.GetComponent<ArticyReference>().reference.GetObject();
	}
}

That takes care of our null reference exception from before, but we need to take one more step, as of right now we still have the situation that if we interact with NPC1 or even just move through its trigger zone and then interact with NPC2 the dialogue is also shown for NPC2, which is not what we want.

We can take care of this issue with another if-statement in OnTriggerExit. We check if the ArticyReference component of our triggering object is not equal to null. If this is the case we set availableDialogue to null. If we enter a trigger zone and an ArticyReference exists we fetch it, if we leave the trigger zone and there is a reference it gets cleared.

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

That’s it. Works like a charm.

And before I forget I will revert the component removal on my NPC2 object, to have the ArticyReference in place when we need it later on.

Reverting removal of ArticyReference component

Adding speaker name

In our dialogue UI we have already planned to display the speaker name, let’s take care of that now. We already have this information, we only need to access it. This we will do in the OnFlowPlayerPaused method in the DialogueManager C# script.

First we want to find out if the object has a Speaker property and try to fetch it. We do this by declaring a variable objectWithSpeaker and initializing it to aObject cast to IObjectWithSpeaker. 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 we add an if-statement to 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 speakerEntity and initialize it to objectWithSpeaker.Speaker cast to Entity. We have some squiggly lines beneath Entity and Alt+Enter shows that we are missing a namespace. Don’t be confused by the name of this namespace, it is derived from the name of your articy project.

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

Adding new namespace

Now we are going to add another check: If speakerEntity is not equal to null then we assign dialogueSpeaker.text the value of speakerEntity.DisplayName. And same as before with the dialogue text, we also want to make sure not to display the same speaker multiple times as a potential issue when an object contains no speaker, and to that end we set dialogueSpeaker.text to an empty string at the top of the method.

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

Let’s save our script and switch to Unity to run a quick test. And voilà, we now can see that Kirian is the speaker for this line of dialogue.

Speaker name is now displayed in dialogue UI

Recap lesson 3

This will be all for this lesson. We refactored some code in the PlayController script, with the result that we got rid of an unnecessary variable and even improved the functionality of the OnTriggerEnter method over all. And secondly, we implemented displaying the speaker name in our dialogue UI.

Recap lesson 3

Next lesson

In the next lesson we will add functionality to the buttons of our dialogue UI, so that we can finally traverse through the entire linear test dialogue.

What's next?

See you there!

Current state of C# scripts we have worked on this lesson:

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;

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

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

    public void OnBranchesUpdated(IList<Branch> aBranches)
    {
        
    }
}

PlayerController.cs

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

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

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 communities on reddit and discord.