The Settings view set is built from three abstractions: a SettingsDataSO bundling many SettingsDataSections (model), and a SettingsMenuView hosting many SettingsMenuViewSections (view). Each pair handles a category — Graphics, Audio, Localization, etc. The GameFull sample ships a complete working example you can fork.


| Layer | Class | Purpose |
|---|---|---|
| Model — bundle | SettingsDataSO | ScriptableObject holding a dictionary of SettingsDataSections. |
| Model — section | SettingsDataSection | Abstract SO. One per category; loads/saves to PlayerPrefs and applies values at runtime. |
| View — root | SettingsMenuView | The host MonoBehaviour. Add view sections as children. |
| View — section | SettingsMenuViewSection | Abstract MonoBehaviour. One per category; binds UXML controls to its model section. |
Holds the full settings model — a KeyValueDatabase<string, SettingsDataSection> whose keys identify each section.

Abstract ScriptableObject. One subclass per category. You override:
| Method | Purpose |
|---|---|
SaveToPlayerPrefs(string key) | Write each field with a per-section key prefix. |
LoadFromPlayerPrefs(string key) | Read each field back. |
CopyValuesFrom(SettingsDataSection) | Deep-copy fields from another instance (used for "discard changes"). |
ApplySettings(SettingsDataSection) | Push values from another instance into the live game (e.g. update audio mixer, locale). |
Equals(object) / GetHashCode() | Power the "settings changed?" diff that gates the Apply button. |
[CreateAssetMenu(fileName = "SectionGameplay", menuName = "CupkekGames/Settings/SectionGameplay")]
public class SettingsDataSectionGameplay : SettingsDataSection
{
[SerializeField] private float _cursorSensitivity;
public float CursorSensitivity
{
get => _cursorSensitivity;
set
{
_cursorSensitivity = value;
// Apply at runtime: e.g. Cursor.sensitivity = value;
}
}
public override bool Equals(object obj)
=> obj is SettingsDataSectionGameplay b && _cursorSensitivity == b._cursorSensitivity;
public override int GetHashCode() => _cursorSensitivity.GetHashCode();
public override void SaveToPlayerPrefs(string key)
{
PlayerPrefs.SetFloat($"{key}_CursorSensitivity", _cursorSensitivity);
PlayerPrefs.Save();
}
public override void LoadFromPlayerPrefs(string key)
{
if (PlayerPrefs.HasKey($"{key}_CursorSensitivity"))
_cursorSensitivity = PlayerPrefs.GetFloat($"{key}_CursorSensitivity");
}
public override void CopyValuesFrom(SettingsDataSection section)
{
if (section is SettingsDataSectionGameplay copy)
_cursorSensitivity = copy._cursorSensitivity;
}
public override void ApplySettings(SettingsDataSection section)
{
if (section is SettingsDataSectionGameplay copy)
CursorSensitivity = copy._cursorSensitivity;
}
}Root host. Add SettingsMenuViewSection MonoBehaviours as children — one per category.
Abstract MonoBehaviour. One subclass per category. Override:
| Method | Purpose |
|---|---|
Awake() | Resolve UXML controls. Call base.Awake(). |
ApplySettingsToUI() | Push current _changedSettings values into the UI controls. |
| Per-control change handlers | Push UI changes back into _changedSettings.Dictionary[<key>]. |
using UnityEngine.UIElements;
using CupkekGames.Systems;
public class SettingsMenuViewAudio : SettingsMenuViewSection
{
private Slider _sliderCursorSensitivity;
protected override void Awake()
{
base.Awake();
_sliderCursorSensitivity = UIDocument.rootVisualElement.Q<Slider>("CursorSensitivitySlider");
}
public void OnEnable()
=> _sliderCursorSensitivity.RegisterValueChangedCallback(OnSliderChanged);
public void OnDisable()
=> _sliderCursorSensitivity.UnregisterValueChangedCallback(OnSliderChanged);
public override void ApplySettingsToUI()
{
var gameplay = (SettingsDataSectionGameplay)_changedSettings.Dictionary["gameplay"];
_sliderCursorSensitivity.value = gameplay.CursorSensitivity;
}
private void OnSliderChanged(ChangeEvent<int> evt)
{
var gameplay = (SettingsDataSectionGameplay)_changedSettings.Dictionary["gameplay"];
gameplay.CursorSensitivity = evt.newValue;
}
}Settings
Theme
Light
Contrast
Material
Dark
Dim
Material Dark
System
Sidebar(Light & Contrast only)
Font Family
DM Sans
Wix
Inclusive Sans
AR One Sans
Direction