Key Components of the Settings System in Undertale-Changer Template: A Data-Driven Architecture Guide

The settings system uses a data-driven architecture that separates persisted values, UI definitions, and navigation layers, allowing developers to declare menu options without writing custom rendering logic.

The Undertale-Changer template provides a modular, extensible settings subsystem built for Unity. Understanding the key components of the settings system enables developers to customize video, audio, and input configurations while maintaining clean separation between data storage and presentation logic.

The Three-Pillar Architecture

The system organizes functionality into three distinct concerns:

  1. Data Persistence – Static storage for runtime values
  2. Option Definition – Declarative descriptions of menu items
  3. Layer Management – Hierarchical grouping of related options

This separation allows the SettingsController to render any menu generically, driving the UI purely from the metadata defined in each SettingsOption.

SettingsStorage: The Persistent Value Layer

SettingsStorage is a static class located at Assets/Scripts/UCT/Settings/SettingsStorage.cs that acts as the single source of truth for all configurable values. It exposes properties for resolution, volume levels, key bindings, and fullscreen state that both the UI and game systems read directly.

The class maintains a dictionary called CubismSettingsLayers that stores the hierarchy of available menu layers, enabling runtime navigation between settings categories.

SettingsOption: Defining Menu Items

SettingsOption (defined in Assets/Scripts/UCT/Settings/SettingsOption.cs) represents a single entry in the settings menu. Each instance declares:

  • Display metadataDataName and DescriptionDataName for localization
  • Option typeSelectionToggle, SelectionBasedFalse, ConfigurableKeyTrue, or SelectionBasedTrue
  • Data binding – Getter and setter delegates (SelectionBasedChangedValueGetter/Setter) that link the UI to SettingsStorage
  • Interaction callbacksOnSelected actions triggered when the user activates the option

This declarative approach allows complex options like volume sliders or key rebinders to be defined with simple property assignments rather than custom UI code.

ISettingsLayer and Concrete Implementations

The ISettingsLayer interface and SettingsLayerBase abstract class (located in Assets/Scripts/UCT/Settings/ISettingsLayer.cs) provide the structure for grouping related options into menu screens.

Each concrete layer inherits from SettingsLayerBase and populates its AllSettingsOptions list in the constructor. The template includes eight predefined layers defined as region blocks within ISettingsLayer.cs:

  • HomeSettingsLayer – Root menu entry point
  • VideoSettingsLayer – Resolution, fullscreen, VSync, and frame-rate caps
  • AudioSettingsLayer – Master, BGM, and SFX volume controls
  • GraphicSettingsLayer – Quality presets and effect simplification
  • InputSettingsLayer – Navigation entry for key binding configuration
  • SettingKeyControlLayer – Concrete key rebinders for actions like MoveUp, Confirm, and Settings
  • SettingLanguagePackageLayer – Localization pack selection
  • SubtitleSettingsLayer – Text display speed and subtitle toggles

The base class provides helper methods like AddBackOptionForDisplay() and AddConfigurableKeyOption() to standardize common UI patterns across layers.

SettingsController: The UI Driver

SettingsController (in Assets/Scripts/UCT/Settings/SettingsController.cs) orchestrates the runtime behavior of the settings menu. It maintains the navigation stack (_settingsPreviousLayers), tracks the currently selected option index, and rebuilds the displayed text whenever the user changes layers or modifies values.

The controller queries the active layer's AllSettingsOptions collection to determine available choices, invokes the getter delegates to display current values, and executes setter delegates when the user confirms changes. This generic handling allows the controller to manage any future layer without modification.

Practical Implementation Examples

Reading a Setting from Gameplay Code

Access persisted values directly through the static SettingsStorage class:

// Check fullscreen state before adjusting UI layout
if (SettingsStorage.FullScreen)
{
    canvasScaler.referenceResolution = new Vector2(1920, 1080);
}

Creating a Custom Settings Layer

Define a new menu screen by inheriting from SettingsLayerBase:

public class AccessibilitySettingsLayer : SettingsLayerBase
{
    public AccessibilitySettingsLayer()
    {
        // Toggle for high contrast mode
        AllSettingsOptions.Add(new SettingsOption(false)
        {
            DataName = "HighContrast",
            DescriptionDataName = new[] { "HighContrastTip" },
            Type = OptionType.SelectionToggle,
            SelectionBasedChangedValueGetter = () => SettingsStorage.HighContrast,
            SelectionBasedChangedValueSetter = v => SettingsStorage.HighContrast = (bool)v,
            OnSelected = () => SettingsStorage.HighContrast = !SettingsStorage.HighContrast
        });

        AddBackOptionForDisplay(null);
    }
}

Registering and Navigating to Custom Layers

Add the layer to the storage dictionary during initialization:

// In your bootstrap or GameManager
SettingsStorage.CubismSettingsLayers["Accessibility"] = new AccessibilitySettingsLayer();

// Navigate from another layer
_settingsPreviousLayers.Add(currentLayer);
settingsLayer = "Accessibility";
_settingSelectedOption = 0;

Implementing a Numeric Slider Option

Use SelectionBasedFalse with percentage display for volume-like controls:

AllSettingsOptions.Add(new SettingsOption(0.5f)
{
    DataName = "DialogueVolume",
    DescriptionDataName = new[] { "DialogueVolumeTip" },
    Type = OptionType.SelectionBasedFalse,
    OptionDisplayMode = OptionType.Percentage,
    SelectionBasedChangedValueGetter = () => SettingsStorage.DialogueVolume,
    SelectionBasedChangedValueSetter = v => SettingsStorage.DialogueVolume = (float)v,
    SelectionBasedChangedUnit = 0.05f,
    SelectionBasedChangedMin = 0f,
    SelectionBasedChangedMax = 1f
});

Summary

Frequently Asked Questions

How do I add a new settings category to the menu?

Create a class inheriting from SettingsLayerBase, populate AllSettingsOptions in the constructor with your desired SettingsOption instances, then register the instance in SettingsStorage.CubismSettingsLayers with a unique string key. The SettingsController will automatically handle navigation and rendering once registered.

What is the difference between SelectionToggle and SelectionBasedFalse option types?

SelectionToggle represents binary on/off states where the value inverts when the user selects the option, typically used for boolean settings like fullscreen mode. SelectionBasedFalse represents ranged numeric values where left/right inputs decrement or increment the value within defined min/max bounds, commonly used for volume sliders or sensitivity settings.

How does the settings system persist data between game sessions?

While the analysis focuses on the runtime architecture, SettingsStorage serves as the central value holder that typically serializes to Unity's PlayerPrefs or a custom JSON file on application quit. The static properties in SettingsStorage ensure that any changes made through SettingsOption delegates are immediately available to the rest of the game without requiring additional load calls.

Can I customize the navigation behavior or visual presentation of individual options?

Yes, through the SettingsOption metadata properties. You can specify DescriptionDataName arrays for tooltip text, set OptionDisplayMode to formats like Percentage for numeric values, and provide custom OnSelected callbacks for immediate side effects. However, the actual rendering logic resides in SettingsController, so significant visual changes would require modifying how the controller interprets the option metadata.

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 →