【Unity】ミクゆにのセーブ方法について
書き手:肥田野
セーブとロード
15.03.27現在のセーブ方式です。
必要なスクリプト
□Save.cs
□DeadLine.cs
□Goal.cs
■Save.cs
using UnityEngine; using System.Collections; using System.IO; public class Save : MonoBehaviour { public int savePoint; void OnTriggerEnter(Collider col){ if(col.tag == "Player"){ if (File.Exists("save.bin")){ File.Delete("save.bin"); } FileStream BinaryFile = new FileStream("save.bin", FileMode.Create, FileAccess.Write); BinaryWriter Writer = new BinaryWriter(BinaryFile); if(Application.loadedLevelName.Equals("Stage1")){ Writer.Write(1); }else if(Application.loadedLevelName.Equals("Stage2")){ Writer.Write(2); }else if(Application.loadedLevelName.Equals("Stage3")){ Writer.Write(3); }else if(Application.loadedLevelName.Equals("Stage4")){ Writer.Write(4); } Writer.Write(savePoint); BinaryFile.Close(); } } }
各チェックポイントへ動的に取り込むため、このスクリプトはプロジェクトに入れておくだけにします。
取り込む方法はDeadLine.cs参照。
チェックポイントにはBoxColliderが必要ですが、DeadLine.csがこのスクリプトを動的に取り込む際、一緒にBoxColliderも設置されるので、エディターから設定する必要はありません。
また、バイナリファイルを扱うためにSystem.IOを使用しています。
Playerタグの付いたオブジェクトが侵入すると、save.binファイルを探し、もし存在すれば削除します。
そして新しいsabe.binファイルを書き込み形式で新規作成し、現在ロードされているSceneファイル名からステージ番号を、チェックポイント自身が保持するsavePoint変数からチェックポイントの番号を書き込みます。
■DeadLine.cs
※残機を表示する処理が含まれます
using UnityEngine; using System.Collections; using System.IO; public class DeadLine : MonoBehaviour { public static Vector3 StartPosition; public GameObject negi; [HideInInspector] public static int startPoint; public static Vector3 playerRotate; public static GameObject Player; public static GameObject[] CheckPoint; public static GameObject lifenegi; void OnTriggerEnter(Collider col){ if(col.gameObject.tag == "Player"){ for(int i=0;i<MikuMove.life;i++){ GameObject.Destroy(lifenegi); } MikuMove.life--; if(MikuMove.life>=0){ Load (); Player.transform.Rotate (playerRotate-Player.transform.rotation.eulerAngles); } } } void Start () { CheckPoint = GameObject.FindGameObjectsWithTag ("CheckPoint"); for(int i=0;i<CheckPoint.Length;i++){ Save component = CheckPoint[i].AddComponent<Save>(); component.savePoint = i; BoxCollider pos = CheckPoint[i].AddComponent<BoxCollider>(); pos.isTrigger = true; pos.center = new Vector3(0.0f,0.6f,0.0f); } Load (); for(int i=0;i<MikuMove.life;i++){ lifenegi = Instantiate(negi,Player.transform.position,Quaternion.identity)as GameObject; lifenegi.transform.parent = Player.gameObject.transform; lifenegi.transform.Rotate(-90.0f,0.0f,0.0f); lifenegi.transform.localPosition = new Vector3(-1.7f+0.3f*i,1.3f,0.0f); } playerRotate = Player.transform.rotation.eulerAngles; } void Update () { if(MikuMove.life<=0){ if(File.Exists("save.bin")){ File.Delete("save.bin"); } MikuMove.life = 3; Load (); } } void Load(){ Player = GameObject.FindGameObjectWithTag("Player"); GameObject stpos = GameObject.Find ("StartPosition"); StartPosition = stpos.transform.position; if (File.Exists("save.bin")){ FileStream BinaryFile = new FileStream("save.bin", FileMode.Open, FileAccess.Read); BinaryReader Reader = new BinaryReader(BinaryFile); Reader.ReadInt32(); startPoint = Reader.ReadInt32(); if(startPoint == -1){ Player.transform.position = StartPosition; }else{ Player.transform.position = CheckPoint[startPoint].transform.position; } BinaryFile.Close(); }else{ Player.transform.position = StartPosition; } } }
まずStart()を解説します。
GameObject型のメンバ変数配列「CheckPoint」に、FindGameObjectsWithTag関数で「CheckPointタグ」の付いたオブジェクトを一つずつ代入していきます。
C#の配列はC++と異なり、「型[] 識別名」と宣言します。要素の数は動的に変動するので、宣言時に添字は不要になります。
また、その配列の要素数は「配列名.Length」で取得できます。
次のfor文内が変則的ですが、変数「component」の型「Save」は、スクリプトのファイル名です。
Save型の変数componentに先ほど用意したCheckPointオブジェクトの一つを代入し、AddComponent関数でSave.csを設置します。
新たに変数componentを用意しないと、CheckPointオブジェクトのメンバ変数にアクセスができませんでした。理由は不明です。ご存知の方はコメント等で指摘していただけると嬉しいです。
同様にBoxCollider型の変数posを宣言し、CheckPointオブジェクトにBoxColliderを追加します。
ついでにBoxColliderの設定を変更し、衝突判定をなくしてセンターの位置を少し上にずらしています。通常はエディタ画面で人力で行う作業ですが、このようにスクリプトで自動的に行うことで、思わぬ設定忘れを防ぎ、作業量を減らすことができます。
Load()の後に続くfor文は、残機を表示するための処理なので解説は省略します。
次にLoad()を解説します。
この関数が実行されると、ゲームオブジェクト「Player」に、該当タグの付いたオブジェクトを探して保持させます。
また、GameObject型変数「stpos」(スタートポジションの略)を宣言し、StartPositionタグのついたオブジェクトを探して代入します。
次に「save.bin(バイナリファイル)」ファイルがフォルダ内に存在するかを調べて、存在すれば読み取り形式で開き、専用の変数に格納します(if文2,3行目)。
バイナリファイルの最初の文字はステージ番号なので、読み飛ばします(Save.cs参照)。
変数「startPoint」に、2文字目を格納します。2文字目は、Start()で各チェックポイントに与えられた番号が記録されています。
もしどこのチェックポイントも通過していなければ、バイナリファイルの2文字目には「-1」が記録されるので、その場合stposの座標をプレイヤーに代入します。
バイナリファイルの2文字目が-1以外のときは、その番号を持つCheckPointの座標を代入しています。
OnTrrigetEnter()は、プレイヤーが接触した時にライフを1減らし、Load()を実行するだけです。
Update()は、ライフが0以下になった時にセーブファイルを消去(=通過したセーブポイントの情報を消去)するので、ライフが全快してスタート地点に戻されます。
■Goal.cs
using UnityEngine; using System.Collections; using System.IO; public class Goal : MonoBehaviour { void OnCollisionEnter(Collision col){ if(col.gameObject.Tag == "Player"){ if(Application.loadedLevelName.Equals("Stage1")){ Application.LoadLevel("Stage2"); }else if(Application.loadedLevelName.Equals("Stage2")){ Application.LoadLevel("Stage3"); }else if(Application.loadedLevelName.Equals("Stage3")){ Application.LoadLevel("Stage4"); } } } }
こちらはシンプルです。
プレイヤーが浸入した時、現在ロードされているSceneファイル名を参照して、次のステージのデータをロードしています。
ざっくりと説明しましたが、自分でも読み返すと(分かりにくいスクリプトだなぁ……(-_-;))と感じたので、その内改良版を作るかもしれません。
現状はこのスクリプトで動いていますので、挙動が気になった場合は参考にしてください。