スタート時にはコライダーの赤いCubeだけがあり、それに入るとCubeが消えて秒読みが始まります。3秒立つと左上に球が出現して、ゲームの進行状況がセーブされ、シーンを再読み込みすると、球だけがある状態からはじまります。
球の方を向くと球が消えて緑のCubeが出現するので、そのCubeに入ると2秒後にシーンが再読み込みされます。このロード前に最初の状態でセーブされるので、再スタートするとまた赤いCubeだけがある状態に戻っています。
ゲームの進行管理やセーブ/ロードするスクリプトはゲームの進行を管理するを元にしています。今回はUnityEngine.PlayerPrefsを使わずにXMLファイルにデータを保存しました。
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.IO;
public class GameScript : MonoBehaviour
{
[SerializeField] GameObject[] colliders;
[SerializeField] Text text;
[SerializeField] Transform target;
public static bool onTriggerEnter;
int chapter;
int state;
float sec;
Transform playerHead;
AudioSource audioSource;
string filename;
// Start is called before the first frame update
void Start()
{
filename = Application.dataPath + "/testsavedata.xml";
if (File.Exists(filename))
{
var data = XmlUtil.Deserialize<SaveData>(filename);
chapter = data.chapter;
}
else {
chapter = 1;
Save(); // 保存
}
state = 1;
text.text = "";
playerHead = Camera.main.transform;
target.gameObject.SetActive(false);
audioSource = GetComponent<AudioSource>();
// コライダーを非アクティブ
foreach (GameObject col in colliders)
{
col.SetActive(false);
}
switch (chapter)
{
case 1:
colliders[0].SetActive(true);
break;
case 2:
target.gameObject.SetActive(true);
break;
}
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// シーンをリロード
SceneManager.LoadScene("New Scene");
}
switch (chapter)
{
case 1:
switch (state)
{
case 1:
if(onTriggerEnter)
{
onTriggerEnter = false;
audioSource.Play();
colliders[0].SetActive(false); // 赤いのコライダーを非アクティブ
state = 2; // 次のステートへ
}
break;
case 2:
sec += Time.deltaTime;
text.text = sec + "";
if (sec >= 3f)
{
sec = 0f;
text.text = "";
target.gameObject.SetActive(true);
chapter = 2; // 次のチャプター
state = 1;
Save(); // セーブ
}
break;
}
break;
case 2:
switch (state)
{
case 1:
RaycastHit hit;
Vector3 dir = target.position - playerHead.position;
if (Physics.Raycast(playerHead.position, dir, out hit, 20f))
{
if (hit.collider.tag == "Target")
{
float dot = Vector3.Dot(playerHead.forward, Vector3.Normalize(dir));
// プレイヤーがターゲットの方を見たら
if (dot > 0.9f)
{
target.gameObject.SetActive(false);
colliders[1].SetActive(true); // 緑のコライダーをアクティブ
state = 2; // 次のステートへ
}
}
}
break;
case 2:
if (onTriggerEnter)
{
onTriggerEnter = false;
audioSource.Play();
colliders[1].SetActive(false); // 緑のコライダーを非アクティブ
state = 3;
text.text = "ゲーム終了";
}
break;
case 3:
sec += Time.deltaTime;
if (sec >= 2f)
{
chapter = 1;
state = 1;
Save();
SceneManager.LoadScene("New Scene");
}
break;
}
break;
}
}
void Save()
{
SaveData data = new SaveData();
data.chapter = chapter;
XmlUtil.Seialize(filename, data);
}
}
Start()でまずデータの保存場所を指定します。Application.dataPathはUnityエディターではAssetsフォルダのパスです。
filename = Application.dataPath + "/testsavedata.xml";
AssetsフォルダはProjectウィンドウで右クリックからShow in Explorerをクリックするとすぐに見つかります。
指定したファイルがあればデータをロードして、無ければセーブします。先に同じ名前の空のファイルを自分で作っておくと、Root element is missing…とエラーがでます。
using System.IO;
// Start()
if (File.Exists(filename))
{
var data = XmlUtil.Deserialize<SaveData>(filename);
chapter = data.chapter;
}
else {
chapter = 1;
Save(); // 保存
}
// ---
void Save()
{
SaveData data = new SaveData();
data.chapter = chapter;
XmlUtil.Seialize(filename, data);
}
データをセーブ/ロードするために、データを入れるクラスとXMLファイルに読み書きするクラスを作っておきます。参考:http://ftvoid.com/blog/post/1061
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class SaveData
{
public int chapter;
}
using System.IO;
using System.Xml.Serialization;
public class XmlUtil
{
public static T Seialize<T>(string filename, T data)
{
using (var stream = new FileStream(filename, FileMode.Create))
{
var serializer = new XmlSerializer(typeof(T));
serializer.Serialize(stream, data);
}
return data;
}
public static T Deserialize<T>(string filename)
{
using (var stream = new FileStream(filename, FileMode.Open))
{
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(stream);
}
}
}
データを保存するときのFileStreamのファイルモードがCreateになっているので、ファイルがあれば上書きし無ければ新規作成します。セーブするときにFile.Createでファイルを作ると、IOException: Sharing violation on path C:\Users…とエラーがでました。
Start()ではその後、このチャプターの値によって、シーンの初期状態を設定しています。
switch (chapter)
{
case 1:
colliders[0].SetActive(true);
break;
case 2:
target.gameObject.SetActive(true);
break;
}
チャプター1のとき赤いCubeだけをアクティブにして、2では球だけをアクティブにしています。
その後、Update()でチャプター1のステート2まで来たときに3秒立つと、チャプターを2ステートを1にしてセーブします。
case 2:
sec += Time.deltaTime;
text.text = sec + "";
if (sec >= 3f)
{
sec = 0f;
text.text = "";
target.gameObject.SetActive(true);
chapter = 2; // 次のチャプター
state = 1;
Save(); // セーブ
}
break;
すると、シーンを再読み込みしたときに、チャプター2から始まるので、Start()で球だけがアクティブになっている状態になり、Update()でもチャプター1の処理は飛ばされます。
緑のCubeに入ってゲーム終了のテキストが表示されると、2秒後にチャプター1ステート1にしてから保存してシーンをロードします。
sec += Time.deltaTime;
if (sec >= 2f)
{
chapter = 1;
state = 1;
Save();
SceneManager.LoadScene("New Scene");
}
break;
すると、次は一番最初の状態からスタートします。これで直前のセーブポイントから始まるようになりました。
チャプター/セクション/ステート 等と区切りを増やしても良いかもしれません。