Unity + Ink: Part 5: Ink Variables and Functions

Unity + Ink

The narrative scripting language Ink works with the game engine Unity through a plugin that allows for quickly recompiling, testing, and integrating Ink files in a Unity project.


Ink Variables and Functions

To close out this series, two more aspects of working working with Ink in Unity need to be examined: variables and functions.

Global Variables

In Ink, all variables created using the VAR keyword are global. Their values can be accessed and changed anywhere in a project. External programs can also change them as well.

As part of the Story API, the property variablesState is an accessor to any global variables available in the story.

An updated version of the previous Ink code now containing a global variable looks like the following:


VAR name = "Freya"

"What? Have you <i>never</i> seen a dialogue system before, {name}?" #Dan

* ["No. Of course I have!"]
    "That's good. As this is the beginnings of one. 
    
    "It even has <b>multiple</b> lines of dialogue.
    
    "That's neat, right?" #Dan
    -> DONE

As any variables defined at the beginning of a story exist outside of its content, they can be changed before loading anything.


// Add a new Text component to the new GameObject
Text newTextObject = newGameObject.AddComponent<Text>();
// Set the fontSize larger
newTextObject.fontSize = 24;

// Access the global variable and change its value
story.variablesState["name"] = "Alva";

// Load the next block and save text (if any)
string text = getNextStoryBlock();

 // Get the current tags (if any)
 List<string> tags = story.currentTags;

Now, instead of using the value set in Ink, the Unity C# code is overwriting it during run-time.

Calling Functions

While working with variables directly is one approach to working with the values within a Ink story, there is also another: calling functions.

Like with the variables, it is also possible to call functions in Ink from Unity. The function to do this is EvaluateFunction(). It accepts the name of the function to call and any parameters to pass to the function as comma-separated values.

Returning to the Ink code, it could be updated to the following where, now, a function changes the internal value, leaving the work up to Ink on how to handle things internally.


VAR name = "Freya"

"What? Have you <i>never</i> seen a dialogue system before, {name}?" #Dan

* ["No. Of course I have!"]
    "That's good. As this is the beginnings of one. 
    
    "It even has <b>multiple</b> lines of dialogue.
    
    "That's neat, right?" #Dan
    -> DONE

=== function changeName(newName) ===
~ name = newName

In the Unity code, the change can happen on the same line as the previous variablesState usage.


 // Add a new Text component to the new GameObject
 Text newTextObject = newGameObject.AddComponent<Text>();
 // Set the fontSize larger
newTextObject.fontSize = 24;

// Access the global variable and change its value
story.EvaluateFunction("changeName", "Alva");

// Load the next block and save text (if any)
string text = getNextStoryBlock();

The result is the same.

Why would I need to change a variable or call an Ink function in Unity?

When working with Unity, additional UI elements can be added. Some of these, like InputField, can be used to accept data from a user. In a game context, collecting a name from a user and then changing the value in Ink to match is an easy and common example. Another use case might be to update values after loading a save file or some settings.

InkExample.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Ink.Runtime;
public class InkExample : MonoBehaviour
{
public TextAsset inkJSONAsset;
private Story story;
public Button buttonPrefab;
// Start is called before the first frame update
void Start()
{
// Load the next story block
story = new Story(inkJSONAsset.text);
// Start the refresh cycle
refresh();
}
// Refresh the UI elements
// – Clear any current elements
// – Show any text chunks
// – Iterate through any choices and create listeners on them
void refresh()
{
// Clear the UI
clearUI();
// Create a new GameObject
GameObject newGameObject = new GameObject("TextChunk");
// Set its transform to the Canvas (this)
newGameObject.transform.SetParent(this.transform, false);
// Add a new Text component to the new GameObject
Text newTextObject = newGameObject.AddComponent<Text>();
// Set the fontSize larger
newTextObject.fontSize = 24;
// Access the global variable and change its value
story.EvaluateFunction("changeName", "Alva");
// Load the next block and save text (if any)
string text = getNextStoryBlock();
// Get the current tags (if any)
List<string> tags = story.currentTags;
// If there are tags, use the first one.
// Otherwise, just show the text.
if (tags.Count > 0)
{
newTextObject.text = "<color=grey>" + tags[0] + "</color> – " + text;
}
else
{
newTextObject.text = text;
}
// Load Arial from the built-in resources
newTextObject.font = Resources.GetBuiltinResource(typeof(Font), "Arial.ttf") as Font;
foreach (Choice choice in story.currentChoices)
{
Button choiceButton = Instantiate(buttonPrefab) as Button;
choiceButton.transform.SetParent(this.transform, false);
// Gets the text from the button prefab
Text choiceText = choiceButton.GetComponentInChildren<Text>();
choiceText.text = " " + (choice.index + 1) + ". " + choice.text;
// Set listener
choiceButton.onClick.AddListener(delegate {
OnClickChoiceButton(choice);
});
}
}
// When we click the choice button, tell the story to choose that choice!
void OnClickChoiceButton(Choice choice)
{
story.ChooseChoiceIndex(choice.index);
refresh();
}
// Clear out all of the UI, calling Destory() in reverse
void clearUI()
{
int childCount = this.transform.childCount;
for (int i = childCount 1; i >= 0; i)
{
GameObject.Destroy(this.transform.GetChild(i).gameObject);
}
}
// Load and potentially return the next story block
string getNextStoryBlock()
{
string text = "";
if (story.canContinue)
{
text = story.ContinueMaximally();
}
return text;
}
// Update is called once per frame
void Update()
{
}
}
view raw InkExample.cs hosted with ❤ by GitHub

2 thoughts on “Unity + Ink: Part 5: Ink Variables and Functions

  1. Congrats for the tutorial, I think it’s the best I could find. Thank you very much for it!!
    I would like to ask the following question.
    What should we do if we want to offer an option to write the name the player wants? Of course, we must create a new function in the cs script, but is it needed to include new elements (like the button) on the Unity project? Like a pop up where the player can write from the keyboard.
    Sorry for my writing, hehe, I’m not an english speaker, so I do what I can.
    Thank you very much!

    1. Dan Cox

      Yes, a new button would work to start. You would probably also want something with Text Input that would allow a player to enter their name and have the button save that data in a way that Ink could access and use later.

Comments are closed.