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:
- Duplicate resolution: Any other action already using the pressed key has its binding swapped with the target action's previous key.
- Storage update: The new
KeyCodeis committed to the active table viaKeyBindings.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.csas three static dictionaries (Primary,SecondaryA,SecondaryB). - Runtime selection:
SettingsStorage.KeyBindingTypedetermines which table is active, allowing instant switching between control schemes. - Abstraction layer:
InputServiceroutes Unity input checks through the active binding table so gameplay code remains layout-agnostic. - Live rebinding:
SettingsControllerprovides a UI for capturing new keys, resolving duplicates by swapping conflicting bindings, and persisting changes immediately. - Programmatic access: Static methods
GetKeyCode,SetKeyCode, andResetDictionaryallow 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →