課題10: ゲームデータの保存機能の追加
ゲーム開発において、プレイヤーのスコアや進行状況を保存し、後で再開できるようにすることは非常に重要です。Unityでは、PlayerPrefs
を使用して簡単にデータを保存・読み込みすることができます。また、データをJSON形式で保存することで、データの構造を柔軟に管理することが可能になります。本課題では、GameDirector
スクリプトを拡張し、スコアをJSONデータとして作成し、PlayerPrefs
を利用して保存・読み込みする方法を学びます。
全体の流れ
1. JSON 形式のデータ保存とは
JSON (JavaScript Object Notation) は、軽量なデータ交換フォーマットです。JSON は、プログラミング言語に依存せず、データをテキスト形式で保存・送信できるため、Unity でもよく使われます。
Unity では、ゲームの状態(スコアやハイスコアなど)を JSON 形式に変換し、それをローカルファイルや PlayerPrefs に保存することで、次回のプレイ時に前回のゲーム状態を復元できます。
2. GameDirector
でデータを保存・復元する流れ
GameDirector
スクリプトでは、以下のようなデータを JSON 形式で保存します。
- ハイスコア (
highScore
) - その他の保存したいデータがあれば追記します(List型も可能)
次回のゲーム起動時には、保存された JSON データを読み込み、ゲームの状態を復元します。
3. PlayerPrefs でのデータ保存と読み込み
PlayerPrefs は、Unity で簡単にデータを保存するためのクラスです。スコアなどの簡単なデータをローカルに保存でき、ゲームを再起動してもデータを保持できます。今回は、JSON 形式でデータを文字列として保存します。
目的
JSONデータの作成:
- ゲームデータ(スコア)をJSON形式で保存するためのデータ構造を定義し、シリアライズ(変換)する方法を学ぶ。
PlayerPrefsによる保存と読み込み:
- JSONデータを
PlayerPrefs
に保存し、後で読み込む方法を理解する。
ステップバイステップガイド
1. ゲームデータ用のクラスを定義する
まず、保存したいデータを格納するためのクラスを定義します。ここでは、スコアのみを保存対象としますが、将来的に他のデータも追加できます。
手順:
新しいC#スクリプトを作成:
- プロジェクトビューで右クリックし、
Create
>C# Script
を選択。 - スクリプト名を
GameData.cs
とします。
GameData
クラスの定義:
- 作成した
GameData.cs
をダブルクリックして開き、以下のコードを記述します。
[System.Serializable]
public class GameData
{
public int highScore;
}
説明:
[Serializable]
属性を付与することで、このクラスがシリアライズ可能になります。
2. GameDirector
スクリプトの修正
次に、GameDirector
スクリプトを修正して、スコアの保存と読み込み機能を追加します。
修正後の GameDirector.cs
:
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class GameDirector : MonoBehaviour
{
public List<GenerationParameters> generationParametersList; // ScriptableObjectのリスト
private int currentStage = 0;
private float elapsedTime = 0.0f;
GameObject timerText;
GameObject pointText;
float time = 30.0f;
public int point = 0; // 現在のスコア
private int highScore = 0; // ハイスコア
GameObject generator;
bool isGameOver = false; // ゲーム終了フラグ
public GameObject gameOverUI;
private string saveKey = "GameData"; // PlayerPrefsのキー
void Start()
{
this.timerText = GameObject.Find("Time");
this.pointText = GameObject.Find("Point");
this.generator = GameObject.Find("ItemGenerator");
// 保存データがあれば読み込み
LoadGameData();
// 初期パラメータセットを適用
ApplyCurrentGenerationParameters();
}
void Update()
{
if (isGameOver)
return; // ゲーム終了後は処理を停止
this.time -= Time.deltaTime;
elapsedTime += Time.deltaTime;
// パラメータセットの切り替え
if (currentStage < generationParametersList.Count && elapsedTime > generationParametersList[currentStage].duration)
{
elapsedTime = 0.0f; // elapsedTime をリセット
ApplyCurrentGenerationParameters(); // currentStage が有効な範囲内でパラメータを適用
currentStage++; // パラメータ適用後にインクリメント
}
// タイマー終了時の処理
if (this.time < 0)
{
this.time = 0;
this.generator.GetComponent<ItemGenerator>().SetParameter(10000.0f, 0, 0);
EndGame(); // ゲーム終了処理を呼び出す
}
this.timerText.GetComponent<TextMeshProUGUI>().text = this.time.ToString("F1");
UpdateScoreUI();
}
// ゲーム終了処理
private void EndGame()
{
isGameOver = true; // フラグを立ててUpdateメソッドを停止
Debug.Log("Game Over!");
// ゲームオーバー画面のUIを表示(オプション)
// if(gameOverUI != null)
// {
// gameOverUI.SetActive(true);
// }
// ゲームデータを保存
SaveGameData();
}
// パラメータセットを適用するメソッド
private void ApplyCurrentGenerationParameters()
{
GenerationParameters currentParams = generationParametersList[currentStage];
this.generator.GetComponent<ItemGenerator>().SetParameter(currentParams.span, currentParams.speed, currentParams.ratio);
}
// スコア変更イベントハンドラ
public void HandleScoreChange(object sender, ScoreEventArgs e)
{
this.point += e.ScoreChange;
if (this.point > this.highScore)
{
this.highScore = this.point; // ハイスコアを更新
}
UpdateScoreUI();
}
// スコアUIの更新
private void UpdateScoreUI()
{
this.pointText.GetComponent<TextMeshProUGUI>().text = $"{this.point} point (High Score: {this.highScore})";
}
// JSON形式でゲームデータを保存する
private void SaveGameData()
{
GameData data = new GameData
{
highScore = this.highScore;
};
// データをJSON文字列に変換
string jsonData = JsonUtility.ToJson(data);
// PlayerPrefsに保存
PlayerPrefs.SetString(saveKey, jsonData);
PlayerPrefs.Save();
Debug.Log("ゲームデータを保存しました: " + jsonData);
}
// 保存されたゲームデータを読み込む
private void LoadGameData()
{
if (PlayerPrefs.HasKey(saveKey))
{
// PlayerPrefsからデータを取得
string jsonData = PlayerPrefs.GetString(saveKey);
// JSON文字列をオブジェクトに変換
GameData data = JsonUtility.FromJson<GameData>(jsonData);
// ゲームデータを復元
this.highScore = data.highScore;
// generationParametersList は読み込まない
Debug.Log("ゲームデータを読み込みました: " + jsonData);
}
else
{
Debug.Log("保存データがありません。");
}
UpdateScoreUI();
}
}
変更点の詳細:
GameData
クラスの利用:
- GameDataの情報を保存するために、
GameData
クラスをインスタンス化し、そのインスタンスをJSON形式にシリアライズします。
SaveGameData()
メソッドの追加:
- 現在のスコアを
GameData
に格納し、JsonUtility.ToJson()
を使用してJSON文字列に変換します。 - そのJSON文字列を
PlayerPrefs
のSetString
メソッドで保存します。
LoadGameData()
メソッドの追加:
- ゲーム開始時に
PlayerPrefs
から保存されたJSON文字列を取得し、JsonUtility.FromJson<GameData>()
を使用してGameData
オブジェクトにデシリアライズします。 - データが存在しない場合は、新たにデータを保存します。
EndGame()
メソッドの修正:
- ゲーム終了時に
SaveGameData()
を呼び出して、現在のスコアを保存します。
Update()
メソッドの修正:
- スコアが更新された際に自動的に保存するため、
SaveGameData()
を呼び出すロジックを追加することも可能ですが、ここではゲーム終了時に保存する形を採用しています。
3. テストと確認
手順:
- スクリプトの保存:
GameData.cs
と修正したGameDirector.cs
を保存します。
- Unityエディタに戻る:
- Unityエディタが自動的にスクリプトをコンパイルします。コンパイルエラーがないか確認します。
- プレイモードでテスト:
- プレイモード を開始します。
- スコアが正しく表示され、アイテムを収集するたびにスコアが更新されることを確認します。
- ゲームを終了(タイマーが0になる、または手動で終了処理を呼び出す)し、スコアが保存されていることを確認します。
保存データの確認:
- Unityの PlayerPrefs に保存されたデータを確認するには、スクリプトを使用してデバッグログに出力するか、特定のツールを使用します。
再起動後のデータ読み込み確認:
- プレイモード を停止し、再度開始します。
- 以前のセッションで保存されたスコアが正しく読み込まれ、表示されていることを確認します。
5. デバッグと問題解決
もしスコアが正しく保存・読み込みされない場合、以下の点を確認してください。
GameData
クラスがシリアライズ可能か:[Serializable]
属性が付与されているか確認します。
- タグの設定が正しいか:
GoldApple
やその他のアイテムのタグが正しく設定されているか確認します。
PlayerPrefs
キーが一致しているか:gameDataKey
が一貫して使用されているか確認します。
ItemController
が正しくスコアを変更しているか:- アイテム収集時に
HandleScoreChange()
が正しく呼び出されているか確認します。
- アイテム収集時に
- コンソールのログを確認:
Debug.Log
メッセージを確認し、データの保存・読み込みが正しく行われているか確認します。
学習ポイント
- JSONシリアライズの理解:
JsonUtility
を使用してオブジェクトをJSON形式にシリアライズ(変換)およびデシリアライズ(復元)する方法を学びます。
- PlayerPrefsの活用:
- Unityの
PlayerPrefs
を使用して、簡単にデータを保存・読み込みする方法を理解します。
- Unityの
- データの保存と読み込みのタイミング:
- ゲーム終了時やスコア更新時など、適切なタイミングでデータを保存する方法を学びます。
- データ管理の基礎:
- ゲームデータを管理するための構造化された方法を学び、将来的な拡張性を考慮した設計を理解します。
- デバッグ技法:
Debug.Log
を使用して、データの保存・読み込みプロセスを追跡し、問題を特定・解決する方法を学びます。
ディスカッション
コメント一覧
まだ、コメントがありません