# How to Implement Custom Dialogue Using Ink in Undertale-Changer-Template

> Learn to implement custom dialogue in Undertale-Changer-Template using Ink. Author stories in Ink, place them in language packs, and trigger with the loadInk tag for branching narrative.

- Repository: [Archived AIk/undertale-changer-template](https://github.com/arch-aik/undertale-changer-template)
- Tags: how-to-guide
- Published: 2026-02-25

---

**You can implement custom dialogue in Undertale-Changer-Template by authoring stories in Ink, placing them in language pack folders, and triggering them with the `<loadInk=FileName>` tag, which the TypeWritterTagProcessor parses to load stories via InkService and manage branching through TypeWritterSelectController.**

Undertale-Changer-Template is an open-source Unity framework that embeds the Ink narrative scripting language to handle complex dialogue trees. By leveraging the Ink runtime, you can implement custom dialogue using Ink in Undertale-Changer-Template without writing additional C# logic for every conversation. The integration automatically handles compilation, loading, and choice presentation through a tag-based system.

## Understanding the Ink Integration Architecture

The template’s Ink integration consists of three core components that handle compilation, tag-based loading, and choice management.

### InkService Compilation and Loading

The `InkService` class located in [`Assets/Scripts/UCT/Service/InkService.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Service/InkService.cs) (lines 19-45) handles the heavy lifting of Ink file processing. When you request a story, `InkService` checks whether a compiled JSON version exists or if the source `.ink` file is newer. If compilation is required, it uses Ink’s `Compiler` to generate the JSON runtime format. The service provides two primary methods: `ReadInkJsonFileFromLocalPath` for user-added language packs stored in the local file system, and `ReadInkJsonFileFromResources` for built-in packs stored in Unity’s Resources folder.

### TypeWritterTagProcessor Tag Parsing

The rich-text tag system in [`Assets/Scripts/UCT/Core/TypeWritterTagProcessor.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Core/TypeWritterTagProcessor.cs) (lines 587-605) enables runtime loading of Ink stories through the custom `<loadInk=FileName>` tag. When the type-writer encounters this tag, the processor extracts the filename, determines whether to load from the Resources folder (for built-in language packs) or a local path (for user-added packs), and calls `InkService` to retrieve the compiled story. The processor then attaches the story to the `TypeWritterSelectController` and immediately appends the first block of dialogue to the current text stream.

### TypeWritterSelectController Choice Management

Once loaded, the Ink story is managed by `TypeWritterSelectController` in [`Assets/Scripts/UCT/Core/TypeWritterSelectController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Core/TypeWritterSelectController.cs) (lines 10-33). This controller wraps the Ink `Story` object, exposes `currentChoices`, and provides `GetStoryDialogue()` to advance text until choice points. When the player makes a selection, the controller calls `Story.ChooseChoiceIndex(index)` to advance the narrative, and the type-writer continues rendering the next dialogue block.

## Step-by-Step Implementation Guide

Follow these steps to implement custom dialogue using Ink in your project:

1. **Create an Ink file.** Write your dialogue narrative in a `.ink` file (e.g., `MyScene.ink`). Place it under `Assets/LanguagePacks/<LangId>/Ink/` for built-in language packs, or in a custom folder for user-added packs.

2. **Compile the story (optional).** At runtime, `InkService.ReadInkJsonFileFromLocalPath` automatically compiles the `.ink` file to JSON if a newer version is missing. Alternatively, pre-compile using the Ink CLI and ship the `.json` directly to skip runtime compilation.

3. **Insert the load tag.** Inside any type-writer string, add the `<loadInk=FileName>` tag (without extension). For example: `"The forest is quiet... <loadInk=MyScene>"`. The processor strips the tag and injects the Ink story content.

4. **Run the scene.** When the type-writer reaches the tag, `TypeWritterTagProcessor` calls `InkService` to load the story (from Resources or local path depending on `languagePackId`) and attaches it to `typeWritter.SelectController.SetStory(story)`.

5. **Handle choices.** When the story reaches a choice point, `TypeWritter` queries `SelectController.Story.currentChoices` and renders the UI. Player selection triggers `SelectController.Story.ChooseChoiceIndex(index)`, and `GetStoryDialogue()` retrieves the next text block.

6. **Test in Unity.** Play the scene in the editor. Verify the Ink block appears, choices display correctly, and the story advances on selection. Check the console for Ink compile warnings forwarded via `Debug.LogWarning`.

## Code Implementation Examples

### Loading Ink Files via the Tag Handler

The following excerpt from [`TypeWritterTagProcessor.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/TypeWritterTagProcessor.cs) demonstrates how the `<loadInk=...>` tag is processed internally:

```csharp
// 1️⃣ Load an Ink file at runtime (handled by the <loadInk=…> tag)
private static int LoadInkHandler(object[] args)
{
    // args[2] is the raw tag text, e.g. "<loadInk=MyScene>"
    string fileName = ((string)args[2])[9..^1];   // strip "<loadInk=" and trailing ">"
    string pathPrefix;

    // Language pack detection – see TypeWritterTagProcessor for full logic
    if (MainControl.Instance.languagePackId < MainControl.LanguagePackageInternalNumber)
    {
        // Built‑in pack: Resources folder
        pathPrefix = $"TextAssets/LanguagePacks/{DataHandlerService.GetLanguageInsideId(languagePackId)}/Ink/";
        story = InkService.ReadInkJsonFileFromResources(pathPrefix + fileName);
    }
    else
    {
        // User‑added pack: local file system
        pathPrefix = $@"{Directory.GetDirectories(Application.dataPath + "/LanguagePacks")
                         [languagePackId - MainControl.LanguagePackageInternalNumber]}\Ink\";
        story = InkService.ReadInkJsonFileFromLocalPath(pathPrefix + fileName);
    }

    // Attach story to the current type‑writer
    var typeWritter = (TypeWritter)args[0];
    typeWritter.SelectController.SetStory(story);

    // Pull the first block of dialogue and append it to the running text
    string dialogue = typeWritter.SelectController.GetStoryDialogue();
    typeWritter.originString += dialogue;
    return (int)args[3];
}

```

### Sample Ink Dialogue Script

Create your narrative logic in a dedicated `.ink` file. This example demonstrates basic branching:

```ink
// 2️⃣ Simple Ink file (MyScene.ink)
=== start ===
You meet a mysterious figure.

*   [Greet them]   -> greet
*   [Ignore them] -> ignore

=== greet ===
"Hello, traveler!" -> END

=== ignore ===
You walk away, hearing their whisper fade. -> END

```

### Triggering Dialogue in Unity

Invoke the Ink story by including the load tag in any string passed to the type-writer:

```csharp
// 3️⃣ Using the tag in a Unity UI string
string cutsceneText = "The forest is quiet... <loadInk=MyScene>";
typeWritter.StartTyping(cutsceneText);

```

## Key Source Files and Their Roles

| File | Purpose |
|------|---------|
| [`Assets/Scripts/UCT/Service/InkService.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Service/InkService.cs) | Compiles `.ink` files to JSON at runtime (if needed) and returns `Ink.Runtime.Story` objects via `ReadInkJsonFileFromLocalPath` or `ReadInkJsonFileFromResources`. |
| [`Assets/Scripts/UCT/Core/TypeWritterTagProcessor.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Core/TypeWritterTagProcessor.cs) | Parses the `<loadInk=FileName>` tag (lines 587-605), determines whether to load from Resources or local paths, and injects the story content into the type-writer stream. |
| [`Assets/Scripts/UCT/Core/TypeWritterSelectController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Core/TypeWritterSelectController.cs) | Wraps the Ink `Story` object, exposes `currentChoices`, and provides `GetStoryDialogue()` to retrieve text blocks until choice points are reached. |
| `Assets/LanguagePacks/<LangId>/Ink/` | Default location for packaged Ink files intended for built-in language packs. |
| `Assets/Resources/TextAssets/LanguagePacks/<LangId>/Ink/` | Resources-based path for built-in packs accessed via `ReadInkJsonFileFromResources`. |

## Summary

- **Ink Integration**: The template embeds the open-source Ink runtime to handle branching dialogue through three core components: `InkService` for compilation, `TypeWritterTagProcessor` for tag-based loading, and `TypeWritterSelectController` for choice management.
- **Tag-Based Loading**: Use the `<loadInk=FileName>` tag in any type-writer string to dynamically load Ink stories from either built-in Resources folders or user-added local language packs.
- **Runtime Compilation**: `InkService` automatically compiles `.ink` files to JSON at runtime if no up-to-date compiled version exists, or you can pre-compile with the Ink CLI for faster loading.
- **Choice Handling**: The `TypeWritterSelectController` exposes Ink choices through `currentChoices` and advances the narrative via `ChooseChoiceIndex()`, seamlessly integrating branching paths into the Undertale-style type-writer UI.

## Frequently Asked Questions

### Where do I place custom Ink files in the project structure?

Place built-in Ink files under `Assets/LanguagePacks/<LangId>/Ink/` or `Assets/Resources/TextAssets/LanguagePacks/<LangId>/Ink/` for packaged language packs. For user-added packs that load at runtime, place them in a custom folder under `Application.dataPath + "/LanguagePacks"` with an `Ink/` subdirectory. The `TypeWritterTagProcessor` automatically detects whether to use `ReadInkJsonFileFromResources` (built-in) or `ReadInkJsonFileFromLocalPath` (user-added) based on the current `languagePackId`.

### How does the template handle Ink compilation at runtime?

The `InkService` class in [`Assets/Scripts/UCT/Service/InkService.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Service/InkService.cs) handles compilation automatically. When you call `ReadInkJsonFileFromLocalPath`, the service checks if a compiled JSON exists and whether the source `.ink` file is newer. If compilation is required, it uses Ink’s `Compiler` class to generate the JSON runtime format. You can also pre-compile your Ink files using the official Ink CLI and ship only the JSON files to eliminate runtime compilation overhead.

### Can I use Ink choices and branching in Undertale-Changer-Template?

Yes, the template fully supports Ink choices and branching narratives. When the story reaches a choice point, the `TypeWritterSelectController` exposes the `currentChoices` collection from the underlying Ink `Story` object. The `TypeWritter` renders these choices using its built-in UI, and when the player selects an option, the controller calls `Story.ChooseChoiceIndex(index)` to advance the narrative. The next call to `GetStoryDialogue()` retrieves the subsequent text block until the next choice or the end of the story.

### What is the difference between built-in and user-added language packs for Ink?

Built-in language packs reside in Unity’s Resources folders (`Assets/Resources/TextAssets/LanguagePacks/`) and are accessed via `InkService.ReadInkJsonFileFromResources`. These are packaged with the build and identified by `languagePackId` values less than `MainControl.LanguagePackageInternalNumber`. User-added packs are stored in the local file system under `Application.dataPath + "/LanguagePacks"` and are accessed via `InkService.ReadInkJsonFileFromLocalPath`. These allow players to add custom language packs at runtime without rebuilding the project, and they use higher `languagePackId` values offset by `LanguagePackageInternalNumber`.