進行状況を保存する
Assets/
Scenes/
Arena.unity
Scripts/
すでにあるものこのレッスンで追加
スマホのゲームはしょっちゅう中断されます。電話がかかってきたり、通知が来たり、OS がメモリを空けようとアプリを落としたりします。アプリを閉じただけで進行度が失われるゲームに、人は戻ってきません。
素の C# クラスを JSON にして persistentDataPath に書き出し、ゲームを起動したら読み戻します。
PlayerPrefs か、ファイルか
✓ PlayerPrefs
✓ JSON file
ステップ 1 — データだけの class
セーブ用クラスが保持するのは値だけで、MonoBehaviour は保持しません。GameObject は serialise できませんから、その代わりとして作り直しに必要な情報を書き残しておいてください。
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class SaveData
{
public int version = 1; // bump this when the shape changes
public int level = 1;
public float health = 100f;
public int gold;
public float playTimeSeconds;
public string sceneName = "Arena";
public Vector3 playerPosition; // Unity serialises Vector3 fine
public List<string> ownedArtifacts = new List<string>();
public List<string> equippedArtifacts = new List<string>();
public string savedAtUtc = "";
}ステップ 2 — 書き出して読み込む
using System;
using System.IO;
using UnityEngine;
public static class SaveSystem
{
// the ONLY folder you are allowed to write to on iOS and Android
private static string Path => System.IO.Path.Combine(Application.persistentDataPath, "save.json");
public static bool Exists => File.Exists(Path);
public static void Save(SaveData data)
{
data.savedAtUtc = DateTime.UtcNow.ToString("o");
string json = JsonUtility.ToJson(data, prettyPrint: true);
string temp = Path + ".tmp";
// if the phone dies mid-write, the old save is still intact
File.WriteAllText(temp, json);
File.Copy(temp, Path, overwrite: true);
File.Delete(temp);
Debug.Log("Saved to " + Path);
}
public static SaveData Load()
{
if (!Exists) return new SaveData(); // first run
try
{
string json = File.ReadAllText(Path);
SaveData data = JsonUtility.FromJson<SaveData>(json);
return Migrate(data);
}
catch (Exception e)
{
// a corrupt save must never brick the game
Debug.LogError("Save file unreadable, starting fresh: " + e.Message);
File.Move(Path, Path + ".broken");
return new SaveData();
}
}
public static void Delete()
{
if (Exists) File.Delete(Path);
}
/// old saves must keep working after you ship an update
private static SaveData Migrate(SaveData data)
{
if (data.version < 1)
{
data.gold = 0;
data.version = 1;
}
return data;
}
}Application.persistentDataPath はスマホ上で書き込みできる唯一のフォルダです。Application.dataPath はアプリバンドルの中にあり、読み取り専用です。
一時ファイルに書いてから入れ替えるのは、書き込みの途中で OS にアプリを落とされても、セーブデータが壊れないための工夫です。
Load は必ず try/catch で包んでください。ファイルが壊れてゲームを開けない人は、★1 つのレビューを残していきます。
version フィールドは今まったくコストになりませんが、1.3 でフィールドを追加した日に必ず役立ちます。
ステップ 3 — 適切なタイミングで保存する
毎フレーム保存するのはダメですが、プレイヤーがボタンを押したときだけ保存するのも同じくダメです——スマホではプレイヤーにそんな余裕はありません。
using UnityEngine;
public class GameSession : MonoBehaviour
{
public static GameSession Instance { get; private set; }
public SaveData Data { get; private set; }
[SerializeField] private CharacterStats playerStats; // the Knight's
[SerializeField] private Transform player;
void Awake()
{
if (Instance != null) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
Data = SaveSystem.Load();
Apply(Data);
}
void Update() => Data.playTimeSeconds += Time.deltaTime;
void Collect()
{
Data.health = playerStats.currentHealth;
Data.playerPosition = player.position;
Data.sceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
}
void Apply(SaveData d)
{
playerStats.currentHealth = d.health;
if (d.playerPosition != Vector3.zero) player.position = d.playerPosition;
}
/// fires when the app goes to the background — THIS is the important one on mobile
void OnApplicationPause(bool paused)
{
if (paused) { Collect(); SaveSystem.Save(Data); }
}
/// desktop and a graceful exit
void OnApplicationQuit()
{
Collect();
SaveSystem.Save(Data);
}
}自動保存を連発せず、チェックポイントで保存
プレイヤーが新しいエリアに入ったとき、戦闘が終わったとき、買い物をしたとき、アプリがバックグラウンドに回ったときに保存します。
小さな JSON ファイルを 1 回書くのは 1 ミリ秒ほどです。それでも 1 秒に 60 回書けば、フラッシュメモリを確実に摩耗させます。
「保存しました」の目印を 0.5 秒だけ表示してください。保存しているのが見えないゲームを、プレイヤーは信用してくれません。
ファイルを見つけてデバッグする
# print the path from inside the game
Debug.Log(Application.persistentDataPath);
# Android — pull the save off the device
adb shell run-as com.yourstudio.yourgame cat files/save.json
# Editor paths
# macOS ~/Library/Application Support/CompanyName/ProductName/
# Windows %USERPROFILE%\AppData\LocalLow\CompanyName\ProductName\