設定画面は、音量や言語のような値を変えて、すぐゲームに効かせ、次の起動まで残すための画面です。Godot では、値を Autoload で覚え、ConfigFile でファイルに残して作ります。
このとき、開くときにファイルの値を画面へ戻す1行が、人が操作したときと同じ合図(シグナル)を出します。その合図で保存が走ると、何も触らずに閉じただけで、前回選んだ言語が初期値に戻ります。
この記事では、音量・言語・最大化の3つを1つの画面にまとめ、開いただけでは値が変わらない設定画面の作り方を解説します。
この記事でわかること
- 設定画面の仕事が「変える」「効かせる」「残す」の3つに分かれること
- コードからの代入もシグナルを出すことと、出さずに値を入れる書き方
- ファイルへ書く値を、画面ではなく覚えている側から取る理由
- 実践:音量・言語・最大化を1画面にまとめ、再生し直しても値がそろうことを確かめる
動作確認環境: Godot 4.7
設定画面の3つの仕事
設定画面の仕事は3つに分かれます。スライダーやドロップダウンで値を「変える」、その値をゲームに「効かせる」、次の起動のためにファイルへ「残す」です。

| 仕事 | 担当 | 中身 |
|---|---|---|
| 変える | 画面の Control | スライダー・ドロップダウン・チェックが並ぶ |
| 効かせる | 覚えている側 | AudioServer(音)・ TranslationServer(言語)・ DisplayServer(ウィンドウ)を呼ぶ |
| 残す | 覚えている側 | ConfigFile で user://settings.cfg へ書く |
「覚えている側」は、設定の値を1か所で持つノードで、あとの節で作ります。user:// は、ゲームごとに用意される書き込み用のフォルダです。AudioServer などの3つはノードではなく、エンジンに最初から1つずつある窓口で、シーンに置かなくても、どのスクリプトからも名前で呼べます。
画面は値を渡すだけで、エンジンにもファイルにも触りません。この線を引いておくと、あとの節で出てくる問題がほとんど消えます。
効かせる中身は、それぞれ別の記事が持っています。音量のバスは オーディオ入門 、言語の切り替えは 多言語対応入門 、キーの差し替えは InputMap入門 、画面の見た目は UIレイアウト入門 です。ConfigFile の書式そのものは セーブ/ロード入門 にあります。
コードからの代入とシグナル
シグナル は、ボタンを押し たりスライダーをつまんだりしたときに出るもの、という感覚で書いてしまいがちです。ところが HSlider の value_changed は、「値が変わった」ことを知らせるシグナルです。変えたのが人かコードかを区別しません。CheckButton の toggled も同じです。
冒頭で書いた事故は、ここから起きます。画面の値を集めてファイルへ書く保存関数を、value_changed につないでいたとします。開くときに slider.value = ... でファイルの値を戻すと、その代入で保存関数が走ります。そのとき言語のドロップダウンはまだ戻しておらず、最初の項目の「日本語」を選んだままです。ファイルの en は、ここで ja に上書きされます。

値を入れることと、知らせることを切り離す入口が、それぞれのノードに用意されています。
| ノード | シグナルが出る書き方 | 出ない書き方 |
|---|---|---|
| HSlider | slider.value = 0.3 | slider.set_value_no_signal(0.3) |
| CheckButton | check.button_pressed = true | check.set_pressed_no_signal(true) |
| OptionButton | — | option.select(1) |
他のエンジンでいうと: Unity の
Slider.SetValueWithoutNotify()とToggle.SetIsOnWithoutNotify()にあたります。Unity でもslider.valueへの代入はonValueChangedを呼びます。

OptionButton だけ、シグナルが出る書き方の欄が空です。select() はもともと item_selected を出さないので、値を戻すときもそのまま呼べます。
ファイルへ書く値の出どころ
シグナルを止めても、危うさはもう1つ残っています。さきほどの保存関数は、画面に並んだコントロールを見て回って、そのときの値をファイルへ書いていました。

この書き方だと、保存が走る時刻に画面が完成していることが前提になります。開いた直後や、コントロールを1つ足した直後に、その前提が崩れます。
そこで、値を覚えている側を1つ作り、そこを唯一の出どころにします。画面は値を渡すだけで、ファイルへは覚えている側の変数からだけ書きます。画面がどこまで組み上がっていても、保存の中身は変わりません。
覚えている側は Autoload に置きます。Autoload はシーンを切り替えても残り続けるノードで、最初のシーンが出る前に読み込まれます。起動した瞬間にファイルを読んで、音量や言語を効かせておけます。
ファイルへ書くタイミング
value_changed のたびにファイルへ書くと、スライダーを1回ドラッグするだけで何十回も書き込みが走ります。

スライダーなら、つまんでいるあいだは音量だけを更新し、指を離したときに出る drag_ended で1回だけ書きます。ドロップダウンやチェックは連続値ではないので、操作した瞬間に効かせて、その場で書いて問題ありません。
drag_ended は、キーボードやゲームパッドの左右、ホイールで値を変えたときには出ません。その取りこぼしは、閉 じるときにもう一度書いて拾います。ゲームのウィンドウを閉じようとすると、NOTIFICATION_WM_CLOSE_REQUEST という通知が届きます。エディタの停止ボタンではこの通知が届かないので、確かめるときはウィンドウの × で閉じてください。
既定値の持ち方
設定ファイルは、手で開いて書き換えられます。前の版から項目が増えていることもあります。読み込みでは、次の3つをどれも同じコードの定数へ戻します。

ファイルが無いか読めないときは、ConfigFile.load() が OK を返さないので、定数のまま起動します。ConfigFile のファイルは、[audio] のような見出し(セクション)の下に、master=0.8 のような「キー=値」の行が並んだものです。キーが無いときは、get_value() の第3引数に渡した定数が、そのキーにだけ入ります。型が違う・範囲の外、のように値がおかしいときも、読み込みのコードで定数に戻します。
定数を1組だけ持っておけば、「既定に戻す」ボタンにも同じものを使えます。ただし、ボタンでファイルを消すだけでは足りません。いま鳴っている音量と、いまの言語が古いままになります。
変数を定数に戻し、エンジンへ効かせ、画面へシグナルを出さずに反映し、ファイルへ書きます。この4つがそろって、押した瞬間の見た目と次の起動が一致します。
実践:音量・言語・最大化を1つの画面にまとめる
RPGのオプション、対戦ゲームの設定タブ、モバイルの歯車アイコン——設定画面の中身はジャンルが変わっても似た顔ぶれになります。ここでは種類の違う3つを1画面に置いて、開いても値が消えないことを確かめます。
① 置くもの
新しいシーンを1つ作ります。
- ルートに Control を置いて名前を Main にし、Main Scene に指定する。Anchors Preset は Full Rect。あとで書く
main.gdを付ける - Main の子に PanelContainer を置いて Box 。Anchors Preset を Center にし、Custom Minimum Size を
(460, 0)にする - Box の子に VBoxContainer を置いて Rows
- Rows の子に HBoxContainer を2つ置いて VolumeRow と LocaleRow 。それぞれの子に Label (Text は「音量」「言語」)と、HSlider の VolumeSlider ・ OptionButton の LocaleOption を置く
- VolumeSlider の Min
0・Max1・Step0.01。Layout の Container Sizing で Horizontal に Expand と Fill を付ける(行の残りの幅いっぱいまでつまみを伸ばす) - Rows の子に CheckButton の MaximizeCheck (Text は「最大化」)、Button の ResetButton (Text は「既定に戻す」)、Label の StateLabel を置く
- VolumeSlider ・ LocaleOption ・ MaximizeCheck ・ ResetButton ・ StateLabel の5つに、右クリックメニューの「Access as Unique Name」を付ける。コードから
%VolumeSliderのように名前で取れるようになる settings.gdを作り、Project Settings の Globals → Autoload に Settings という名前で登録する
ウィンドウの最大化は、エディタにゲームを埋 め込んだまま再生すると効きません。Game タブの右上のメニューで Embed Game on Next Play のチェックを外してから再生します。
② 組み立て
覚えている側です。ファイルの読み書きと、エンジンへの適用が、ここに閉じています。
# settings.gd(Autoload に Settings の名前で登録)
extends Node
const PATH := "user://settings.cfg"
const LOCALES := ["ja", "en"]
const DEFAULT_VOLUME := 0.8
const DEFAULT_LOCALE := "ja"
const DEFAULT_MAXIMIZED := false
var volume := DEFAULT_VOLUME
var locale := DEFAULT_LOCALE
var maximized := DEFAULT_MAXIMIZED
func _ready() -> void:
var config := ConfigFile.new()
if config.load(PATH) == OK: # 読めなければ定数のまま
var v = config.get_value("audio", "master", DEFAULT_VOLUME)
volume = v if v is float and v >= 0.0 and v <= 1.0 else DEFAULT_VOLUME
locale = _known(str(config.get_value("display", "locale", DEFAULT_LOCALE)))
var m = config.get_value("display", "maximized", DEFAULT_MAXIMIZED)
maximized = m if m is bool else DEFAULT_MAXIMIZED # 型が違えば定数へ
_apply()
func set_volume(value: float) -> void:
volume = clampf(value, 0.0, 1.0)
AudioServer.set_bus_volume_db(0, linear_to_db(volume)) # 0〜1 をデシベルに直して Master(0番)へ
func set_locale_code(code: String) -> void:
locale = _known(code)
TranslationServer.set_locale(locale)
save()
func set_maximized(on: bool) -> void:
maximized = on
DisplayServer.window_set_mode(
DisplayServer.WINDOW_MODE_MAXIMIZED if on else DisplayServer.WINDOW_MODE_WINDOWED
)
save()
func reset() -> void:
volume = DEFAULT_VOLUME
locale = DEFAULT_LOCALE
maximized = DEFAULT_MAXIMIZED
_apply()
save()
func save() -> void:
var config := ConfigFile.new()
config.set_value("audio", "master", volume) # 画面ではなく、この変数から書く
config.set_value("display", "locale", locale)
config.set_value("display", "maximized", maximized)
config.save(PATH)
func _notification(what: int) -> void:
if what == NOTIFICATION_WM_CLOSE_REQUEST: # ウィンドウを閉じようとしたという知らせ
save() # ホイールで変えた音量を取りこぼさない
func _known(code: String) -> String:
return code if code in LOCALES else DEFAULT_LOCALE
func _apply() -> void:
AudioServer.set_bus_volume_db(0, linear_to_db(volume))
TranslationServer.set_locale(locale)
DisplayServer.window_set_mode(
DisplayServer.WINDOW_MODE_MAXIMIZED if maximized else DisplayServer.WINDOW_MODE_WINDOWED
)
画面側は、値を渡すことと、状態を2行で見せることだけをします。
# main.gd(Main の Control)
extends Control
@onready var slider: HSlider = %VolumeSlider
@onready var option: OptionButton = %LocaleOption
@onready var check: CheckButton = %MaximizeCheck
@onready var state: Label = %StateLabel
func _ready() -> void:
option.add_item("日本語")
option.add_item("English")
_pull()
slider.value_changed.connect(_on_volume_changed)
slider.drag_ended.connect(_on_drag_ended)
option.item_selected.connect(_on_locale_selected)
check.toggled.connect(_on_maximize_toggled)
%ResetButton.pressed.connect(_on_reset_pressed)
func _on_volume_changed(value: float) -> void:
Settings.set_volume(value) # エンジンにだけ効かせる(ファイルには書かない)
_refresh()
func _on_drag_ended(changed: bool) -> void: # changed は音量ではなく、つまむ前と値が変わったかの真偽
if changed:
Settings.save() # 離したときに1回だけ書く
_refresh()
func _on_locale_selected(index: int) -> void:
Settings.set_locale_code(Settings.LOCALES[index])
_refresh()
func _on_maximize_toggled(on: bool) -> void:
Settings.set_maximized(on)
_refresh()
func _on_reset_pressed() -> void:
Settings.reset()
_pull()
func _pull() -> void:
slider.set_value_no_signal(Settings.volume) # シグナルを出さずに戻す
check.set_pressed_no_signal(Settings.maximized)
option.select(Settings.LOCALES.find(Settings.locale))
_refresh()
func _refresh() -> void: # 確かめるための表示
var config := ConfigFile.new()
config.load(Settings.PATH) # 読めなければ、下の2つは -1 と「—」になる
var saved_volume: float = config.get_value("audio", "master", -1.0)
var saved_locale := str(config.get_value("display", "locale", "—"))
state.text = "音量 画面 %.2f / エンジン %.2f / ファイル %.2f\n言語 画面 %s / エンジン %s / ファイル %s" % [
slider.value, db_to_linear(AudioServer.get_bus_volume_db(0)), saved_volume,
Settings.LOCALES[maxi(option.selected, 0)], TranslationServer.get_locale(), saved_locale
]
_refresh() はこの記事のための表示で、実際のゲームには要りません。3つの場所がいま何を持っているかを、そのまま並べています。値が変わるたびにファイルを読み直すので、確かめ終わったら外してください。
③ 動かして確かめる

再生して、言語を「English」にし、音量のつまみを左へ寄せて 0.20 あたりにし、「最大化」をオンにします。ウィンドウが画面いっぱいに広がり、下の2行は音量が3つとも同じ数、言語が3つとも en になります。広がらないときは、まず Embed Game on Next Play のチェックが外れているかを見てください。
エディタの停止ボタンで止めて、もう一度再生します。ウィンドウは最大化された状態で立ち上がり、つまみは 0.20 の位置、ドロップダウンは「English」です。何も触らずに停止ボタンで止めて settings.cfg を開いても、locale="en" のままです。ファイルの更新日時も前回のままで、開いただけでは1回も書いていないことが分かります。保存先のフォルダは、エディタの Project メニューの Open User Data Folder で開けます。
次に、つまみを右の 0.70 あたりまでドラッグします。動かしているあいだは 画面 と エンジン だ けが動き、ファイル は 0.20 のままです。指を離した瞬間に ファイル も 0.70 になります。「既定に戻す」を押すと、ウィンドウが元の大きさに戻ります。つまみは 0.80 、ドロップダウンは「日本語」になり、2行とも3つの値がそろいます。

仕上げに1行足して、書く時刻の違いを見ます。main.gd の _on_volume_changed() で、Settings.set_volume(value) の次の行に Settings.save() を足します。再生して、つまみをドラッグしてください。今度はドラッグ中も ファイル の数字がついてきます。1回のドラッグのあいだに、ファイルを何十回も書いているということです。足した1行を消せば、書くのは指を離したときの1回に戻ります。
ポイントは2つです。
- 代入と操作は同じ入口を通る: 画面へ値を戻すときは
set_value_no_signal()とset_pressed_no_signal()で入れます。OptionButton だけはselect()がもともとシグナルを出さないので、そのまま呼べます - 書く値の出どころを1つにする:
save()が見ているのは Settings の変数だけです。画面がどこまで組み上がっていても、ファイルの中身は変わりません
項目を増やすときは、定数と変数を1組足します。そのうえで 、_ready() の読み込み・ _apply() ・ reset() ・ save() ・画面側の _pull() に1行ずつ足します。触る場所が決まっているので、増えても散らかりません。キーの割り当てまで入れるなら、差し替えの作法は InputMap入門 にあります。
うまくいかないときは
| 症状 | 原因 | 直し方 |
|---|---|---|
| 効果音用のスライダーを足したら、動かしても音量がほとんど変わらない | 0〜1 の値をそのまま set_bus_volume_db() に渡している。受け取るのはデシベルで、0 は元の音量のまま | linear_to_db() で変換してから渡す |
| 設定画面をポーズメニューから開くようにしたら、スライダーもボタンも反応しない | get_tree().paused = true のあいだは、Process Mode が既定の Inherit の画面も止まり、入力を受け取らない | 設定画面のルート(Main)の Process Mode を Always にする(ポーズ入門) |
| ゲームに「終了」ボタンを足して閉じると、ホイールで変えた音量が残らない | get_tree().quit() で閉じると、NOTIFICATION_WM_CLOSE_REQUEST が届かない | 終了ボタンでは、quit() の前に Settings.save() を呼ぶ |
| 言語を「English」にしても、画面の文字が英語にならない | set_locale() は言語を切り替えるだけで 、訳文は翻訳ファイルから来る | 翻訳ファイルを Project Settings の Localization → Translations に登録する(多言語対応入門) |
おまけ:先に知っておくと良いこと
- セーブデータとは別のファイルに分ける: 設定と進行は寿命が違います。セーブを消したら音量まで消えた、という事故は入れ物を分けるだけで防げます。項目の形式を後から変えるときは、版の番号を入れて変換します(セーブデータのバージョン管理)
- 「キャンセル」は後回しでよい: 変えた瞬間に効かせる作りと両立させるには、開いた時点の状態を一式覚えておく必要があります。まずは「既定に戻す」だけで足ります
まとめ
- 3つの場所に分ける: 変えるのは画面、効かせるのと残すのは Autoload の Settings
- 代入もシグナルを出す: 画面へ戻すときは
set_value_no_signal()・set_pressed_no_signal()。OptionButton.select()はもともと出さない - 書く値は変数から: 画面のコントロールを見て回らないので、組み上がり具合に左右されない
- 書くのは操作の終わり: ドラッグ中はエンジンだけ。
drag_endedとウ ィンドウを閉じるときの通知で残す - 既定値はコードに1組: ファイルが無い・キーが無い・値がおかしい、の3つとも同じ定数へ戻す
まずは実践の3項目を動かして、画面 と エンジン と ファイル の3つがそろうところを見てください。言語の切り替えを実際の訳文までつなぐなら、多言語対応入門 に続きがあります。あなたのゲームの設定画面には、何を並べますか。