課題10: ゲームデータの保存機能の追加

2024年9月30日

ゲーム開発において、プレイヤーのスコアや進行状況を保存し、後で再開できるようにすることは非常に重要です。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文字列を PlayerPrefsSetString メソッドで保存します。

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 を使用して、簡単にデータを保存・読み込みする方法を理解します。
  • データの保存と読み込みのタイミング:
    • ゲーム終了時やスコア更新時など、適切なタイミングでデータを保存する方法を学びます。
  • データ管理の基礎:
    • ゲームデータを管理するための構造化された方法を学び、将来的な拡張性を考慮した設計を理解します。
  • デバッグ技法:
    • Debug.Log を使用して、データの保存・読み込みプロセスを追跡し、問題を特定・解決する方法を学びます。

C#,Unity

Posted by hidepon