課題11の参考資料)非同期処理を導入した GameDirector クラス
以下に、非同期処理(async/await) を導入し、SaveLoadManager
クラスと連携させた GameDirector
クラスの最新バージョンを示します。この実装により、大量のデータを扱う際や保存・読み込みに時間がかかる場合でも、メインスレッドの負荷を軽減し、フレームドロップを防ぐことができます。
目次
更新ポイント
非同期メソッドの呼び出し:
- ゲーム開始時に非同期でデータを読み込み。
- ゲーム終了時に非同期でデータを保存。
コルーチンの使用:
- Unityの
MonoBehaviour
メソッド(Start
やUpdate
)は直接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()
を呼び出して非同期にデータを読み込み、結果を返します。
エラーハンドリング:
- 各コルーチン内でタスクの完了を待機した後、タスクが失敗しているかどうかをチェックし、適切なログを出力します。
- UIの更新:
- スコアの更新時やデータの読み込み後に
UpdateScoreUI()
を呼び出して、UIを最新の状態に保ちます。
注意点
- Unity APIの制約:
- Unityの多くのAPI(例:
GameObject.Find
、GetComponent
、TextMeshProUGUI
など)はメインスレッドでのみ安全に呼び出せます。そのため、非同期メソッド内で直接これらのAPIを呼び出さないようにし、必要な場合はコルーチンを通じてメインスレッドで実行します。
- Unityの多くのAPI(例:
- シングルトン
SaveLoadManager
の存在:SaveLoadManager
クラスがシングルトンとして正しく実装され、シーン内に存在することを確認してください。シーンに存在しない場合、SaveLoadManager.Instance
がnull
になる可能性があります。
- データの整合性:
- 非同期処理中にデータの整合性が損なわれないよう、適切なロックや同期機構を導入することを検討してください。今回の実装では、
highScore
の更新はメインスレッド内で行われるため、基本的な整合性は保たれています。
- 非同期処理中にデータの整合性が損なわれないよう、適切なロックや同期機構を導入することを検討してください。今回の実装では、
実装の流れ
ゲーム開始時:
Start()
メソッドでLoadGameDataCoroutine()
を開始し、非同期でゲームデータを読み込みます。- 読み込みが完了すると、
highScore
が更新され、スコアUIが最新の状態に更新されます。
ゲーム終了時:
- タイマーがゼロになった時点で
EndGame()
メソッドが呼び出され、SaveGameDataCoroutine()
を開始します。 - 非同期でゲームデータが保存され、保存が完了するとログが出力されます。
スコアの更新:
HandleScoreChange()
メソッドがスコアの変更をハンドルし、必要に応じてhighScore
を更新します。UpdateScoreUI()
により、スコアがUIに反映されます。
参考資料
- C#の
async
/await
入門 - Unity公式ドキュメント – Coroutine
- Task-based Asynchronous Pattern (TAP) の理解
- Unityでの非同期プログラミングのベストプラクティス
ディスカッション
コメント一覧
まだ、コメントがありません