【Cocos Creator 3.8】SettingSlider の実装:アタッチするだけで「音量・明るさなどの設定値をスライダーで調整し、その場で永続保存」できる汎用スクリプト
このコンポーネントは、UI の Slider コンポーネントにアタッチするだけで、音量・明るさ・感度などの数値設定を操作し、即座にローカル保存(Config ファイル相当:localStorage)します。
外部の GameManager や ConfigManager などに依存せず、この 1 ファイルだけで完結する設計です。
コンポーネントの設計方針
要件整理
- 対象は UI > Slider コンポーネントを持つノード。
- Slider の値が変化したら:
- 即座に ローカル設定(localStorage)へ保存する。
- 保存キー(例:
masterVolumeやbrightness)はインスペクタで指定できる。 - ゲーム起動時(またはシーン開始時)に、保存済みの値を自動で読み込み、Slider に反映する。
- 外部スクリプトへの依存は禁止:
- ConfigManager などは使わず、自前で localStorage を直接扱う。
- 必要な情報はすべて
@propertyで受け取る。
- 防御的な実装:
- アタッチされたノードに Slider コンポーネントが無い場合は エラーログを出す。
- ブラウザ以外(ネイティブ等)で
localStorageが使えない場合も考慮し、安全に例外処理を行う。
インスペクタで設定可能なプロパティ設計
SettingSlider コンポーネントに用意するプロパティと役割は以下の通りです。
- settingKey: string
- このスライダーが保存・読み込みに使用する 一意なキー名。
- 例:
"masterVolume","bgmVolume","sfxVolume","brightness","mouseSensitivity"など。 - 空文字の場合は警告を出し、保存処理をスキップする。
- defaultValue: number
- 保存データがまだ存在しない場合に使用する 初期値。
- Slider の
minValue~maxValueの範囲で指定する。 - 初期値が範囲外なら、範囲内にクランプ(補正)される。
- autoLoadOnStart: boolean
true:start 時に保存済みの値を読み込み、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()
autoLoadOnStartがtrueかつlocalStorageが使える場合:_loadValue()で保存済み値を取得。- 存在すればそれを
_applySliderValue()で反映。 - 存在しなければ
defaultValueを反映。
autoLoadOnStartがfalseの場合:- 現在の Slider の値をそのまま使う。
saveOnChangeがtrueなら、その初期値を保存しておくことも可能。
saveOnChangeがtrueで有効なキーがある場合、slider.node.on('slide', ...)でイベント登録。
- _onSliderChanged()
- Slider の値変更イベントが来たら、
_slider.progressを取得して_saveValue()を呼ぶ。 saveOnChangeや_storageAvailable、settingKeyの有無を再度チェックして安全に処理。
- Slider の値変更イベントが来たら、
- _checkStorageAvailable()
sys.localStorageの有無を確認し、簡単な書き込み・削除テストを行う。- 失敗した場合は
warn()を出し、以降は保存機能を無効化。
- _loadValue() / _saveValue()
sys.localStorageを直接操作し、文字列として保存・読み込み。- 読み込み時は
parseFloat()で数値に変換し、NaN の場合は警告を出して無視。 logDebugフラグにより、デバッグログの ON/OFF を制御。
- _applySliderValue()
- Slider の
progressプロパティ(0.0 ~ 1.0)に値をセット。 - 範囲外の値が来ても
math.clamp01()で 0~1 に収める防御的実装。
- Slider の
使用手順と動作確認
ここからは、実際にエディタで SettingSlider を使う手順を説明します。
1. スクリプトファイルの作成
- Assets パネルで右クリックします。
- Create > TypeScript を選択します。
- 作成されたファイル名を
SettingSlider.tsに変更します。 SettingSlider.tsをダブルクリックして開き、中身をすべて削除してから、上記の TypeScript コードを丸ごと貼り付けて保存します。
2. テスト用 UI(Slider)の作成
- Hierarchy パネルで右クリックし、Create > UI > Canvas を作成します(まだ無い場合)。
- Canvas を選択した状態で右クリックし、Create > UI > Slider を選択して Slider ノードを作成します。
- 作成された Slider ノードを選択し、Inspector を確認します。
- Slider コンポーネントが付いていることを確認してください。
- 必要に応じて
Direction(横 or 縦)やHandleの見た目を調整します。
3. SettingSlider コンポーネントのアタッチ
- Slider ノードを選択した状態で、Inspector の下部にある Add Component ボタンをクリックします。
- メニューから Custom > SettingSlider を選択します。
- もし
Custom内に見つからない場合は、エディタがスクリプトをコンパイル中の可能性があるので、数秒待ってから再度試してください。
- もし
- これで Slider ノードに
SettingSliderコンポーネントが追加されます。
4. プロパティの設定例
Inspector 上の SettingSlider コンポーネントに、以下のようにプロパティを設定してみましょう。
- 例1:マスターボリューム用スライダー
- settingKey:
masterVolume - defaultValue:
0.8(80% の音量を初期値にするイメージ) - autoLoadOnStart:
true - saveOnChange:
true - logDebug:
true(動作確認したいので ON)
- settingKey:
- 例2:画面の明るさ用スライダー
- settingKey:
brightness - defaultValue:
0.5 - autoLoadOnStart:
true - saveOnChange:
true - logDebug:
true
- settingKey:
なお、Slider コンポーネント側の minValue / maxValue は 0 ~ 1 にしておくと、progress と直感的に対応します。
5. 再生して動作確認
- エディタ右上の Play ボタンを押してゲームを実行します。
- ゲーム画面上の Slider をドラッグして値を変更します。
logDebugをtrueにしている場合、Console に[SettingSlider] "masterVolume" に値 0.73 を保存しました。[SettingSlider] "masterVolume" から値 0.73 を読み込みました。
のようなログが表示されるはずです。
- 一度ゲームを停止し、再度 Play してみてください。
autoLoadOnStart = trueなので、前回保存した位置に Slider が戻っていることを確認できます。
6. 設定が本当に保存されているか確認したい場合
- ブラウザで実行している場合:
- ブラウザの開発者ツールを開き、Application > Local Storage(または Storage)を確認します。
- キー名として
masterVolumeやbrightnessが追加され、その値が 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 を増やしていくとよいでしょう。




