【Godot】セーブ/ロードシステムの実装 - JSON、ConfigFile、カスタムリソースの徹底比較

作成: 2025-12-08

Godot Engineでゲームデータを永続化するための主要な3つの方法(JSON、ConfigFile、カスタムリソース)を、初心者向けに徹底比較し、具体的な実装例とともに解説します。

ゲーム開発において、プレイヤーの進行状況や設定を永続的に保存し、次回起動時に復元できる セーブ/ロードシステム は、ゲーム体験の根幹をなす重要な要素です。

Godot Engineでは、データを永続化するためのいくつかの強力な組み込み機能が提供されています。本記事では、特に初心者から中級者の開発者に向けて、最も一般的に使用される3つの方法、すなわち JSONConfigFile、そして カスタムリソース(Custom Resource) を徹底的に比較し、それぞれの特徴と具体的な実装方法を解説します。

データの永続化:なぜこのトピックが重要なのか

Godot Engineは、Vector2Colorといった独自のデータ型を多用します。これらのGodot固有のデータ型を、ファイルシステム上で安全かつ効率的に保存・復元する仕組み(シリアライズデシリアライズ)を理解することが、堅牢なセーブシステム構築の鍵となります。

1. ConfigFile:設定ファイルに最適なシンプルな構造

ConfigFileクラスは、WindowsのINIファイルやUnixの設定ファイルに似た、セクションとキー・値のペアでデータを管理する形式です。

ConfigFileのメリット・デメリット

メリットデメリット
シンプルさ複雑な階層構造や配列の保存には不向き
人間が読みやすい大量のゲームデータには非効率
Godotネイティブ

ConfigFileを使った実装例

extends Node

const SAVE_PATH = "user://settings.cfg"

func save_settings(volume: float, fullscreen: bool) -> void:
    var config = ConfigFile.new()
    config.set_value("audio", "master_volume", volume)
    config.set_value("video", "fullscreen", fullscreen)

    var error = config.save(SAVE_PATH)
    if error != OK:
        print("設定の保存に失敗しました: ", error)

func load_settings() -> Dictionary:
    var config = ConfigFile.new()
    var error = config.load(SAVE_PATH)

    if error != OK:
        return {"volume": 1.0, "fullscreen": false}

    var volume = config.get_value("audio", "master_volume", 1.0)
    var fullscreen = config.get_value("video", "fullscreen", false)

    return {"volume": volume, "fullscreen": fullscreen}

2. JSON:汎用性と複雑なデータ構造への対応

JSON(JavaScript Object Notation)は、Webの世界で広く使われている軽量なデータ交換フォーマットです。

JSONのメリット・デメリット

メリットデメリット
汎用性Godot固有の型を直接保存できない
複雑な構造読み書きに手動変換が必要
デバッグの容易さ

JSONを使った実装例

extends Node

const SAVE_PATH = "user://game_save.json"

func save_game_data(player_position: Vector2, inventory: Array) -> void:
    var save_data = {
        "player_pos": [player_position.x, player_position.y],
        "inventory": inventory,
        "timestamp": Time.get_unix_time_from_system()
    }

    var json_string = JSON.stringify(save_data, "\t")

    var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
    if file:
        file.store_string(json_string)
        file.close()

func load_game_data() -> Dictionary:
    if not FileAccess.file_exists(SAVE_PATH):
        return {}

    var file = FileAccess.open(SAVE_PATH, FileAccess.READ)
    if file:
        var json_string = file.get_as_text()
        file.close()

        var parse_result = JSON.parse_string(json_string)
        if parse_result is Dictionary:
            var loaded_data = parse_result
            var pos_array = loaded_data.get("player_pos", [0.0, 0.0])
            loaded_data["player_pos"] = Vector2(pos_array[0], pos_array[1])
            return loaded_data

    return {}

3. カスタムリソース(Custom Resource):Godotネイティブなセーブの決定版

Godot Engineにおいて、最も推奨され、強力なセーブ方法が カスタムリソース の利用です。

カスタムリソースのメリット・デメリット

メリットデメリット
型安全性外部からの改ざんリスクがある
Godot固有型を直接サポート外部アプリケーションとの連携に不向き
少ないコード量

カスタムリソースを使った実装例

# SaveGame.gd
class_name SaveGame
extends Resource

@export var coins := 0
@export var player_global_position := Vector2(0, 0)
@export var unlocked_levels := []
# SaveManager.gd
extends Node

const SAVE_PATH = "user://game_save.tres"
var current_save: SaveGame = null

func load_game() -> void:
    if ResourceLoader.exists(SAVE_PATH):
        current_save = ResourceLoader.load(SAVE_PATH, "", ResourceLoader.CACHE_MODE_IGNORE)
    else:
        current_save = SaveGame.new()

func save_game() -> void:
    var error = ResourceSaver.save(current_save, SAVE_PATH)
    if error != OK:
        print("セーブに失敗しました: ", error)

徹底比較:どの方法を選ぶべきか

特徴ConfigFileJSONカスタムリソース
主な用途設定ファイル複雑なデータ、外部連携ゲームのセーブデータ全体
データ構造シンプルなキー/値複雑な階層、配列、辞書複雑な階層、Godot固有型
Godot固有型サポート非サポート(手動変換必要)完全サポート
コード量少ない多い(変換処理が必要)最も少ない

実践的な活用:使い分けの指針

  1. ConfigFile: ゲーム設定(音量、グラフィック設定、キーバインド)に使用
  2. JSON: 外部ツールとのデータ連携、大量の静的データ(アイテムリスト)に使用
  3. カスタムリソース: ゲームのセーブデータ(プレイヤーの状態、インベントリ、マップ情報など)に最も推奨

まとめ

Godot Engineでセーブ/ロードシステムを実装する際、ConfigFile は設定ファイルに、JSON は汎用的なデータ交換に、そして カスタムリソース はゲームのセーブデータ本体に、それぞれ最適な選択肢となります。

特にカスタムリソースは、Godotの設計思想に最も合致しており、@export変数とResourceSaver/ResourceLoaderを使うだけで、複雑なゲームデータを驚くほど簡単に永続化できます。