プレイデータの保存/読込

## 概要
ゲームのプレイデータの保存を、Userクラスに集約して、jsonにして保存するやり方。生徒向け。
~~※クラスのカプセル化については割愛~~
_2020/5/21 カプセル化したパターンも追記_

例で想定する仕様は、クエストごとにプレイデータが存在するってかんじ。
ユーザーに保存する予定の構造を以下と想定する。

```
- ユーザー
	- ニックネーム
	- レベル
	- 経験値
	- クエスト結果(配列)	
		- クエストID
		- チャレンジ回数
		- クリア回数
		- ハイスコア
```

## データを入れるクラスを作成

新規で`User.cs`、`QuestResult.cs`を作成する。
Unityで.csファイルを作成した時に最初から書いてあるやつは邪魔だから全部消してよし。
MonoBehaviourは継承させない。

```User.cs
using System.Collections.Generic;

[System.Serializable]
public class User {
	public string nickname;
	public int level, exp;
	public List<QuestResult> questResults;
}
```
```QuestResult.cs
[System.Serializable]
public class QuestResult {
	public int
		id,
		challengeCount,
		clearCount,
		highScore;
}
```


## Inspectorに出してみる

新規で`GameManager.cs`を作成し、中身を下記のように編集する。

```GameManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour {

	public User user;
	
	void Start () {
		//あとで
	}

	void Update () {
		//あとで
	}
}

```

CreateEmptyで空のGameObjectを作成してGameManager.csをくっつけると、
Inspectorでこのように表示されるはず。
[![https://gyazo.com/39468ef08515fedafaba29255244dfca](https://i.gyazo.com/39468ef08515fedafaba29255244dfca.png)](https://gyazo.com/39468ef08515fedafaba29255244dfca)


## jsonにして保存する

### jsonとは
この記事が分かりやすかった気がするので読んで。
https://qiita.com/SotaSuzuki/items/c3b46c4e24c1ca9b4d37

### コンソールにjsonを出力してみる
UnityでJsonを扱う場合は、JsonUtilityっていう便利なクラスが用意されてます。

`GameManager.cs`で、スペースキーを押したらUserデータをjsonにしてコンソールに出してみる、
んだけど、データになんか入ってたほうがおもしろいと思うのでちょっといれてみよう。
[![https://gyazo.com/dce7bcf31270aea4963919b7ebbe9d68](https://i.gyazo.com/dce7bcf31270aea4963919b7ebbe9d68.png)](https://gyazo.com/dce7bcf31270aea4963919b7ebbe9d68)


`GameManager.cs`を編集

```GameManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour {

	public User user;

	void Start () {
		//あとで
	}

	void Update () {
		if (Input.GetKeyDown(KeyCode.Space)) {
			string json = JsonUtility.ToJson( user );
			print ( json );
		}
	}
}

```

実行してSpaceキーを押してConsoleを見てみるとこんな感じになってるはず
[![https://gyazo.com/b508ac19c76a9552d76799addb437ce7](https://i.gyazo.com/b508ac19c76a9552d76799addb437ce7.png)](https://gyazo.com/b508ac19c76a9552d76799addb437ce7)

ちなみにこの文字列を展開してあげるとこんな感じ

```json
{
  "nickname": "imadake398yen",
  "level": 39,
  "exp": 12345,
  "questResults": [
    {
      "id": 1,
      "challengeCount": 4,
      "clearCount": 2,
      "highScore": 78
    },
    {
      "id": 2,
      "challengeCount": 6,
      "clearCount": 4,
      "highScore": 92
    }
  ]
}
```
jsonを展開してみる方法はたくさんあるけどwebサービスでもあるのでリンク乗っけとく。
コンソールに出てきたやつをコピペすれば展開してくれる。
http://www.ctrlshift.net/jsonprettyprinter/


### PlayerPrefsで保存 -> 読み込み
じゃあ、レベルがあがったら保存する。っていうふうにしてみるか。
とりあえずスペースキー押したらレベルアップにしとくね。とりあえず。

`GameManager.cs`を編集

```GameManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour {

	const string _SaveKey = "UserData";
	public User user;
  
	void Start () {
		//読込
		string json = PlayerPrefs.GetString(_SaveKey);
		user = JsonUtility.FromJson<User>(json);
	}

	void Update () {
		if (Input.GetKeyDown(KeyCode.Space)) {
			LevelUp();
		}
	}
  
	void LevelUp () {
		user.level += 1;
		Save();
	}
  
	//保存
	void Save () {
		string json = JsonUtility.ToJson( user );
		PlayerPrefs.SetString(_SaveKey, json);
	}
}
```

これで再生時に読み込み → スペースキーでレベルアップして保存ができてることが
Inspectorで確認できると思う。
逆に現状はInspectorでしか確認できないからTextやらに表示させたり、
nicknameをInputFieldで編集したりしたらいいと思います。



## Userクラスのカプセル化をやってみる

想定仕様は察してください。
GameManagerは葬ります。

```User.cs
using UnityEngine;
using System.Collections.Generic;
using System.Linq;

[System.Serializable]
public class User {
    
    [SerializeField] private string nickname;
    [SerializeField] private int id, level, exp;

    public string Nickname { get{ return nickname; } }
    public int Level { get{ return level; } }
    public int Exp { get{ return exp; } }
    public List<QuestResult> QuestResults {
       get { return QuestResult.GetByUserID(id); }
    }

    private static User self;
    public static User Self {
        get { return (self != null) ? self : Load(); }
    }

    const string SAVE_KEY = "UserData";
    private static User Load () {
        if (PlayerPrefs.HasKey(SaveKey)) {
            string json = PlayerPrefs.GetString(SAVE_KEY);
            self = JsonUtility.FromJson<User>(json);
        } else {
            self = new User();
            self.level = 1; //これはコンストラクタをサボってる
        }
        return self;
    }
    
    private void Save () {
        string json = JsonUtility.ToJson(self);
        PlayerPrefs.SetString(SAVE_KEY, json);
    }

    public void UpdateNuckname (string nickname) {
        this.nickname = nickname;
        Save();
    }

    public void LevelUp () {
        level += 1;
        Save();
    }

    public void SaveQuestResult (int id, ResultState state, int score) {
        var result = questResults.Find(q => q.ID == id);
        if (result == null) 
            questResults.Add( new QuestResult(id, state, score) );
        else result.UpdateData(state, score);
        Save();
    }
}
```

```QuestResult.cs
using UnityEngine;
using System.Collections.Generic;
using System.Linq;

[System.Serializable]
public class QuestResult {
    [SerializeField] private int
        id,
　　　　　userId,
        challengeCount,
        clearCount,
        highScore;
    
    public int ID { get{ return id; } } 
    public int ChallengeCount { get{ return challengeCount; } } 
    public int ClearCount { get{ return clearCount; } }
    public int HighScore { get{ return highScore; } }

    //ロードは後で書く
    public static List<QuestResult> All { get; set; }

    public static List<QuestResult> GetByUserID (int userId) {
        return All.Where(q => q.userId == userId).ToList();
    }

    public QuestResult (int id, int userId, ResultState state, int score) {
        this.id = id;
        this.userId = userId;
        this.clearCount = clearCount;
        this.highScore = score;
        this.challengeCount = 1;
    }

    public void UpdateData (ResultState state, int score) {
        challengeCount += 1;
        clearCount = (state == ResultState.Clear) ? clearCount++ : clearCount;
        highScore = (highScore < score) ? score : highScore;
    }
}

public enum ResultState {
    Clear,
    Failed
}
```


外部からの使用例はこんなかんじ。

```C#
var user = User.Self;

user.UpdateNickname("たかし");

user.LevelUp();

user.SaveQuestResult(questID, resultState, score);
```


書いてて思ったけどexpを受け取ってlevelは直接あげないほうが良かったね
まぁ仕様書いてないしね

レベルアップしたときにUnityEventでOnLevelUpとか仕込んでいきたいね。
気が向いたら更新します。