LoginSignup
11
13

More than 1 year has passed since last update.

【Unity】 jsonファイルを使ったセーブとロード

Last updated at Posted at 2022-09-26

■ はじめに

ゲームに不可欠なセーブデータを作りましょう!!
今回は、スコアのランキングを例にデータのセーブとロードの処理を実装していきます。

(個人的な忘備録のついでとして記入してます。もっと効率的な方法があるかもですが、ご了承ください)

■ 実装結果

こんな感じのができます。一度ゲームを終了しても、保存されたデータをもとにランキングが表示されます。
savedata.gif

GitHubにもアップロードしたので、どうぞ↓

■ 説明

○ 必要なもの

  1. 保存するデータのクラス
  2. セーブやロードの処理をするクラス
  3. ランキングの表示、ランキングの値の保存などの処理をするクラス

・3つのクラスのみで作成できます。コードの量もそこまで多くないですが、少し複雑かもです。
・今回はランキングを例としていますが、ハイスコアやゲームのセーブデータの保存などに流用できます。

○ すること

● 保存データクラス

  1. 保存したい変数を宣言
  2. クラスのシリアライズ

● セーブロードクラス

  1. ファイルのパスを取得
  2. 開始時、ファイルがあるかどうか確認
  3. ファイルがなかったら作成
  4. ファイルがあれば、読み込んで「保存データクラス」型の変数に格納
  5. 終了時、データを保存

● ランキングクラス

  1. 「セーブロードクラス」から「保存データクラス」型の変数を参照
  2. ランキングに表示するテキストのオブジェクトとコンポーネントの取得
  3. 常時、ランキングを表示
  4. 入力欄に数値を入力後、Enterボタンを押したらランキング内の数値入れ替え、数値の保存
  5. Deleteボタンを押したらランキング内の数値を削除

■コード

① 保存データクラス(SaveData)

SaveData.cs
[System.Serializable]
public class SaveData {
    public const int rankCnt = 3;
    public int[] rank = new int[rankCnt];
}
  • [System.Serializable]でシリアル化する必要があります。
  • 宣言するメンバ変数には、publicを付ける必要があります。

② セーブロードクラス(DataManager)

DataManager.cs
using System.IO;
using UnityEngine;

public class DataManager : MonoBehaviour 
{
    [HideInInspector] public SaveData data;     // json変換するデータのクラス
    string filepath;                            // jsonファイルのパス
    string fileName = "Data.json";              // jsonファイル名

    //-------------------------------------------------------------------
    // 開始時にファイルチェック、読み込み
    void Awake()
    {
        // パス名取得
        filepath = Application.dataPath + "/" + fileName;       

        // ファイルがないとき、ファイル作成
        if (!File.Exists(filepath)) {
            Save(data);
        }

        // ファイルを読み込んでdataに格納
        data = Load(filepath);          
    }

    //-------------------------------------------------------------------
    // jsonとしてデータを保存
    void Save(SaveData data)
    {
        string json = JsonUtility.ToJson(data);                 // jsonとして変換
        StreamWriter wr = new StreamWriter(filepath, false);    // ファイル書き込み指定
        wr.WriteLine(json);                                     // json変換した情報を書き込み
        wr.Close();                                             // ファイル閉じる
    }

    // jsonファイル読み込み
    SaveData Load(string path)
    {
        StreamReader rd = new StreamReader(path);               // ファイル読み込み指定
        string json = rd.ReadToEnd();                           // ファイル内容全て読み込む
        rd.Close();                                             // ファイル閉じる
                                                                
        return JsonUtility.FromJson<SaveData>(json);            // jsonファイルを型に戻して返す
    }

    //-------------------------------------------------------------------
    // ゲーム終了時に保存
    void OnDestroy()
    {
        Save(data);
    }
}
  • ファイルへの書き込み、読み込みにはusing System.IO;が必要になります。

Awake()

該当箇所のコード(クリックで展開)
    // 開始時にファイルチェック、読み込み
    void Awake()
    {
        // パス名取得
        filepath = Application.dataPath + "/" + fileName;       

        // ファイルがないとき、ファイル作成
        if (!File.Exists(filepath)) {
            Save(data);
        }

        // ファイルを読み込んでdataに格納
        data = Load(filepath);          
    }
  • filepath = Application.dataPath + "/" + fileName;ファイルのパス名を取得します。

    • Application.dataPathは、プラットフォームによってパスが変わります。(詳細)

    • ※追記: Android で使用する場合、 Application.datapathApplication.persistentDataPathに変更する

    Example.cs
    #if UNITY_EDITOR
        filepath = Application.dataPath + "/" + fileName;
    
    #elif UNITY_ANDROID
        filepath = Application.persistentDataPath + "/" + fileName;    
    
    #endif
    
  • if (!File.Exists(filepath))データファイルの有無を確認します。

  • ファイルが無い場合、Save(data);でファイルを作成します。

  • data = Load(filepath);でファイルを読み込みます。

参照


Save(SaveData data)

該当箇所のコード(クリックで展開)
    // jsonとしてデータを保存
    void Save(SaveData data)
    {
        string json = JsonUtility.ToJson(data);                 // jsonとして変換
        StreamWriter wr = new StreamWriter(filepath, false);    // ファイル書き込み指定
        wr.WriteLine(json);                                     // json変換した情報を書き込み
        wr.Close();                                             // ファイル閉じる
    }
  • 引数dataは、保存するSaveData型のデータを受けとります。
  • JsonUtility.ToJson(data); SaveData型からjson(string型)に変換します。
  • StreamWriter wr = new StreamWriter(filepath, false);上書き保存するよう指定します。
  • wr.WriteLine(json);ファイルにSaveData内の変数を書き込みます。

参照

Load(string path)

該当箇所のコード(クリックで展開)
    // jsonファイル読み込み
    SaveData Load(string path)
    {
        StreamReader rd = new StreamReader(path);               // ファイル読み込み指定
        string json = rd.ReadToEnd();                           // ファイル内容全て読み込む
        rd.Close();                                             // ファイル閉じる
                                                                
        return JsonUtility.FromJson<SaveData>(json);            // jsonファイルを型に戻して返す
    }
  • 引数pathは、読み込むjsonファイルのパスを受けとります。
  • return JsonUtility.FromJson<SaveData>(json);で、jsonファイルからSaveData型に変換して返します。

参照


OnDestroy()

該当箇所のコード(クリックで展開)
    // ゲーム終了時に保存
    void OnDestroy()
    {
        Save(data);
    }
  • ゲーム終了時にファイルをセーブするようにしてます。

③ ランキングクラス(Ranking)

Ranking.cs
using UnityEngine;
using UnityEngine.UI;

public class Ranking : MonoBehaviour
{
    /* 値 */
    string[]    rankNames = { "1st", "2nd", "3rd" };        // ランキング名
    const int   rankCnt = SaveData.rankCnt;                 // ランキング数

    /* コンポーネント取得用 */
    Text[]      rankTexts = new Text[rankCnt];              // ランキングのテキスト
    SaveData    data;                                       // 参照するセーブデータ

    //-------------------------------------------------------------------
    void Start()
    {
        data = GetComponent<DataManager>().data;            // セーブデータをDataManagerから参照

        for (int i = 0; i < rankCnt; i++) {
            Transform rankChilds = GameObject.Find("RankTexts").transform.GetChild(i);      // 子オブジェクト取得
            rankTexts[i] = rankChilds.GetComponent<Text>();                                 // 子オブジェクトのコンポーネント取得
        }
    }

    //-------------------------------------------------------------------
    void FixedUpdate()
    {
        DispRank();
    }

    // ランキング表示
    void DispRank()
    {
        for (int i = 0; i < rankCnt; i++) {
            rankTexts[i].text = (rankNames[i] + " : " + data.rank[i]);
        }
    }

    // ランキング保存
    public void SetRank()
    {
        InputField inpFld = GameObject.Find("InputField").GetComponent<InputField>();
        int score = int.Parse(inpFld.text);     // string -> int

        // スコアがランキング内の値よりも大きいときは入れ替え
        for (int i = 0; i < rankCnt; i++) {
            if (score > data.rank[i]) {
                var rep = data.rank[i];
                data.rank[i] = score;
                score = rep;
            }
        }
    }

    // ランクデータの削除
    public void DelRank()
    {
        for (int i = 0; i < rankCnt; i++) {
            data.rank[i] = 0;
        }
    }
}

  • TextやInputFieldなどを使用するので、using UnityEngine.UI;が必要になります。
  • ランキングの要素数rankCntは、SaveDataクラスから参照してます。
  • DataManagerクラスでAwakeをしてからRankingクラスのStartを実行しないといけません。

Start()

該当箇所のコード(クリックで展開)
    void Start()
    {
        data = GetComponent<DataManager>().data;            // セーブデータをDataManagerから参照

        for (int i = 0; i < rankCnt; i++) {
            Transform rankChilds = GameObject.Find("RankTexts").transform.GetChild(i);      // 子オブジェクト取得
            rankTexts[i] = rankChilds.GetComponent<Text>();                                 // 子オブジェクトのコンポーネント取得
        }
    }
  • dataをDataManagerから参照しています。
  • for文でランキングのテキストのオブジェクトとコンポーネントを取得しています。

DispRank()

該当箇所のコード(クリックで展開)
    // ランキング表示
    void DispRank()
    {
        for (int i = 0; i < rankCnt; i++) {
            rankTexts[i].text = (rankNames[i] + " : " + data.rank[i]);
        }
    }
  • for文でランキングのテキストを変更します。
  • FixedUpdate内で常時実行しています。

SetRank()

該当箇所のコード(クリックで展開)
    // ランキング保存
    public void SetRank()
    {
        InputField inpFld = GameObject.Find("InputField").GetComponent<InputField>();
        int score = int.Parse(inpFld.text);     // string -> int

        // スコアがランキング内の値よりも大きいときは入れ替え
        for (int i = 0; i < rankCnt; i++) {
            if (score > data.rank[i]) {
                var rep = data.rank[i];
                data.rank[i] = score;
                score = rep;
            }
        }
    }
  • int.Parse(inpFld.text)InputFieldで入力された値を取得します。
  • for文で、入力された値とランキング内の値の判定と入れ替えをしています。

DelRank()

該当箇所のコード(クリックで展開)
    // ランクデータの削除
    public void DelRank()
    {
        for (int i = 0; i < rankCnt; i++) {
            data.rank[i] = 0;
        }
    }
  • データの値を全て0にしています。

■Unity操作

○必要なもの

  • 空のオブジェクト(Manager)
  • Canvas
    • InputField
    • Enterボタン
    • Deleteボタン
    • ランキング表示用テキスト×3

image.png

○すること

  • 空のオブジェクト(Manager)に"DataManager","Ranking"をコンポーネントに追加する
    image.png

  • InputFieldの"InputField"コンポーネント内の"Content Type"を"Integer Number"に変更して、入力を整数のみに制限する
    image.png

  • Enter,Deleteボタンの"OnClick()"にManagerの"Ranking"スクリプトのpublic関数を入れる
    image.png

■さいごに

  • 重要なデータは暗号化しないと、書き換えとか簡単にできちゃうので、その辺もそのうち勉強します。
  • まだ実際のゲームにこんな感じのシステムを使用できてないので、今後とも頑張ります。

■参考

11
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
13