【Unity】設定画面を一本につなぐ:音量・画面・言語・操作をまとめて保存する

作成: 2026-07-16

音量はAudioMixer、解像度はScreen、言語はLocalization、キーコンフィグはInput System——個別のやり方は分かったのに、いざ「設定画面」を作ると、保存がバラバラ・起動時に復元されない・適用タイミングが揃わないと崩壊しがちです。SettingsDataに1本化し、適用役と保存役を分け、起動時に全設定を復元するSettingsManagerを完成させます。各分野の詳細は既存記事へつなぐ、統合のための記事です。

音量調整は AudioMixerの記事 で、解像度は 画面設定の記事 で、言語は Localizationの記事 で、キーコンフィグは リバインドの記事 で——個別には全部できるようになった。ところが、いざ「設定画面」として1つにまとめると、別の敵が現れます。 保存キーがバラバラに散らばる。起動しても半分しか復元されない。スライダーは動くのに音が変わらない。

設定画面の難しさは個々の機能ではなく 統合 にあります。この記事では、設定を1つのデータ(SettingsData)に集め、適用役と保存役を分け、 起動時に全設定を復元する ところまでを完成させます。各分野の深掘りは上のリンク先に任せ、ここでは「一本につなぐ」ことだけに集中します。

バラバラの設定が1つにまとまるイメージ

この記事でわかること

  • 設定画面が崩壊する典型パターン( 保存の散乱・復元漏れ
  • SettingsData に全設定を1本化する設計
  • 音量(AudioMixer)・画面(Screen)・言語(Localization)・操作(Rebind)の 適用コード
  • 保存はPlayerPrefsの1キー(JSON化)に集約する
  • 実践:起動時に全設定を復元する SettingsManager

動作確認環境: Unity 2022.3 LTS / Unity 6・Input System・Localizationパッケージ

Sponsored

設定画面は「寄せ集め」ではなく1つのシステム

個別記事のコードをそのまま設定画面に貼り合わせると、こうなりがちです。

  • 音量はスクリプトAが PlayerPrefs.SetFloat("bgm") で保存
  • 解像度はスクリプトBが PlayerPrefs.SetInt("width")"height" で保存
  • 言語は自前キー "lang" と公式Selectorで 二重管理
  • キーコンフィグは……保存し忘れていた

その結果が「起動するたび音量だけ戻っている」「言語が再起動で食い違う」です。原因はシンプルで、 設定という1つの関心事が、コードのあちこちに散っている から。治療法もシンプルです—— 1カ所に集めます

バラバラの設定管理と一本化の比較図。個別のスクリプトがそれぞれ別のキーで保存していた状態から、SettingsDataという1つのデータに音量・画面・言語・操作が集まり、1つの入口から保存・復元される状態へ

設計:データ1つ、適用役と保存役

設計は3つの部品でできています。

設定システムの3部品の図。中央のSettingsDataを挟んで、Applyがゲームへの反映(Mixer・Screen・Locale・Input)を、Save/Loadがディスクへの保存と読み込みを担当する
部品役割
SettingsData全設定の値を持つただのクラス(保存の単位)
Apply(適用)値をゲームへ反映する(Mixerへ、Screenへ、Localeへ…)
Save / Load(保存)SettingsDataを丸ごと書く・読む

分けて考えるのが肝心です。 「値を変える」と「ゲームに反映する」と「ディスクに残す」は別の仕事 で、スライダーを動かした瞬間はApplyだけ、決定した時にSave、起動した時にLoad+Apply——と組み合わせて使います。

// SettingsData.cs — 全設定の入れ物(ただのデータクラス)
[System.Serializable]
public class SettingsData
{
    // 音量(0〜1)
    public float masterVolume = 0.8f;
    public float bgmVolume = 0.8f;
    public float seVolume = 0.8f;

    // 画面
    public int resolutionWidth = 1920;
    public int resolutionHeight = 1080;
    public bool fullscreen = true;

    // 操作(リバインドの上書き分JSON。空なら既定のまま)
    public string bindingOverridesJson = "";

    // 言語は公式Selectorに任せるため、ここには持たない(後述)
}

4分野の適用コード

それぞれの分野の「反映のしかた」は、既存記事で学んだ通りです。要点だけ集めます。

音量 → AudioMixer(詳細は AudioMixerの記事):

// スライダーの0〜1を、Mixerの対数dBへ変換して渡す
void ApplyVolume(string parameter, float value01)
{
    float dB = Mathf.Log10(Mathf.Clamp(value01, 0.0001f, 1f)) * 20f;
    audioMixer.SetFloat(parameter, dB); // "BGMVolume"などExposeしたパラメータ名
}

画面 → Screen(詳細は 画面設定の記事):

void ApplyDisplay(SettingsData data)
{
    Screen.SetResolution(data.resolutionWidth, data.resolutionHeight, data.fullscreen);
}

言語 → Localization(詳細は Localizationの記事):

// SelectedLocaleを切り替えるだけ。保存と起動時復元は
// 公式のPlayerPrefs Selectorが担当するので、自前で保存しない(二重管理禁止)
void ApplyLanguage(Locale locale)
{
    LocalizationSettings.SelectedLocale = locale;
}

操作 → Input System(詳細は リバインドの記事):

// 保存時: 上書き分だけをJSON文字列にする
data.bindingOverridesJson = inputActions.SaveBindingOverridesAsJson();

// 復元時: JSONを読み戻す(空文字なら何もしない=既定のキー配置)
if (!string.IsNullOrEmpty(data.bindingOverridesJson))
{
    inputActions.LoadBindingOverridesFromJson(data.bindingOverridesJson);
}
Sponsored

保存:PlayerPrefsの1キーに集約する

保存先は PlayerPrefs で十分——ただしキーを散らかさず、 SettingsDataをJSONにして1キーへ 入れます。

const string SettingsKey = "game_settings";

void Save(SettingsData data)
{
    string json = JsonUtility.ToJson(data);
    PlayerPrefs.SetString(SettingsKey, json);
    PlayerPrefs.Save();
}

SettingsData Load()
{
    if (!PlayerPrefs.HasKey(SettingsKey))
    {
        return CreateDefaults(); // 初回起動:環境から初期値を作る(実践で後述)
    }
    return JsonUtility.FromJson<SettingsData>(PlayerPrefs.GetString(SettingsKey));
}

キーが1つなら「どこかの設定だけ保存し忘れる」事故が構造的に消えます。項目を増やすときもSettingsDataにフィールドを足すだけです。設定の構造を大きく変える日が来たら、セーブデータのバージョン管理 と同じ version フィールドの考え方がそのまま使えます。

実践:起動時に全設定を復元する

仕上げに、全部をつなぐ SettingsManager を作ります。仕事は3つ——起動時に Load→ApplyAll、UIから値が変わったら Apply、決定されたら Save です。

起動時の設定復元フロー図。ゲーム起動時にSettingsManagerが保存データを読み込み、音量・画面・言語・操作の4分野へ一斉に適用してから、タイトル画面が表示される
// SettingsManager.cs — 起動シーンに1つ置く
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.InputSystem;

public class SettingsManager : MonoBehaviour
{
    [SerializeField] private AudioMixer audioMixer;
    [SerializeField] private InputActionAsset inputActions;

    public SettingsData Current { get; private set; }

    const string SettingsKey = "game_settings";

    void Awake()
    {
        // 起動時の入口はここ1カ所:読み込んで、全分野へ適用
        Current = Load();
        ApplyAll(Current);
    }

    public void ApplyAll(SettingsData data)
    {
        ApplyVolume("MasterVolume", data.masterVolume);
        ApplyVolume("BGMVolume", data.bgmVolume);
        ApplyVolume("SEVolume", data.seVolume);

        Screen.SetResolution(data.resolutionWidth, data.resolutionHeight, data.fullscreen);

        if (!string.IsNullOrEmpty(data.bindingOverridesJson))
        {
            inputActions.LoadBindingOverridesFromJson(data.bindingOverridesJson);
        }
        // 言語は公式Selectorが起動時に復元するので、ここでは触らない
    }

    // ---- UIのスライダー/トグルから呼ぶ(即時反映) ----
    public void SetBgmVolume(float value01)
    {
        Current.bgmVolume = value01;
        ApplyVolume("BGMVolume", value01);
    }

    // ---- 設定画面を閉じる/決定ボタンで呼ぶ ----
    public void SaveCurrent()
    {
        Current.bindingOverridesJson = inputActions.SaveBindingOverridesAsJson();
        PlayerPrefs.SetString(SettingsKey, JsonUtility.ToJson(Current));
        PlayerPrefs.Save();
    }

    // ---- 初回起動:ユーザーの環境から初期値を作る ----
    SettingsData CreateDefaults()
    {
        var data = new SettingsData
        {
            resolutionWidth = Screen.currentResolution.width,
            resolutionHeight = Screen.currentResolution.height,
            fullscreen = true
        };
        return data;
    }

    SettingsData Load()
    {
        if (!PlayerPrefs.HasKey(SettingsKey)) return CreateDefaults();
        return JsonUtility.FromJson<SettingsData>(PlayerPrefs.GetString(SettingsKey));
    }

    void ApplyVolume(string parameter, float value01)
    {
        float dB = Mathf.Log10(Mathf.Clamp(value01, 0.0001f, 1f)) * 20f;
        audioMixer.SetFloat(parameter, dB);
    }
}

UI側の配線は素直です。スライダーの OnValueChangedSetBgmVolume を( 動かした瞬間に音が変わる のが良い設定画面の条件です)、設定画面を閉じるボタンに SaveCurrent をつなぎます。

確認の儀式はこうです。BGMを小さくし、ウィンドウモードへ切り替え、キーを1つ変えて、設定画面を閉じる。 ゲームを完全に終了して、起動し直す。タイトルのBGMが小さいまま、ウィンドウのまま、変えたキーが効いたままなら——全経路がつながっています。もしどれか1つだけ戻っていたら、その分野の「Applyのし忘れ」(保存はされているのに反映していない)をまず疑ってください。設定バグの9割はSaveではなくApplyの漏れです。

ポイントは2つ。 起動時の入口は1カ所Awake のLoad→ApplyAll。ここに全部通す)。 変更は即Apply、保存は区切りで(スライダーごとにPlayerPrefsへ書かない)。

おまけ:先に知っておくと良いこと

  • 危険な設定には「元に戻す」猶予を: 解像度変更は、失敗すると画面が見えなくなって操作不能になり得ます。「この設定を保持しますか? 10秒後に元に戻ります」という確認ダイアログは、PCゲームの定番の保険です
  • リセットボタンを最初から: 「デフォルトに戻す」は ApplyAll(new SettingsData()) +キーコンフィグの RemoveAllBindingOverrides() で作れます。設定をいじり壊したプレイヤーの救済路として、最初から用意しておく価値があります
  • グラフィック品質の段階: Quality Settings(Low/Medium/High)を項目に足すのが次の一歩です。QualitySettings.SetQualityLevel() で切り替えられ、SettingsDataにintを1つ足すだけで収まります

まとめ

  • 設定画面の敵は個々の機能ではなく 統合。保存キーの散乱と復元漏れが二大事故
  • SettingsData(データ)・Apply(反映)・Save/Load(保存) の3役に分ける
  • 保存は PlayerPrefsの1キーにJSON で集約。言語だけは 公式Selectorに一本化 して自前保存しない
  • 起動時の入口は Awake のLoad→ApplyAll の1カ所
  • 検証は 完全再起動。1つだけ戻る症状は、SaveでなくApplyの漏れ

あなたのゲームをいま再起動したら、設定はいくつ生き残るでしょうか——4つ全部なら、この記事はもう卒業です。