dasukoの技術ブログ

現役エンジニアのブログです。

【Unity】ゲームデータの保存を実装してみる

はじめに

前回このような"ゲームデータの保存と読み込み方法"に関する記事を書きました。

dasuko.hatenadiary.jp

今回は実際に実装してみたいと思います。

まずは簡単に実装してみる

まずはPlayerPrefsを使って簡単に実装してみます。

※セーブデータを定義したDataクラスがあると仮定します。

// 保存
var json = JsonUtility.ToJson(data);
PlayerPrefs.SetString(key, json);

// 読み込み
var json = PlayerPrefs.GetString(key);
var data = JsonUtility.FromJson<Data>(json);

// 削除
PlayerPrefs.DeleteKey(Key);

// 全て削除
PlayerPrefs.DeleteAll();

Jsonを操作する時にはJsonUtilityを使っています。

JsonUtilityに関する記事もあるので、見てみてください!

dasuko.hatenadiary.jp

少しだけ拡張してみる

保存するためのキーをコンストラクタで渡すようにしました。

読み込み、保存時処理はジェネリックで実装しました。

using UnityEngine;

public class SaveData
{
    private string customKey;

    protected virtual string Key { get { return "SaveData." + GetType() + customKey; } }

    public SaveData(string key)
    {
        customKey = key;
    }

    public T Load<T>() where T : new()
    {
        var json = PlayerPrefs.GetString(Key);
        return JsonUtility.FromJson<T>(json);
    }

    public void Save<T>(T data) where T : new()
    {
        var json = JsonUtility.ToJson(data);
        PlayerPrefs.SetString(Key, json);
    }

    public void Delete()
    {
        PlayerPrefs.DeleteKey(Key);
    }

    public static void DeleteAll()
    {
        PlayerPrefs.DeleteAll();
    }
}

具体的な実装

更にデータを暗号化して保存してみます。

cryptorは暗号化の実装を変更できるようにインターフェースにしてみました。

今回実装したAESについてはこちらを参照

Advanced Encryption Standard - Wikipedia

実装方法についてはこちら

RijndaelManaged クラス (System.Security.Cryptography) | Microsoft Docs

public class SaveData<T> where T : SaveData<T>, new()
{
    private string customKey = string.Empty;
    private ICryptor cryptor;

    protected virtual string Key { get { return string.Format("{0}.{1}.{2}", GetType(), typeof(T), customKey); } }

    public SaveData()
    {
        cryptor = new AESCryptor();
        Load();
    }

    public SaveData(string key)
    {
        customKey = key;
    }

    public void Load()
    {
        var encryptedData = PlayerPrefs.GetString(Key);
        var json = cryptor.Decrypt(encryptedData);
        JsonUtility.FromJsonOverwrite(json, this);
    }

    public void Save()
    {
        var json = JsonUtility.ToJson(this);
        var encryptedData = cryptor.Encrypt(json);
        PlayerPrefs.SetString(Key, encryptedData);
        PlayerPrefs.Save();
    }

    public void Delete()
    {
        PlayerPrefs.DeleteKey(Key);
    }

    public static void DeleteAll()
    {
        PlayerPrefs.DeleteAll();
    }
}

public class AESCryptor : ICryptor
{
    private const string AES_IV = "KPQASpi3HmAYvGAE";
    private const string AES_KEY = "NEGsvvr9KB3PvHhF";

    public string Encrypt(string data)
    {
        return Encrypt(data, AES_KEY, AES_IV);
    }

    public string Decrypt(string data)
    {
        return Decrypt(data, AES_KEY, AES_IV);
    }

    public static string Encrypt(string text, string key, string iv)
    {
        using (var rijAlg = new RijndaelManaged())
        {
            rijAlg.Key = Encoding.UTF8.GetBytes(key);
            rijAlg.IV = Encoding.UTF8.GetBytes(iv);

            var cryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);

            using (var ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(ms, cryptor, CryptoStreamMode.Write))
                {
                    using (var sw = new StreamWriter(cs))
                    {
                        sw.Write(text);
                    }
                }

                return Convert.ToBase64String(ms.ToArray());
            }
        }
    }

    public static string Decrypt(string text, string key, string iv)
    {
        using (var rijAlg = new RijndaelManaged())
        {
            rijAlg.Key = Encoding.UTF8.GetBytes(key);
            rijAlg.IV = Encoding.UTF8.GetBytes(iv);

            var decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);

            using (var ms = new MemoryStream(Convert.FromBase64String(text)))
            {
                using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                {
                    using (var sr = new StreamReader(cs))
                    {
                        return sr.ReadToEnd();
                    }
                }
            }
        }
    }
}

public class GameData : SaveData<GameData>
{
    public static GameData Data { get; } = new GameData();

    public string id;
}

実際に動かしてみます。

まずは以下を実行してみます。

        var data = GameData.Data;
        data.id = "user_id";
        data.Save();

次にアプリを再起動し、ログを出力してみます。

        var data = GameData.Data;
        data.Load();

        Debug.Log("id:" + data.id);

実行してみます…   

f:id:dasuko:20201002012700p:plain

期待通り、"user_id"という文字列が出力されました!

最後に

今回はコンストラクタでLoadを呼んでいますが、

わかりづらいようであれば、分けてもいいかなと思います。

セーブデータをファイルに保存するパターンの実装も紹介できれば紹介したいと思います。

参考

Unityでセーブデータを暗号化してSerialize保存 ~その2~ - Qiita

【Unity】Jsonファイルとしてデータを保存する - Qiita

いい感じのUnity用セーブデータ管理クラス🎍 - Qiita

RijndaelManaged クラス (System.Security.Cryptography) | Microsoft Docs

c#の暗号化クラスを使ってみた(AES,RSA) - Qiita