Hello and welcome to lesson 3 of the articy:draft Importer for Unity tutorial series.
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.
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.
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.
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.
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.
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.
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; }
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.
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.
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.
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; } } }
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.