How Key Bindings Are Configured and Managed in the Undertale-Changer Template

The Undertale-Changer Template centralizes key binding management through a static repository in KeyBindings.cs that stores three default mapping tables, a runtime selector in SettingsStorage.KeyBindingType to switch between them, and a configuration UI in SettingsController.cs that enables players to reassign keys without restarting the game.

The arch-aik/undertale-changer-template repository implements a flexible input system for Unity-based Undertale fangames. Understanding how key bindings are configured and managed allows developers to customize default layouts, add new actions, or create custom input handling while maintaining compatibility with the existing settings UI.

Default Binding Tables in KeyBindings.cs

All default key mappings live in Assets/Scripts/UCT/Settings/KeyBindings.cs. This static class defines three distinct KeyBindingType tables—Primary, SecondaryA, and SecondaryB—each mapping logical action names (e.g., MoveDown, Confirm, Backpack) to Unity KeyCode values.

The Three KeyBindingType Tables

The _keyBindings dictionary initializes the default layouts in static field initializers:

private static Dictionary<KeyBindingType, Dictionary<string, KeyCode>> _keyBindings = new()
{
    { KeyBindingType.Primary, new Dictionary<string, KeyCode>
        { { MoveDown, KeyCode.DownArrow }, { Confirm, KeyCode.Z }, … } },
    { KeyBindingType.SecondaryA, new Dictionary<string, KeyCode>
        { { MoveDown, KeyCode.S }, { Confirm, KeyCode.Return }, … } },
    { KeyBindingType.SecondaryB, new Dictionary<string, KeyCode>
        { { MoveDown, KeyCode.None }, { Confirm, KeyCode.None }, … } }
};

Source: KeyBindings.cs lines 30-84

Selecting the Active Binding Table

The template uses SettingsStorage.cs to track which binding table is active at runtime. The KeyBindingType property defaults to Primary but can be switched to SecondaryA or SecondaryB to instantly change the control scheme.

public static KeyBindingType KeyBindingType { get; set; } = KeyBindingType.Primary;

Source: SettingsStorage.cs line 34

To cycle between tables programmatically, the SettingsController uses an enum increment helper:

SettingsStorage.KeyBindingType = EnumService.IncrementEnum(SettingsStorage.KeyBindingType);

Source: SettingsController.cs line 411

Querying Keys During Gameplay

Gameplay code never hardcodes KeyCode values. Instead, InputService.cs provides a centralized wrapper that translates logical actions into the current binding lookup.

When checking for input, InputService retrieves the full dictionary from KeyBindings and maps specific KeyCode queries to their logical indices:

public static bool GetKeyDown(KeyCode key)
{
    var keyCodes = KeyBindings.GetDictionary();            // all tables
    return key switch
    {
        KeyCode.DownArrow => GetKeyDownFrom(keyCodes, 0),  // index 0 → MoveDown
        KeyCode.Z => GetKeyDownFrom(keyCodes, 4),          // index 4 → Confirm

        _ => throw new ArgumentNullException($"Unknown {key}")
    };
}

Source: InputService.cs lines 14-24

The internal GetKeyDownFrom method checks the active KeyBindingType against the stored KeyCode using Unity's Input.GetKeyDown:

private static bool GetKeyDownFrom(Dictionary<KeyBindingType, Dictionary<string, KeyCode>> keyCodes, int index)
{
    var result = keyCodes.Any(keyBinding
        => KeyBindings.GetInputEveryKeyCodeAtIndex(index, keyBinding, Input.GetKeyDown));
    return result;
}

Source: InputService.cs lines 36-44

Runtime Key Reassignment

The SettingsController.cs handles the interactive rebinding UI. When a player selects a ConfigurableKeyFalse option, the controller enters a listening state (_isSettingKey = true) and waits for the next key press via GetSettingKeyControl().

Upon capturing input, the system performs two critical operations:

  1. Duplicate resolution: Any other action already using the pressed key has its binding swapped with the target action's previous key.
  2. Storage update: The new KeyCode is committed to the active table via KeyBindings.SetKeyCode.
var oldKeyCodeStorage = KeyBindings.GetKeyCode(SettingsStorage.KeyBindingType, dataName);
...
KeyBindings.SetKeyCode(SettingsStorage.KeyBindingType, dataName, pressedKeycode);

Source: SettingsController.cs lines 757-771

Players can also restore factory defaults through a reset handler that invokes KeyBindings.ResetDictionary():

private static void GetKeyDownToKeyBindingsReset()
{
    if (InputService.GetKeyDown(KeyCode.Z))
    {
        KeyBindings.ResetDictionary();
    }
}

Source: SettingsController.cs lines 90-96

Programmatic API Reference

Developers can manipulate bindings directly through the static KeyBindings class without using the UI:

Operation Method Example
Get a key KeyBindings.GetKeyCode(type, actionName) KeyCode confirm = KeyBindings.GetKeyCode(KeyBindingType.Primary, "Confirm");
Set a key KeyBindings.SetKeyCode(type, actionName, key) KeyBindings.SetKeyCode(KeyBindingType.Primary, "Confirm", KeyCode.Space);
Set by index (used for UI iteration) KeyBindings.SetKeyCodeAtIndex(type, index, key) KeyBindings.SetKeyCodeAtIndex(KeyBindingType.Primary, 4, KeyCode.Space);
Reset all KeyBindings.ResetDictionary() KeyBindings.ResetDictionary();

Sources: KeyBindings.cs lines 85-108 (set/get), 98-108 (by index), 139-155 (reset)

Practical Code Examples

Query a Binding in Gameplay

Gameplay scripts should use InputService rather than Unity's raw Input class to respect the player's chosen layout:

using UCT.Service;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    void Update()
    {
        // Move based on the *currently selected* key-binding table
        if (InputService.GetKeyDown(KeyCode.DownArrow))
            MoveDown();

        if (InputService.GetKeyDown(KeyCode.Z))   // "Confirm" action
            ConfirmSelection();
    }
}

Change a Key Programmatically

Switch the active table or modify specific bindings without UI interaction:

using UCT.Settings;
using UnityEngine;

// Switch the whole table to the secondary set
SettingsStorage.KeyBindingType = KeyBindingType.SecondaryA;

// Re-assign the "Backpack" button in the primary table
KeyBindings.SetKeyCode(KeyBindingType.Primary, "Backpack", KeyCode.B);

Reset All Bindings to Defaults

Restore factory defaults across all three tables:

using UCT.Settings;

// Restores the three default tables (Primary, SecondaryA, SecondaryB)
KeyBindings.ResetDictionary();

Hook into the Configuration UI

Trigger the settings screen from custom scripts:

using UCT.Settings;
using UnityEngine;

public class ShortcutOpener : MonoBehaviour
{
    void Update()
    {
        // Press V in overworld to open the Settings UI (the template already does this)
        if (InputService.GetKeyDown(KeyCode.V) && !overworldControl.isSetting)
            SettingsController.Instance.OpenSetting();
    }
}

Summary

  • Centralized storage: All default mappings reside in KeyBindings.cs as three static dictionaries (Primary, SecondaryA, SecondaryB).
  • Runtime selection: SettingsStorage.KeyBindingType determines which table is active, allowing instant switching between control schemes.
  • Abstraction layer: InputService routes Unity input checks through the active binding table so gameplay code remains layout-agnostic.
  • Live rebinding: SettingsController provides a UI for capturing new keys, resolving duplicates by swapping conflicting bindings, and persisting changes immediately.
  • Programmatic access: Static methods GetKeyCode, SetKeyCode, and ResetDictionary allow scripts to manipulate bindings directly without UI interaction.

Frequently Asked Questions

Where are the default key bindings defined in the Undertale-Changer Template?

Default key bindings are defined in Assets/Scripts/UCT/Settings/KeyBindings.cs within the static _keyBindings dictionary. This field initializes three tables—Primary, SecondaryA, and SecondaryB—that map logical action names like MoveDown and Confirm to specific Unity KeyCode values such as KeyCode.DownArrow and KeyCode.Z.

How does the game know which key binding set to use at runtime?

The active binding set is determined by the SettingsStorage.KeyBindingType property, which defaults to KeyBindingType.Primary. When a player selects a different control scheme in the settings menu, or when code assigns a new value such as KeyBindingType.SecondaryA, InputService immediately begins querying the corresponding dictionary from KeyBindings.cs without requiring a scene reload.

Can players rebind keys without restarting the game?

Yes. The SettingsController.cs handles live rebinding through a listening state triggered by ConfigurableKeyFalse options. When activated, the controller captures the next key press via GetSettingKeyControl(), resolves duplicate bindings by swapping conflicting keys, and commits the change instantly using KeyBindings.SetKeyCode. The new binding is active immediately in the game session.

How do I reset key bindings to default values programmatically?

To restore all bindings to their original defaults, invoke the static KeyBindings.ResetDictionary() method. This routine repopulates the internal dictionaries with the factory defaults defined in the static constructor or field initializers of KeyBindings.cs, affecting all three tables (Primary, SecondaryA, and SecondaryB) simultaneously. This is the same method called when the player presses the reset key in the settings UI.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →