【Cocos Creator】アタッチするだけ!SettingSlider (設定スライダー)の実装方法【TypeScript】

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

【Cocos Creator 3.8】SettingSlider の実装:アタッチするだけで「音量・明るさなどの設定値をスライダーで調整し、その場で永続保存」できる汎用スクリプト

このコンポーネントは、UI の Slider コンポーネントにアタッチするだけで、音量・明るさ・感度などの数値設定を操作し、即座にローカル保存(Config ファイル相当:localStorage)します。
外部の GameManager や ConfigManager などに依存せず、この 1 ファイルだけで完結する設計です。


コンポーネントの設計方針

要件整理

  • 対象は UI > Slider コンポーネントを持つノード。
  • Slider の値が変化したら:
    • 即座に ローカル設定(localStorage)へ保存する。
    • 保存キー(例: masterVolumebrightness)はインスペクタで指定できる。
    • ゲーム起動時(またはシーン開始時)に、保存済みの値を自動で読み込み、Slider に反映する。
  • 外部スクリプトへの依存は禁止:
    • ConfigManager などは使わず、自前で localStorage を直接扱う
    • 必要な情報はすべて @property で受け取る。
  • 防御的な実装:
    • アタッチされたノードに Slider コンポーネントが無い場合は エラーログを出す
    • ブラウザ以外(ネイティブ等)で localStorage が使えない場合も考慮し、安全に例外処理を行う。

インスペクタで設定可能なプロパティ設計

SettingSlider コンポーネントに用意するプロパティと役割は以下の通りです。

  • settingKey: string
    • このスライダーが保存・読み込みに使用する 一意なキー名
    • 例: "masterVolume", "bgmVolume", "sfxVolume", "brightness", "mouseSensitivity" など。
    • 空文字の場合は警告を出し、保存処理をスキップする。
  • defaultValue: number
    • 保存データがまだ存在しない場合に使用する 初期値
    • Slider の minValuemaxValue の範囲で指定する。
    • 初期値が範囲外なら、範囲内にクランプ(補正)される。
  • autoLoadOnStart: boolean
    • truestart 時に保存済みの値を読み込み、Slider に適用する。
    • false:保存済み値を無視し、現在の Slider 値をそのまま使う。
    • UI をシーンごとに初期化したい場合などに false を選べる。
  • saveOnChange: boolean
    • true:Slider の値が変化するたびに 即座に保存する。
    • false:保存は行わない(読み込み専用 UI として使いたい場合など)。
  • logDebug: boolean
    • true:読み込み・保存時に ログを出力して動作確認をしやすくする。
    • false:本番用など、ログを抑制したい場合。

これらのプロパティにより、「どの設定を保存するか」「いつ読み込むか」「いつ保存するか」をインスペクタだけで柔軟に制御できます。


TypeScriptコードの実装

以下が完成した SettingSlider.ts の全コードです。


import { _decorator, Component, Slider, sys, warn, error, log, math } from 'cc';
const { ccclass, property } = _decorator;

/**
 * SettingSlider
 * 
 * UI Slider にアタッチするだけで、
 * - 起動時に設定値を localStorage から読み込み
 * - スライダー操作時に即座に保存
 * を行う汎用コンポーネント。
 * 
 * 外部の GameManager や ConfigManager に依存せず、
 * このスクリプト単体で完結します。
 */
@ccclass('SettingSlider')
export class SettingSlider extends Component {

    @property({
        tooltip: 'このスライダーに対応する設定キー名。\n例: "masterVolume", "bgmVolume", "brightness" など。\n空のままだと保存・読み込みは行われません。'
    })
    public settingKey: string = '';

    @property({
        tooltip: '保存された値がまだ存在しない場合に使用する初期値。\nスライダーの最小値~最大値の範囲で指定してください。'
    })
    public defaultValue: number = 1.0;

    @property({
        tooltip: 'true の場合、start 時に保存済みの設定値を読み込み、スライダーに反映します。\nfalse の場合、現在のスライダー値をそのまま使用します。'
    })
    public autoLoadOnStart: boolean = true;

    @property({
        tooltip: 'true の場合、スライダーの値が変化するたびに即座に設定を保存します。'
    })
    public saveOnChange: boolean = true;

    @property({
        tooltip: 'true の場合、読み込み・保存時にログを出力して動作を確認しやすくします。'
    })
    public logDebug: boolean = false;

    /** このノードに付与された Slider コンポーネント */
    private _slider: Slider | null = null;

    /** localStorage が利用可能かどうかのフラグ */
    private _storageAvailable: boolean = false;

    onLoad() {
        // Slider コンポーネントの取得と検証
        this._slider = this.getComponent(Slider);
        if (!this._slider) {
            error('[SettingSlider] Slider コンポーネントが見つかりません。' +
                ' このスクリプトは UI > Slider を持つノードにアタッチしてください。');
        }

        // localStorage 利用可否のチェック
        this._storageAvailable = this._checkStorageAvailable();

        if (!this.settingKey || this.settingKey.trim().length === 0) {
            warn('[SettingSlider] settingKey が空です。保存・読み込みは行われません。');
        }
    }

    start() {
        // Slider または settingKey が正しくない場合は何もしない
        if (!this._slider) {
            return;
        }

        if (this.autoLoadOnStart && this._storageAvailable && this._hasValidKey()) {
            const loaded = this._loadValue();
            if (loaded !== null) {
                // 読み込めた値をそのまま使う
                this._applySliderValue(loaded);
            } else {
                // 保存が無い場合は defaultValue を使用
                this._applySliderValue(this.defaultValue);
            }
        } else {
            // autoLoadOnStart が false の場合や、ストレージが使えない場合は
            // 現在のスライダーの値をそのまま使用する。
            // ただし、キーが有効で saveOnChange が true なら、
            // 最初の値を保存しておくこともできる。
            if (this.saveOnChange && this._storageAvailable && this._hasValidKey()) {
                this._saveValue(this._slider.progress);
            }
        }

        // スライダーの値変更イベントにリスナーを登録
        // Slider には "slideEvents" もありますが、
        // ここでは on('slide') を使って汎用的に対応します。
        if (this._slider && this.saveOnChange && this._hasValidKey()) {
            this._slider.node.on('slide', this._onSliderChanged, this);
        }
    }

    onDestroy() {
        // イベントリスナーの解除
        if (this._slider) {
            this._slider.node.off('slide', this._onSliderChanged, this);
        }
    }

    /**
     * スライダーの値が変更されたときのコールバック
     */
    private _onSliderChanged() {
        if (!this._slider) {
            return;
        }
        if (!this.saveOnChange || !this._storageAvailable || !this._hasValidKey()) {
            return;
        }

        const value = this._slider.progress;
        this._saveValue(value);
    }

    /**
     * localStorage が使用可能かどうかをチェックする
     */
    private _checkStorageAvailable(): boolean {
        // Cocos Creator では sys.localStorage を利用するのが推奨
        if (!sys || !sys.localStorage) {
            warn('[SettingSlider] sys.localStorage が利用できません。設定の永続保存は行われません。');
            return false;
        }

        try {
            const testKey = '__setting_slider_test__';
            sys.localStorage.setItem(testKey, '1');
            sys.localStorage.removeItem(testKey);
            return true;
        } catch (e) {
            warn('[SettingSlider] localStorage へのアクセスに失敗しました。', e);
            return false;
        }
    }

    /**
     * settingKey が有効かどうか
     */
    private _hasValidKey(): boolean {
        return !!this.settingKey && this.settingKey.trim().length > 0;
    }

    /**
     * 保存された値を読み込む
     * @returns number | null 読み込めた場合は数値、無い場合や失敗時は null
     */
    private _loadValue(): number | null {
        if (!this._storageAvailable || !this._hasValidKey()) {
            return null;
        }

        try {
            const raw = sys.localStorage.getItem(this.settingKey);
            if (raw === null || raw === undefined) {
                if (this.logDebug) {
                    log(`[SettingSlider] "${this.settingKey}" に保存された値はまだありません。`);
                }
                return null;
            }

            const parsed = parseFloat(raw);
            if (Number.isNaN(parsed)) {
                warn(`[SettingSlider] "${this.settingKey}" の保存値 "${raw}" は数値として無効です。`);
                return null;
            }

            if (this.logDebug) {
                log(`[SettingSlider] "${this.settingKey}" から値 ${parsed} を読み込みました。`);
            }

            return parsed;
        } catch (e) {
            warn(`[SettingSlider] "${this.settingKey}" の読み込み中にエラーが発生しました。`, e);
            return null;
        }
    }

    /**
     * 指定した値を保存する
     * @param value 保存する値
     */
    private _saveValue(value: number): void {
        if (!this._storageAvailable || !this._hasValidKey()) {
            return;
        }

        try {
            sys.localStorage.setItem(this.settingKey, String(value));
            if (this.logDebug) {
                log(`[SettingSlider] "${this.settingKey}" に値 ${value} を保存しました。`);
            }
        } catch (e) {
            warn(`[SettingSlider] "${this.settingKey}" の保存中にエラーが発生しました。`, e);
        }
    }

    /**
     * スライダーに値を適用する(範囲外ならクランプ)
     * @param value 適用したい値
     */
    private _applySliderValue(value: number): void {
        if (!this._slider) {
            return;
        }

        // Slider.progress は 0.0 ~ 1.0 の範囲
        const clamped = math.clamp01(value);

        this._slider.progress = clamped;

        if (this.logDebug) {
            log(`[SettingSlider] スライダーに値 ${clamped} を適用しました。`);
        }
    }
}

コードのポイント解説

  • onLoad()
    • アタッチ先ノードから Slider コンポーネントを取得。
    • 見つからなければ error() を出して、開発時に気づけるようにする。
    • sys.localStorage が使えるかどうかをテストして、結果を _storageAvailable に保持。
    • settingKey が空なら warn() を出し、保存・読み込みはスキップする方針を明確化。
  • start()
    • autoLoadOnStarttrue かつ localStorage が使える場合:
      • _loadValue() で保存済み値を取得。
      • 存在すればそれを _applySliderValue() で反映。
      • 存在しなければ defaultValue を反映。
    • autoLoadOnStartfalse の場合:
      • 現在の Slider の値をそのまま使う。
      • saveOnChangetrue なら、その初期値を保存しておくことも可能。
    • saveOnChangetrue で有効なキーがある場合、slider.node.on('slide', ...) でイベント登録。
  • _onSliderChanged()
    • Slider の値変更イベントが来たら、_slider.progress を取得して _saveValue() を呼ぶ。
    • saveOnChange_storageAvailablesettingKey の有無を再度チェックして安全に処理。
  • _checkStorageAvailable()
    • sys.localStorage の有無を確認し、簡単な書き込み・削除テストを行う。
    • 失敗した場合は warn() を出し、以降は保存機能を無効化。
  • _loadValue() / _saveValue()
    • sys.localStorage を直接操作し、文字列として保存・読み込み。
    • 読み込み時は parseFloat() で数値に変換し、NaN の場合は警告を出して無視。
    • logDebug フラグにより、デバッグログの ON/OFF を制御。
  • _applySliderValue()
    • Slider の progress プロパティ(0.0 ~ 1.0)に値をセット。
    • 範囲外の値が来ても math.clamp01() で 0~1 に収める防御的実装。

使用手順と動作確認

ここからは、実際にエディタで SettingSlider を使う手順を説明します。

1. スクリプトファイルの作成

  1. Assets パネルで右クリックします。
  2. Create > TypeScript を選択します。
  3. 作成されたファイル名を SettingSlider.ts に変更します。
  4. SettingSlider.ts をダブルクリックして開き、中身をすべて削除してから、上記の TypeScript コードを丸ごと貼り付けて保存します。

2. テスト用 UI(Slider)の作成

  1. Hierarchy パネルで右クリックし、Create > UI > Canvas を作成します(まだ無い場合)。
  2. Canvas を選択した状態で右クリックし、Create > UI > Slider を選択して Slider ノードを作成します。
  3. 作成された Slider ノードを選択し、Inspector を確認します。
    • Slider コンポーネントが付いていることを確認してください。
    • 必要に応じて Direction(横 or 縦)や Handle の見た目を調整します。

3. SettingSlider コンポーネントのアタッチ

  1. Slider ノードを選択した状態で、Inspector の下部にある Add Component ボタンをクリックします。
  2. メニューから Custom > SettingSlider を選択します。
    • もし Custom 内に見つからない場合は、エディタがスクリプトをコンパイル中の可能性があるので、数秒待ってから再度試してください。
  3. これで Slider ノードに SettingSlider コンポーネントが追加されます。

4. プロパティの設定例

Inspector 上の SettingSlider コンポーネントに、以下のようにプロパティを設定してみましょう。

  • 例1:マスターボリューム用スライダー
    • settingKeymasterVolume
    • defaultValue0.8(80% の音量を初期値にするイメージ)
    • autoLoadOnStarttrue
    • saveOnChangetrue
    • logDebugtrue(動作確認したいので ON)
  • 例2:画面の明るさ用スライダー
    • settingKeybrightness
    • defaultValue0.5
    • autoLoadOnStarttrue
    • saveOnChangetrue
    • logDebugtrue

なお、Slider コンポーネント側の minValue / maxValue は 0 ~ 1 にしておくと、progress と直感的に対応します。

5. 再生して動作確認

  1. エディタ右上の Play ボタンを押してゲームを実行します。
  2. ゲーム画面上の Slider をドラッグして値を変更します。
    • logDebugtrue にしている場合、Console
      • [SettingSlider] "masterVolume" に値 0.73 を保存しました。
      • [SettingSlider] "masterVolume" から値 0.73 を読み込みました。

      のようなログが表示されるはずです。

  3. 一度ゲームを停止し、再度 Play してみてください。
    • autoLoadOnStart = true なので、前回保存した位置に Slider が戻っていることを確認できます。

6. 設定が本当に保存されているか確認したい場合

  • ブラウザで実行している場合
    • ブラウザの開発者ツールを開き、Application > Local Storage(または Storage)を確認します。
    • キー名として masterVolumebrightness が追加され、その値が 0~1 の数値として保存されているはずです。
  • ネイティブやシミュレータの場合
    • 内部的には sys.localStorage を通じて、プラットフォームごとの永続領域に保存されます。
    • コードから sys.localStorage.getItem('masterVolume') を呼んで値を確認することもできます。

まとめ

今回作成した SettingSlider コンポーネントは、

  • Slider にアタッチするだけ
    • 起動時に設定値を読み込む
    • 操作と同時に設定値を保存する
  • 外部の GameManager や ConfigManager に一切依存しない完全独立スクリプト
  • 設定対象(音量・明るさ・感度など)は settingKey を変えるだけで使い回し可能

という特徴を持つ、汎用性の高いコンポーネントです。

実際のゲーム開発では、

  • マスターボリューム / BGM / SE / ボイス など、複数の音量スライダー
  • 画面の明るさ、ガンマ補正
  • マウス感度、スティック感度
  • UI スケール、テキストサイズ

といったさまざまな設定項目を、この SettingSlider 1 つで統一的に扱うことができます。
あとは別の場所で sys.localStorage.getItem('masterVolume') などを参照して実際の音量や明るさに反映すれば、シンプルかつ拡張しやすい設定システムが構築できます。

プロジェクトごとに複雑な Config 管理クラスを用意しなくても、UI 側はこのコンポーネントを使い回すだけで済むため、設定画面の実装コストを大きく削減できます。
まずは 1 つの Slider から試してみて、自分のプロジェクトに合わせて settingKey を増やしていくとよいでしょう。

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

URLをコピーしました!