【Godot】保存失敗時の再試行ボタンを作る|警告と進行を保持する
保存に失敗したときは、現在の進行を残したまま再試行できると便利です。言語を切り替えても警告を残し、再保存が成功した後の連打で別のボタンが動かないようにするには、進行データ・保存結果・表示文を別々に持つことが大切です。
この記事では、保存の基本を試した方向けに、保存失敗中だけ「再保存」を表示するGodot 4.6の小例を作ります。再保存には現在の進行を渡し、成功後はリセットへフォーカスを移さないようにします。保持するのは実行中のメモリ上の進行です。保存前に終了すると、その進行は失われます。
設定の保存と起動時の読み込みから始めたい場合は、ConfigFileで音量設定を保存・復元する方法を先に確認してください。本記事は、書き込みに失敗した後の画面と入力を扱います。

1. 独立したサンプルで失敗と再保存を試す
Godotサンプル一式をダウンロードし、ZIPを展開し、GodotのProject ManagerでImportを押してsample/project.godotを選びます。プロジェクトを開いたらRun Project(標準ではF5)で実行してください。本文とサンプルはGodot 4.6向けで、処理の実行確認には4.6.3を使っています。既存のゲームへ直接貼り付けず、独立したプロジェクトとして試してください。
- 実行して「進行を1増やして保存」を押します。初期設定では失敗を意図的に返すため、進行1と保存失敗の警告が表示されます。
- 「日本語 / English」を押します。言語が変わっても警告と再保存ボタンが残ります。
- 「失敗を再現」をオンのまま再保存します。再び失敗し、進行は1のままです。
- 「失敗を再現」をオフにします。この操作だけでは警告は消えません。「再保存」を押して成功すると消えます。
- 成功した後にEnter・Spaceを続けて押します。再保存があった位置もクリックし、進行が0へ戻らないことを確認します。
専用のuser://retry_learning_example.jsonへJSONを書き込みます。user://はアプリのユーザーデータ保存用パスです。サンプルには起動時の読み込み機能がなく、再起動時の進行は0から始まります。Godot公式:ファイルパス
サンプルの画面は640×360です。F5実行時のゲームビューが小さく、右端のスイッチが見切れる場合は、ゲームビューのウィンドウを最大化して640×360を表示できる大きさにしてください。画面内の「失敗を再現」は保存失敗を安全に試すためのスイッチで、ディスク容量や権限を変更する機能ではありません。
シーンのルートはVBoxContainerです。save_demo.gdがラベル・ボタン・失敗切替を生成して接続します。以下は同じスクリプトから抜き出した関数です。完全な変数定義・シーン・接続処理はサンプルに含めています。
2. 警告文を保存状態の代わりに使わない
次の3つを別の変数にすると、表示を更新しても進行や保存結果が変わりません。
- progress(int):現在メモリ上にある進行。再保存時はこの値を使う
- save_failed(bool):直近の保存が失敗したか。保存結果を受け取ったときだけ更新する
- english(bool):このサンプルの表示言語。切り替えても保存し直さない
GDScriptの変数宣言にはvarが必要なので、サンプルでは「var progress: int」のように型を明記しています。日本語と英語の切り替えは状態の保持を確認するための小例で、TranslationServerを使う本格的な多言語対応ではありません。
言語を変える時はenglishを切り替えて表示を作り直します。save_failedは変更しません。
GDScript(構文色分けなし)のコードです。
func refresh_ui() -> void:
status.text = ("Progress: %d" if english else "進行: %d") % progress
if save_failed:
status.text += " / Save failed" if english else " / 保存失敗"
retry_button.text = "Retry save" if english else "再保存"
retry_button.visible = save_failed
retry_button.disabled = not save_failed
retry_button.focus_mode = Control.FOCUS_ALL if save_failed else Control.FOCUS_NONE失敗中は再保存を表示し、クリック可能・フォーカス可能にします。成功後は表示・有効状態・フォーカス対象をまとめて切り替えます。ラベルの文字列を読み取って保存の成否を判定する必要はありません。Godot公式:BaseButton
3. ファイル操作の結果を呼出し元へ返す
write_snapshot()はErrorを返します。fail_demoがtrueの場合は説明用に失敗させています。通常の保存では、FileAccess.open()がnullならget_open_error()を返します。書き込み時はstore_string()の成否とget_error()を確認します。Godot公式:FileAccess
GDScript(構文色分けなし)のコードです。
func write_snapshot() -> Error:
write_attempts += 1
if fail_demo:
return ERR_CANT_CREATE # Deterministic demo injection, not a disk test.
var file: FileAccess = FileAccess.open(save_path, FileAccess.WRITE)
if file == null:
return FileAccess.get_open_error()
var stored: bool = file.store_string(JSON.stringify({"progress": progress}))
file.flush()
var error: Error = file.get_error()
file.close()
if not stored and error == OK:
return ERR_FILE_CANT_WRITE
return errortry_save()がその結果を保存状態へ反映し、表示を更新します。再保存ボタンを押した瞬間ではなく、書き込み成功を確認した時に警告を消します。
GDScript(構文色分けなし)のコードです。
func try_save() -> bool:
last_error = write_snapshot()
save_failed = last_error != OK
refresh_ui()
return not save_failedこの単純な保存例は古いセーブを保護する仕組みを含みません。FileAccess.WRITEは既存ファイルを開くと内容を切り詰めます。容量不足などで途中失敗した場合、古いデータを失う可能性があります。一時ファイル・バックアップ・置換失敗からの復旧は本番向けに別途設計してください。flush()には戻り値がなく、この処理だけで停電時の永続化は保証できません。
4. 再保存の成功後まで入力を確認する
retry_save()は現在のprogressをもう一度保存します。初期化や古いセーブの読み込みは呼びません。成功後にもう一度呼ばれても、save_failedがfalseなら何もしません。
GDScript(構文色分けなし)のコードです。
func retry_save() -> void:
if not save_failed:
return # Ignore a queued second activation after success.
if not try_save():
retry_button.grab_focus()
return
var focused: Control = get_viewport().gui_get_focus_owner()
if focused != null:
focused.release_focus() # Do not redirect success focus to Reset.失敗が続く時は再保存ボタンへフォーカスを戻します。この小例では成功後にフォーカスを外します。別のボタンへTabで移るまでは、Enter・Spaceを続けて押してもリセットに届かないようにしています。キーボードで次の作業へ進めたい画面では、破壊的な操作ではない明確な移動先を用意する設計も考えられます。Godot公式:Control
さらに、再保存を隠した時にリセットボタンが同じ位置へ移動しないよう、再保存用の高さ44の空間を残しています。見た目の非表示だけでなく、連打・二重呼出し・フォーカス・配置変化を一緒に確認しましょう。独自ショートカットや_input()で扱うキーは、このフォーカス対策だけでは止まりません。Godot公式:入力イベントの流れ
5. うまく動かないときの確認箇所
「失敗を再現」をオフにしても警告が残る
想定どおりの動作です。スイッチは次の保存で失敗させるかを変えるだけで、保存処理は呼びません。「再保存」を押し、成功して初めて警告を消します。
言語切り替えで警告が消える
言語切り替え側でsave_failedをfalseへ戻していないか確認します。表示文だけを一度消して再構築するときも、保存状態から警告をもう一度組み立てます。
再保存ボタンを隠した後にリセットが動く
リセットへgrab_focus()していないか、再保存の非表示でリセットの位置が上へ移動していないか確認します。サンプルではフォーカスを外し、高さ44のControlを残して配置移動を防いでいます。独自の入力処理がリセットを直接呼んでいる場合は、その経路も確認してください。
失敗を再現していないのに保存できない
Debuggerでlast_errorを確認し、error_string(last_error)でエラー名を調べます。保存先を変更した場合は、既存のフォルダーを指しているか、サブフォルダーが存在するかを確認してください。再試行を繰り返すだけでは保存先や空き容量の問題は解決しません。
6. 再現テストと、この小例の範囲
2026年10月12日に、同梱コードをGodot 4.6.3のヘッドレスで再実行し、31項目すべての成功を確認しました。失敗注入、実際に開けない保存先、JSONの書き込みと読み戻し、言語切替時の書込回数、再失敗・復旧、成功後の二重呼出し、合成したEnter・Spaceを確認しています。テストはサンプルのフォルダで次のコマンドから再実行できます。
godot --headless --single-threaded-scene --disable-render-loop --script res://test_retry.gd自動テストは専用の一時JSONを作成・読み戻し、終了前に削除します。「失敗を再現」による失敗ではファイルを開かないため、古い保存内容が残ることも検査しています。これは実際の途中書き込み失敗から古いファイルが守られる検証ではありません。
同梱版をGodot 4.6.3のエディターへインポートし、F5で実行したGUIでも確認しました。日本語から英語への切り替え、Tabで再保存へ移動、Enter・Spaceでの再失敗と成功、成功後の連打と旧ボタン位置クリックでも進行1を保持しています。GUIの失敗は意図的な失敗注入です。エクスポート、他OS・他版、容量不足、強制終了・電源断は未検証です。
実際のゲームへ組み込む時は、起動時読込・バックアップ・同時書込・終了時の扱いも必要です。まずは「失敗→言語切替→再失敗→成功→もう一度入力」をつなげて確認すると、警告の消失と保存後の誤操作を見つけやすくなります。
まとめ
保存失敗時の再試行は、現在の進行を保持し、実際に書き込めるまで警告を残す形にします。成功したら再試行を無効にし、連打の入力先とボタンの配置まで確認すると、復旧後の誤操作を防ぎやすくなります。
次に確認したい関連記事
- ConfigFileで音量設定を保存・復元する方法:起動時の読み込みと初回起動の扱いを追加したいとき
- ボタンのpressedシグナルを接続する方法:押しても処理が呼ばれないとき
- Containerと文字の折り返しでUIを整える方法:小さい画面で警告やボタンがはみ出すとき
サイトアイコン-2-150x150.png)