■ はじめに
ゲームに不可欠なセーブデータを作りましょう!!
今回は、スコアのランキングを例にデータのセーブとロードの処理を実装していきます。
(個人的な忘備録のついでとして記入してます。もっと効率的な方法があるかもですが、ご了承ください)
■ 実装結果
こんな感じのができます。一度ゲームを終了しても、保存されたデータをもとにランキングが表示されます。
GitHubにもアップロードしたので、どうぞ↓
■ 説明
○ 必要なもの
- 保存するデータのクラス
- セーブやロードの処理をするクラス
- ランキングの表示、ランキングの値の保存などの処理をするクラス
・3つのクラスのみで作成できます。コードの量もそこまで多くないですが、少し複雑かもです。
・今回はランキングを例としていますが、ハイスコアやゲームのセーブデータの保存などに流用できます。
○ すること
● 保存データクラス
- 保存したい変数を宣言
- クラスのシリアライズ
● セーブロードクラス
- ファイルのパスを取得
- 開始時、ファイルがあるかどうか確認
- ファイルがなかったら作成
- ファイルがあれば、読み込んで「保存データクラス」型の変数に格納
- 終了時、データを保存
● ランキングクラス
- 「セーブロードクラス」から「保存データクラス」型の変数を参照
- ランキングに表示するテキストのオブジェクトとコンポーネントの取得
- 常時、ランキングを表示
- 入力欄に数値を入力後、Enterボタンを押したらランキング内の数値入れ替え、数値の保存
- Deleteボタンを押したらランキング内の数値を削除
■コード
① 保存データクラス(SaveData)
[System.Serializable]
public class SaveData {
public const int rankCnt = 3;
public int[] rank = new int[rankCnt];
}
[System.Serializable]
でシリアル化する必要があります。- 宣言するメンバ変数には、
public
を付ける必要があります。
② セーブロードクラス(DataManager)
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.datapath
をApplication.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)
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
○すること
-
InputFieldの"InputField"コンポーネント内の"Content Type"を"Integer Number"に変更して、入力を整数のみに制限する
-
Enter,Deleteボタンの"OnClick()"にManagerの"Ranking"スクリプトのpublic関数を入れる
■さいごに
- 重要なデータは暗号化しないと、書き換えとか簡単にできちゃうので、その辺もそのうち勉強します。
- まだ実際のゲームにこんな感じのシステムを使用できてないので、今後とも頑張ります。