【Unity】ゲームの進行状況をセーブ/ロードする

投稿者: | 2020-09-28

スタート時にはコライダーの赤いCubeだけがあり、それに入るとCubeが消えて秒読みが始まります。3秒立つと左上に球が出現して、ゲームの進行状況がセーブされ、シーンを再読み込みすると、球だけがある状態からはじまります。

球の方を向くと球が消えて緑のCubeが出現するので、そのCubeに入ると2秒後にシーンが再読み込みされます。このロード前に最初の状態でセーブされるので、再スタートするとまた赤いCubeだけがある状態に戻っています。

ゲームの進行管理やセーブ/ロードするスクリプトはゲームの進行を管理するを元にしています。今回はUnityEngine.PlayerPrefsを使わずにXMLファイルにデータを保存しました。

  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using UnityEngine.SceneManagement;
  4. using System.IO;
  5.  
  6. public class GameScript : MonoBehaviour
  7. {
  8. [SerializeField] GameObject[] colliders;
  9. [SerializeField] Text text;
  10. [SerializeField] Transform target;
  11.  
  12. public static bool onTriggerEnter;
  13.  
  14. int chapter;
  15. int state;
  16. float sec;
  17. Transform playerHead;
  18. AudioSource audioSource;
  19.  
  20.  
  21. string filename;
  22.  
  23. // Start is called before the first frame update
  24. void Start()
  25. {
  26. filename = Application.dataPath + "/testsavedata.xml";
  27.  
  28. if (File.Exists(filename))
  29. {
  30. var data = XmlUtil.Deserialize<SaveData>(filename);
  31. chapter = data.chapter;
  32. }
  33. else {
  34. chapter = 1;
  35. Save(); // 保存
  36. }
  37.  
  38. state = 1;
  39. text.text = "";
  40. playerHead = Camera.main.transform;
  41. target.gameObject.SetActive(false);
  42. audioSource = GetComponent<AudioSource>();
  43.  
  44. // コライダーを非アクティブ
  45. foreach (GameObject col in colliders)
  46. {
  47. col.SetActive(false);
  48. }
  49. switch (chapter)
  50. {
  51. case 1:
  52.  
  53. colliders[0].SetActive(true);
  54.  
  55. break;
  56.  
  57. case 2:
  58. target.gameObject.SetActive(true);
  59. break;
  60.  
  61. }
  62. }
  63.  
  64. // Update is called once per frame
  65. void Update()
  66. {
  67.  
  68. if (Input.GetKeyDown(KeyCode.Space))
  69. {
  70. // シーンをリロード
  71. SceneManager.LoadScene("New Scene");
  72. }
  73.  
  74. switch (chapter)
  75. {
  76. case 1:
  77.  
  78. switch (state)
  79. {
  80. case 1:
  81.  
  82. if(onTriggerEnter)
  83. {
  84. onTriggerEnter = false;
  85. audioSource.Play();
  86.  
  87. colliders[0].SetActive(false); // 赤いのコライダーを非アクティブ
  88. state = 2; // 次のステートへ
  89. }
  90. break;
  91. case 2:
  92.  
  93. sec += Time.deltaTime;
  94. text.text = sec + "";
  95.  
  96. if (sec >= 3f)
  97. {
  98. sec = 0f;
  99. text.text = "";
  100. target.gameObject.SetActive(true);
  101. chapter = 2; // 次のチャプター
  102. state = 1;
  103. Save(); // セーブ
  104. }
  105. break;
  106.  
  107. }
  108.  
  109. break;
  110.  
  111. case 2:
  112.  
  113. switch (state)
  114. {
  115. case 1:
  116.  
  117. RaycastHit hit;
  118. Vector3 dir = target.position - playerHead.position;
  119. if (Physics.Raycast(playerHead.position, dir, out hit, 20f))
  120. {
  121. if (hit.collider.tag == "Target")
  122. {
  123. float dot = Vector3.Dot(playerHead.forward, Vector3.Normalize(dir));
  124.  
  125. // プレイヤーがターゲットの方を見たら
  126. if (dot > 0.9f)
  127. {
  128. target.gameObject.SetActive(false);
  129. colliders[1].SetActive(true); // 緑のコライダーをアクティブ
  130. state = 2; // 次のステートへ
  131. }
  132. }
  133. }
  134. break;
  135.  
  136. case 2:
  137.  
  138. if (onTriggerEnter)
  139. {
  140. onTriggerEnter = false;
  141. audioSource.Play();
  142. colliders[1].SetActive(false); // 緑のコライダーを非アクティブ
  143. state = 3;
  144. text.text = "ゲーム終了";
  145. }
  146. break;
  147.  
  148. case 3:
  149.  
  150. sec += Time.deltaTime;
  151.  
  152. if (sec >= 2f)
  153. {
  154. chapter = 1;
  155. state = 1;
  156. Save();
  157. SceneManager.LoadScene("New Scene");
  158. }
  159. break;
  160. }
  161. break;
  162. }
  163.  
  164.  
  165.  
  166. }
  167.  
  168. void Save()
  169. {
  170. SaveData data = new SaveData();
  171. data.chapter = chapter;
  172. XmlUtil.Seialize(filename, data);
  173. }
  174. }

Start()でまずデータの保存場所を指定します。Application.dataPathはUnityエディターではAssetsフォルダのパスです。

  1. filename = Application.dataPath + "/testsavedata.xml";

AssetsフォルダはProjectウィンドウで右クリックからShow in Explorerをクリックするとすぐに見つかります。

指定したファイルがあればデータをロードして、無ければセーブします。先に同じ名前の空のファイルを自分で作っておくと、Root element is missing…とエラーがでます。

  1. using System.IO;
  2.  
  3. // Start()
  4. if (File.Exists(filename))
  5. {
  6. var data = XmlUtil.Deserialize<SaveData>(filename);
  7. chapter = data.chapter;
  8. }
  9. else {
  10. chapter = 1;
  11. Save(); // 保存
  12. }
  13.  
  14. // ---
  15. void Save()
  16. {
  17. SaveData data = new SaveData();
  18. data.chapter = chapter;
  19. XmlUtil.Seialize(filename, data);
  20. }
  21.  

データをセーブ/ロードするために、データを入れるクラスとXMLファイルに読み書きするクラスを作っておきます。参考:http://ftvoid.com/blog/post/1061

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4.  
  5. [System.Serializable]
  6. public class SaveData
  7. {
  8. public int chapter;
  9. }
  1. using System.IO;
  2. using System.Xml.Serialization;
  3.  
  4. public class XmlUtil
  5. {
  6. public static T Seialize<T>(string filename, T data)
  7. {
  8. using (var stream = new FileStream(filename, FileMode.Create))
  9. {
  10. var serializer = new XmlSerializer(typeof(T));
  11. serializer.Serialize(stream, data);
  12. }
  13.  
  14. return data;
  15. }
  16.  
  17. public static T Deserialize<T>(string filename)
  18. {
  19. using (var stream = new FileStream(filename, FileMode.Open))
  20. {
  21. var serializer = new XmlSerializer(typeof(T));
  22. return (T)serializer.Deserialize(stream);
  23. }
  24. }
  25. }

データを保存するときのFileStreamのファイルモードがCreateになっているので、ファイルがあれば上書きし無ければ新規作成します。セーブするときにFile.Createでファイルを作ると、IOException: Sharing violation on path C:\Users…とエラーがでました。

Start()ではその後、このチャプターの値によって、シーンの初期状態を設定しています。

  1. switch (chapter)
  2. {
  3. case 1:
  4.  
  5. colliders[0].SetActive(true);
  6.  
  7. break;
  8.  
  9. case 2:
  10. target.gameObject.SetActive(true);
  11. break;
  12.  
  13. }

チャプター1のとき赤いCubeだけをアクティブにして、2では球だけをアクティブにしています。

その後、Update()でチャプター1のステート2まで来たときに3秒立つと、チャプターを2ステートを1にしてセーブします。

  1. case 2:
  2.  
  3. sec += Time.deltaTime;
  4. text.text = sec + "";
  5.  
  6. if (sec >= 3f)
  7. {
  8. sec = 0f;
  9. text.text = "";
  10. target.gameObject.SetActive(true);
  11. chapter = 2; // 次のチャプター
  12. state = 1;
  13. Save(); // セーブ
  14. }
  15. break;

すると、シーンを再読み込みしたときに、チャプター2から始まるので、Start()で球だけがアクティブになっている状態になり、Update()でもチャプター1の処理は飛ばされます。

緑のCubeに入ってゲーム終了のテキストが表示されると、2秒後にチャプター1ステート1にしてから保存してシーンをロードします。

  1. sec += Time.deltaTime;
  2.  
  3. if (sec >= 2f)
  4. {
  5. chapter = 1;
  6. state = 1;
  7. Save();
  8. SceneManager.LoadScene("New Scene");
  9. }
  10. break;

すると、次は一番最初の状態からスタートします。これで直前のセーブポイントから始まるようになりました。

チャプター/セクション/ステート 等と区切りを増やしても良いかもしれません。

コメントを残す

メールアドレスが公開されることはありません。