U
21 / 22 · 9 分
FAQ

進捗の保存:PlayerPrefs、JSON、それぞれの使いどころ

短い答え

プレイヤーの進捗を保存するには?

軽い設定には PlayerPrefs、本当のゲーム状態には Application.persistentDataPath 配下の JSON ファイル。書き込みは OnApplicationQuit だけでなく OnApplicationPause でも——スマホでは Quit はまず走りません。

Unity には保存方法が二つありますが、競合する相手ではありません。PlayerPrefs は少しの設定項目用、JSON ファイルはゲームの状態用です。ゲームの状態を PlayerPrefs に保存するのは、プロジェクトが発売間際になるまで問題に気づけないタイプのミスです。

PlayerPrefs —— 設定専用、それ以外に使い道なし

What PlayerPrefs is genuinely good at
PlayerPrefs.SetFloat("music", 0.7f);
PlayerPrefs.SetInt("language", 1);
PlayerPrefs.Save();

float music = PlayerPrefs.GetFloat("music", 1f);   // 1f if never set
  • 保存できる型は int、float、string の三つだけ。それ以外のものは、手作業でこの三つのどれかに潰して入れるしかありません。

  • ディスク上ではプレーンテキストです —— Windows はレジストリ、macOS は .plist、Android は XML ファイル。テキストエディタで開けるので、プレイヤーがスコアを書き換えることも可能です。

  • 中に何が入っているかを一覧する方法も、バージョンを振る方法もありません。セーブ形式が膨らんで PlayerPrefs を超えても、移行の道は残されていません。

JSON —— ゲームの状態そのものを保存

SaveSystem.cs — one file, two methods
using System.IO;
using UnityEngine;

[System.Serializable]
public class SaveData
{
    public int version = 1;          // pays for itself the first time you change the format
    public int level;
    public float health;
    public string[] unlocked;
}

public static class SaveSystem
{
    // persistentDataPath is the only folder that survives an app update on a
    // phone, and the only one you are allowed to write to on iOS.
    static string Path => System.IO.Path.Combine(Application.persistentDataPath, "save.json");

    public static void Save(SaveData data)
    {
        // Write to a temporary file, then swap. A crash mid-write then loses
        // the new save instead of destroying the old one.
        string tmp = Path + ".tmp";
        File.WriteAllText(tmp, JsonUtility.ToJson(data, true));
        File.Copy(tmp, Path, true);
        File.Delete(tmp);
    }

    public static SaveData Load()
    {
        if (!File.Exists(Path)) return new SaveData();
        return JsonUtility.FromJson<SaveData>(File.ReadAllText(Path));
    }
}

JsonUtility がシリアライズできるもの・できないもの

  • 保存されるのは public フィールドと、[SerializeField] が付いた private フィールドだけ。{ get; set; } の property は完全に無視されます —— はまる人が非常に多い落とし穴です。

  • Dictionary はシリアライズできません。対応する二つの配列を並べるか、[System.Serializable] を付けた小さなクラスのリストとして保存してください。

  • クラス自体にも [System.Serializable] が必要で、MonoBehaviour を継承しているといけません。セーブデータはデータであって、コンポーネントではありません。

保存するタイミング

On a phone this is the difference between saving and not saving
// Android and iOS kill a backgrounded app without ever calling
// OnApplicationQuit. Pause is the last callback you are guaranteed.
void OnApplicationPause(bool paused)
{
    if (paused) SaveSystem.Save(current);
}

void OnApplicationQuit()
{
    SaveSystem.Save(current);
}

これを作るレッスン

進行状況を保存する

この記事はそれだけで完結するレシピです。コースでは同じものを、5 つのタブを貫くひとつのプロジェクトの一部として作ります — モバイルへ書き出す のレッスン 04。