課題11の参考資料)非同期処理を導入した GameDirector クラス

以下に、非同期処理(async/await) を導入し、SaveLoadManager クラスと連携させた GameDirector クラスの最新バージョンを示します。この実装により、大量のデータを扱う際や保存・読み込みに時間がかかる場合でも、メインスレッドの負荷を軽減し、フレームドロップを防ぐことができます。

更新ポイント

非同期メソッドの呼び出し:

  • ゲーム開始時に非同期でデータを読み込み。
  • ゲーム終了時に非同期でデータを保存。

コルーチンの使用:

  • UnityのMonoBehaviourメソッド(StartUpdate)は直接asyncにできないため、コルーチンを用いて非同期処理を管理。

エラーハンドリング:

  • 非同期処理中に発生する可能性のあるエラーを適切にキャッチし、ログに記録。

更新後の GameDirector クラス

using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;

public class GameDirector : MonoBehaviour
{
    public List<GenerationParameters> generationParametersList; // ScriptableObjectのリスト
    private int currentStage = 0;
    private float elapsedTime = 0.0f;

    private GameObject timerText;
    private GameObject pointText;
    private float time = 30.0f;
    public int point = 0; // 現在のスコア
    private int highScore = 0; // ハイスコア
    private GameObject generator;
    private bool isGameOver = false; // ゲーム終了フラグ

    // 例: ゲームオーバー画面のUIを表示のとき必要
    // public GameObject gameOverUI;

    void Start()
    {
        timerText = GameObject.Find("Time");
        pointText = GameObject.Find("Point");
        generator = GameObject.Find("ItemGenerator");

        // 非同期にゲームデータを読み込み
        StartCoroutine(LoadGameDataCoroutine());

        // 初期パラメータセットを適用
        ApplyCurrentGenerationParameters();
    }

    void Update()
    {
        if (isGameOver)
            return; // ゲーム終了後は処理を停止

        time -= Time.deltaTime;
        elapsedTime += Time.deltaTime;

        // パラメータセットの切り替え
        if (currentStage < generationParametersList.Count && elapsedTime > generationParametersList[currentStage].duration)
        {
            elapsedTime = 0.0f;  // elapsedTime をリセット

            ApplyCurrentGenerationParameters(); // currentStage が有効な範囲内でパラメータを適用

            currentStage++; // パラメータ適用後にインクリメント
        }

        // タイマー終了時の処理
        if (time < 0)
        {
            time = 0;
            generator.GetComponent<ItemGenerator>().SetParameter(10000.0f, 0, 0);
            EndGame(); // ゲーム終了処理を呼び出す
        }

        timerText.GetComponent<TextMeshProUGUI>().text = time.ToString("F1");
        UpdateScoreUI();
    }

    // ゲーム終了処理
    private void EndGame()
    {
        isGameOver = true; // フラグを立ててUpdateメソッドを停止
        Debug.Log("Game Over!");

        // ゲームオーバー画面を表示するなどの処理を追加
        // 例: ゲームオーバー画面のUIを表示
        // if(gameOverUI != null)
        // {
        //     gameOverUI.SetActive(true);
        // }

        // 非同期にゲームデータを保存
        StartCoroutine(SaveGameDataCoroutine());
    }

    // パラメータセットを適用するメソッド
    private void ApplyCurrentGenerationParameters()
    {
        if (currentStage >= generationParametersList.Count)
            return;

        GenerationParameters currentParams = generationParametersList[currentStage];
        generator.GetComponent<ItemGenerator>().SetParameter(currentParams.span, currentParams.speed, currentParams.ratio);
    }

    // スコア変更イベントハンドラ
    public void HandleScoreChange(object sender, ScoreEventArgs e)
    {
        point += e.ScoreChange;
        if (point > highScore)
        {
            highScore = point; // ハイスコアを更新
        }
        UpdateScoreUI();
    }

    // スコアUIの更新
    private void UpdateScoreUI()
    {
        pointText.GetComponent<TextMeshProUGUI>().text = $"{point} point (High Score: {highScore})";
    }

    // ゲームデータを非同期に保存するコルーチン
    private IEnumerator SaveGameDataCoroutine()
    {
        Task saveTask = SaveGameDataAsync();
        yield return new WaitUntil(() => saveTask.IsCompleted);

        if (saveTask.IsFaulted)
        {
            Debug.LogError("ゲームデータの保存に失敗しました。");
        }
        else
        {
            Debug.Log("ゲームデータの保存が完了しました。");
        }
    }

    // ゲームデータを非同期に保存するメソッド
    private async Task SaveGameDataAsync()
    {
        GameData data = new GameData
        {
            highScore = this.highScore;
        };

        await SaveLoadManager.Instance.SaveGameDataAsync(data);
    }

    // ゲームデータを非同期に読み込むコルーチン
    private IEnumerator LoadGameDataCoroutine()
    {
        Task<GameData> loadTask = LoadGameDataAsync();
        yield return new WaitUntil(() => loadTask.IsCompleted);

        if (loadTask.IsFaulted)
        {
            Debug.LogError("ゲームデータの読み込みに失敗しました。");
        }
        else if (loadTask.Result != null)
        {
            this.highScore = loadTask.Result.highScore;
            // generationParametersList は読み込まない
        }

        UpdateScoreUI();
    }

    // ゲームデータを非同期に読み込むメソッド
    private async Task<GameData> LoadGameDataAsync()
    {
        return await SaveLoadManager.Instance.LoadGameDataAsync();
    }
}

コードの詳細説明

非同期メソッドの呼び出し:

  • Start() メソッド:
    • StartCoroutine(LoadGameDataCoroutine()); を呼び出し、ゲーム開始時に非同期でゲームデータを読み込みます。
  • EndGame() メソッド:
    • ゲーム終了時に StartCoroutine(SaveGameDataCoroutine()); を呼び出し、非同期でゲームデータを保存します。

コルーチンの実装:

  • LoadGameDataCoroutine():
    • LoadGameDataAsync() メソッドを非同期に実行し、その完了を待ちます。
    • 読み込みに失敗した場合や成功した場合にログを出力します。
    • 読み込まれたデータが存在する場合、highScore を更新します。
  • SaveGameDataCoroutine():
    • SaveGameDataAsync() メソッドを非同期に実行し、その完了を待ちます。
    • 保存に失敗した場合や成功した場合にログを出力します。

非同期メソッドの実装:

  • SaveGameDataAsync():
    • GameData オブジェクトを作成し、SaveLoadManager.Instance.SaveGameDataAsync(data) を呼び出して非同期に保存します。
  • LoadGameDataAsync():
    • SaveLoadManager.Instance.LoadGameDataAsync() を呼び出して非同期にデータを読み込み、結果を返します。

エラーハンドリング:

  • 各コルーチン内でタスクの完了を待機した後、タスクが失敗しているかどうかをチェックし、適切なログを出力します。
  1. UIの更新:
  • スコアの更新時やデータの読み込み後に UpdateScoreUI() を呼び出して、UIを最新の状態に保ちます。

注意点

  • Unity APIの制約:
    • Unityの多くのAPI(例:GameObject.FindGetComponentTextMeshProUGUI など)はメインスレッドでのみ安全に呼び出せます。そのため、非同期メソッド内で直接これらのAPIを呼び出さないようにし、必要な場合はコルーチンを通じてメインスレッドで実行します。
  • シングルトン SaveLoadManager の存在:
    • SaveLoadManager クラスがシングルトンとして正しく実装され、シーン内に存在することを確認してください。シーンに存在しない場合、SaveLoadManager.Instancenull になる可能性があります。
  • データの整合性:
    • 非同期処理中にデータの整合性が損なわれないよう、適切なロックや同期機構を導入することを検討してください。今回の実装では、highScore の更新はメインスレッド内で行われるため、基本的な整合性は保たれています。

実装の流れ

ゲーム開始時:

  • Start() メソッドで LoadGameDataCoroutine() を開始し、非同期でゲームデータを読み込みます。
  • 読み込みが完了すると、highScore が更新され、スコアUIが最新の状態に更新されます。

ゲーム終了時:

  • タイマーがゼロになった時点で EndGame() メソッドが呼び出され、SaveGameDataCoroutine() を開始します。
  • 非同期でゲームデータが保存され、保存が完了するとログが出力されます。

スコアの更新:

  • HandleScoreChange() メソッドがスコアの変更をハンドルし、必要に応じて highScore を更新します。
  • UpdateScoreUI() により、スコアがUIに反映されます。

参考資料


Unity

Posted by hidepon