# How to Extend the Core Systems of Undertale-Changer-Template: A Complete Guide

> Extend Undertale-Changer-Template by adding custom tag handlers UI panels utility services and battle state logic Discover how to enhance core systems with this complete guide.

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

---

**Extend Undertale-Changer-Template by implementing custom tag handlers in [`TypeWritterTagProcessor.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/TypeWritterTagProcessor.cs), creating new `SettingsLayerBase` subclasses for UI panels, adding utility services to the `UCT.Service` namespace, and implementing `IBattleConfig` or `IState` interfaces for battles and player states.**

Undertale-Changer-Template (UCT) is a modular Unity framework designed for Undertale-style RPG development. The repository separates game logic, UI, services, and configuration into distinct namespaces, allowing developers to extend core systems without altering base engine code. This guide provides concrete implementation paths using actual source files from `arch-aik/undertale-changer-template`, covering everything from custom dialogue tags to finite state machines.

## Extending the TypeWriter Tag Processor

The **tag processor** in `UCT.Core.TypeWritterTagProcessor` parses rich-text tags for the dialogue system. UCT supports three extension points: **static tags** (pre-processed), **half-tags** (inline with parameters), and **full tags** (complex game state triggers).

### Adding Static Tags (Preprocessor)

Static tags resolve before the typewriter animation begins. They are ideal for inserting dynamic values like player statistics into dialogue text.

Locate the `StaticTagHandlers` dictionary around line 500 in [`Assets/Scripts/UCT/Core/TypeWritterTagProcessor.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Core/TypeWritterTagProcessor.cs). Add an entry using `MethodWrapper<string>`:

```csharp
["<playerHP>"] = new MethodWrapper<string>(args =>
    (string)args[0] + MainControl.Instance.playerControl.hpCurrent.ToString()),

```

The delegate receives the accumulated text in `args[0]` and returns the modified string. Now `<playerHP>` resolves to the current health value in any Ink or TextMeshPro dialogue.

### Adding Half-Tags (Dynamic Inline)

**Half-tags** process during typewriter execution and accept parameters. They can temporarily modify typing behavior or trigger immediate UI changes.

Define half-tags in the `HalfTagHandlers` dictionary around line 660. These use `MethodWrapper<int>` and receive the `TypeWritter` instance as `args[0]`:

```csharp
["<speed="] = new MethodWrapper<int>(args =>
{
    var typeWritter = (TypeWritter)args[0];
    var speedParam = ((string)args[2]).TrimEnd('>');
    typeWritter.speed = speedParam switch
    {
        "fast"   => 0.05f,
        "slow"   => 0.2f,
        _        => typeWritter.speed
    };
    return (int)args[3];
}),
["</speed>"] = new MethodWrapper<int>(args =>
{
    var typeWritter = (TypeWritter)args[0];
    typeWritter.speed = SettingsStorage.TypingSpeed switch
    {
        TypingSpeed.Slow   => 0.2f,
        TypingSpeed.Medium => 0.1f,
        TypingSpeed.Fast   => 0.05f,
        _                  => typeWritter.speed
    };
    return (int)args[3];
}),

```

Use `<speed=fast>` to accelerate typing and `</speed>` to restore defaults from `SettingsStorage`.

### Adding Full Tags (Game State Triggers)

**Full tags** handle complex logic that affects game state, such as spawning NPCs or starting battles. They utilize `MethodWrapper<FullTagData>` and are processed by `TypeWritterExecuteHalfTag`.

First, create the event method in [`Assets/Scripts/UCT/EventSystem/EventController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/EventSystem/EventController.cs):

```csharp
public static void SpawnHiddenNpc()
{
    var prefab = Resources.Load<GameObject>("Prefabs/HiddenNPC");
    Object.Instantiate(prefab, new Vector3(10, 0, 5), Quaternion.identity);
}

```

Then register the tag in `FullTagHandlers` around line 640:

```csharp
["<spawnHiddenNpc>"] = new MethodWrapper<FullTagData>(args =>
{
    var data = (FullTagData)args[3];
    EventController.SpawnHiddenNpc();
    data.ProceedToDefault = false;
    return data;
}),

```

## Creating Custom Settings Layers

The settings system uses `SettingsLayerBase` subclasses to render UI panels. To add a new "Gameplay" settings tab without modifying existing controllers:

1. Create a new class inheriting from `SettingsLayerBase`:

```csharp
// Assets/Scripts/UCT/Settings/GameplaySettingsLayer.cs
using UnityEngine.UIElements;

public class GameplaySettingsLayer : SettingsLayerBase
{
    public override string LayerName => "Gameplay";

    protected override VisualElement CreateContent()
    {
        var container = new VisualElement();
        var autoSaveToggle = new Toggle("Enable Auto‑Save");
        autoSaveToggle.value = SettingsStorage.AutoSaveEnabled;
        autoSaveToggle.RegisterValueChangedCallback(evt =>
        {
            SettingsStorage.AutoSaveEnabled = evt.newValue;
        });
        container.Add(autoSaveToggle);
        return container;
    }
}

```

2. Register the layer in [`Assets/Scripts/UCT/Settings/SettingsController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Settings/SettingsController.cs):

```csharp
private readonly List<SettingsLayerBase> _layers = new()
{
    new HomeSettingsLayer(),
    new VideoSettingsLayer(),
    // ... existing layers ...
    new GameplaySettingsLayer(),
};

```

3. Add persistent storage in [`Assets/Scripts/UCT/Settings/SettingsStorage.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Settings/SettingsStorage.cs):

```csharp
public static bool AutoSaveEnabled { get; set; } = true;

```

## Adding Service Layer Utilities

Reusable utilities belong in the `UCT.Service` namespace. Create static classes in `Assets/Scripts/UCT/Service/` for cross-cutting concerns like probability calculations or text manipulation.

Example implementation of [`ProbabilityService.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/ProbabilityService.cs):

```csharp
using System;

public static class ProbabilityService
{
    private static readonly System.Random _rng = new();

    public static bool Roll(float percent) =>
        percent >= 100f || (percent > 0f && _rng.NextDouble() < percent / 100.0);
}

```

Reference this anywhere in the project:

```csharp
if (ProbabilityService.Roll(25f))
{
    EventController.SpawnHiddenNpc();
}

```

## Implementing Custom Battle Configurations

UCT discovers battle configurations automatically via reflection. Implementing `IBattleConfig` requires no registration boilerplate.

Create a new battle definition:

```csharp
// Assets/Scripts/UCT/Battle/SecretBossConfig.cs
public class SecretBossConfig : IBattleConfig
{
    public string BattleName => "Secret Boss";
    public int EnemyId => 999;
    public int MaxTurns => 30;
}

```

The `EventController.GetIBattleConfig` method (around line 170 in [`EventController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/EventController.cs)) discovers this class automatically. Trigger it via a full tag:

```csharp
["<startSecretBattle>"] = new MethodWrapper<FullTagData>(args =>
{
    var data = (FullTagData)args[3];
    GameUtilityService.StartBattle(new SecretBossConfig());
    return data;
}),

```

## Extending Overworld Player States

The player controller uses a finite state machine (FSM) defined in `UCT.Overworld.FiniteStateMachine`. Add new behaviors by implementing `IState` and registering them in the player controller.

Create a crouch state:

```csharp
// Assets/Scripts/UCT/Overworld/FiniteStateMachine/CrouchState.cs
using UnityEngine;

[CreateAssetMenu(menuName = "Fsm/State/Crouch")]
public class CrouchState : IState
{
    public void Enter(FiniteStateMachine fsm)
    {
        fsm.Animator.SetBool("Crouch", true);
    }

    public void Execute(FiniteStateMachine fsm)
    {
        if (!InputService.GetKey(KeyCode.C)) 
            fsm.ChangeState(fsm.PreviousState);
    }

    public void Exit(FiniteStateMachine fsm)
    {
        fsm.Animator.SetBool("Crouch", false);
    }
}

```

Register in [`Assets/Scripts/UCT/Overworld/OverworldPlayerBehaviour.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/Assets/Scripts/UCT/Overworld/OverworldPlayerBehaviour.cs):

```csharp
private void Awake()
{
    RegisterState<IdleState>();
    RegisterState<WalkState>();
    RegisterState<CrouchState>();
}

```

Define the input key in your key bindings to expose it to the settings UI if desired.

## Summary

- **Tag Processor**: Extend [`TypeWritterTagProcessor.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/TypeWritterTagProcessor.cs) with `MethodWrapper` delegates for static, half, and full tags to inject dynamic text or trigger game events.
- **Settings UI**: Derive from `SettingsLayerBase`, register in [`SettingsController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/SettingsController.cs), and persist values through [`SettingsStorage.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/SettingsStorage.cs).
- **Services**: Add static helper classes to `Assets/Scripts/UCT/Service` for project-wide utilities.
- **Battles**: Implement `IBattleConfig` in any class; `EventController` discovers it automatically via reflection.
- **Player States**: Implement `IState` and register via `RegisterState<T>()` in [`OverworldPlayerBehaviour.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/OverworldPlayerBehaviour.cs) to extend player mechanics.

## Frequently Asked Questions

### What is the difference between static, half, and full tags in UCT?

**Static tags** preprocess text before typewriter animation starts using `MethodWrapper<string>`. **Half-tags** execute during animation with `MethodWrapper<int>`, receiving the active `TypeWritter` instance to modify fields like typing speed. **Full tags** use `MethodWrapper<FullTagData>` for complex state changes like spawning objects or starting battles, and they control whether the typewriter proceeds to default behavior via the `ProceedToDefault` flag.

### How do I add a new settings panel without modifying the core UI code?

Create a class inheriting from `SettingsLayerBase` in the `UCT.Settings` namespace, implement `CreateContent()` to return a `VisualElement` tree, and add your subclass to the `_layers` list in [`SettingsController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/SettingsController.cs). Store persistent values as static properties in [`SettingsStorage.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/SettingsStorage.cs) to ensure they save via Unity's `PlayerPrefs`.

### Can I create custom battle encounters without changing the event system architecture?

Yes. Implement the `IBattleConfig` interface in a new class (e.g., `SecretBossConfig`). The [`EventController.cs`](https://github.com/arch-aik/undertale-changer-template/blob/main/EventController.cs) automatically discovers implementations via reflection around line 170. Trigger battles using `GameUtilityService.StartBattle(new YourConfig())` inside a full tag handler or any game logic.

### Where should I place utility helper functions in the project structure?

Place static utility classes in `Assets/Scripts/UCT/Service/` following the existing service pattern (e.g., `TextProcessingService`, `MathService`). These classes compile automatically with the project and remain accessible throughout the `UCT` namespace without creating tight coupling to specific game objects.