Week07_GameFlow - M-634/unity-game-dev-tutorial GitHub Wiki
📘 Week07 - ゲームループの構築
🎯 今週の目的
- プレイ開始から終了(タイムアップ or プレイヤー死亡)までのゲームループを実装する
- シーンを使って「タイトル → プレイ → リザルト」までの流れを構築する
- GameManagerによるスコア・ゲーム時間・状態の一元管理
- サバイバル型ゲームに必須なタイマーや状態制御を体験する
🛠 授業の流れ
🎬 1. シーン構成(3つ)
以下新規3つのSceneファイルをScenesフォルダ以下に作成する。
- 今まで開発していたシーンはGameSceneへリネームすること
シーン名 | 役割 |
---|---|
TitleScene |
ゲームの開始画面(スタートボタンなど) |
GameScene |
実際のプレイ画面(敵出現、成長、制限時間) |
ResultScene |
結果画面(スコア、リトライ、タイトルへ戻る) |
🔧 2. Build Settings にシーンを追加
- 各シーンを
Assets/Scenes
に保存する - Unity上部メニュー
File > Build Settings...
を開く - Scenes In Buildに
TitleScene
,GameScene
,ResultScene
をドラッグアンドドロップで追加 - 並び順は上から
TitleScene
,GameScene
,ResultScene
になるように調整(起動時にロードされる)
※ SceneManager.LoadScene()
で指定する名前はこの Build Settings に登録された名前と一致している必要があります。
🧠 3. GameManager(Singleton)による状態管理とスコア管理
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
public GameState CurrentState { get; private set; }
public float ElapsedTime { get; private set; }
public int KillCount { get; private set; }
[SerializeField]
private float _maxTime = 600f; // 10分
public float MaxTime => _maxTime;
private void Awake()
{
if (Instance != null) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
}
private void Update()
{
if (CurrentState != GameState.Playing) return;
ElapsedTime += Time.deltaTime;
if (ElapsedTime >= _maxTime)
{
EndGame(true); // タイムアップ勝利
}
}
public void StartGame()
{
ElapsedTime = 0f;
KillCount = 0;
CurrentState = GameState.Playing;
}
public void AddKill()
{
KillCount++;
}
public void EndGame(bool isTimeUp)
{
CurrentState = isTimeUp ? GameState.TimeUp : GameState.GameOver;
SceneManager.LoadScene("ResultScene");
}
}
public enum GameState
{
Title = 0,
Playing = 1,
GameOver = 2,
TimeUp = 3
}
💡 4. SceneLoaderを作成 (静的クラス)
using UnityEngine;
using UnityEngine.SceneManagement;
public static class SceneLoader
{
public static void LoadGame()
{
SceneManager.LoadScene("GameScene");
}
public static void LoadTitle()
{
SceneManager.LoadScene("TitleScene");
}
public static void LoadResult()
{
SceneManager.LoadScene("ResultScene");
}
public static void QuitGame()
{
Application.Quit();
}
}
⏳ 5. タイマーUIの実装(TextMeshProUGUI)
using UnityEngine;
using TMPro;
public class GameTimerUI : MonoBehaviour
{
[SerializeField]
private TextMeshProUGUI _timerText;
private void Start()
{
//GameSceneがロードされた瞬間にゲームスタート
if (GameManager.Instance != null)
{
GameManager.Instance.StartGame();
}
}
private void Update()
{
if (GameManager.Instance == null) return;
float remain = Mathf.Max(0f, GameManager.Instance.MaxTime - GameManager.Instance.ElapsedTime);
int minutes = Mathf.FloorToInt(remain / 60);
int seconds = Mathf.FloorToInt(remain % 60);
_timerText.text = $"{minutes:00}:{seconds:00}";
}
}
- GameScene上に空のゲームオブジェクトを作成してアタッチ
- その後Timer用のテキストを用意する
🪦 6. PlayerHealth.cs に追記(プレイヤー死亡時の遷移処理)
追加部分のところをコメントしてます。
using UnityEngine;
public class PlayerHealth : MonoBehaviour
{
[SerializeField]
private int _maxHp = 5;
[SerializeField]
private PlayerHealthUI _playerHealthUI;
private int _currentHp;
private bool _isDead;
void Start()
{
_currentHp = _maxHp;
_playerHealthUI.SetHp(_currentHp, _maxHp);
}
public void TakeDamage(int damage)
{
if(_isDead) return;
_currentHp -= damage;
_playerHealthUI.SetHp(_currentHp, _maxHp);
if (_currentHp <= 0)
{
OnDead();//追加・修正部分
}
}
private void OnDead()
{
_isDead = true;
GameManager.Instance?.EndGame(false);
Destroy(gameObject);
}
}
7. 🎮 Title.cs(タイトル画面用)作成
空のGameObjectをTitle.scene上で作成し、以下コンポーネントをアタッチする。
using UnityEngine;
using UnityEngine.UI;
public class Title : MonoBehaviour
{
[SerializeField]
private Button _loadGameButton;
[SerializeField]
private Button _quitGameButton;
private void Start()
{
_loadGameButton.onClick.AddListener(OnStartButton);
_quitGameButton.onClick.AddListener(OnQuitButton);
}
private void OnDestroy()
{
_loadGameButton.onClick.RemoveListener(OnStartButton);
_quitGameButton.onClick.RemoveListener(OnQuitButton);
}
private void OnStartButton()
{
SceneLoader.LoadGame();
}
private void OnQuitButton()
{
SceneLoader.QuitGame();
}
}
8. 🏁 Result.cs(リザルト画面用)
空のGameObjectをResult.scene上で作成し、以下コンポーネントをアタッチする。
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class Result : MonoBehaviour
{
[SerializeField]
private TextMeshProUGUI _killText;
[SerializeField]
private TextMeshProUGUI _timeText;
[SerializeField]
private Button _loadGameButton;
[SerializeField]
private Button _loadTitleButton;
private void Start()
{
int kills = GameManager.Instance?.KillCount ?? 0;
float time = GameManager.Instance?.ElapsedTime ?? 0f;
_killText.text = $"Defeat Enemy Count: {kills}";
_timeText.text = $"Survival Time Count : {Mathf.FloorToInt(time / 60):00}:{Mathf.FloorToInt(time % 60):00}";
_loadGameButton.onClick.AddListener(OnRetryButton);
_loadTitleButton.onClick.AddListener(OnTitleButton);
}
private void OnDestroy()
{
_loadGameButton.onClick.RemoveListener(OnRetryButton);
_loadTitleButton.onClick.RemoveListener(OnTitleButton);
}
private void OnRetryButton()
{
SceneLoader.LoadGame();
}
private void OnTitleButton()
{
SceneLoader.LoadTitle();
}
}
9. 🔧 Title と ResultシーンのUIを調整しよう
- 動作の必要なButtonやTextを用意する
- レイアウトの調整はおこのみで
- Titleシーン上にGameManagerコンポーネントをアタッチした空オブジェクトを作成しておくこと
✅ 動作チェックリスト
- TitleScene → GameScene へ遷移する
- GameScene で 10分タイマーが進行し、0で終了
- プレイヤーが死亡すると即座に終了
- ResultScene に Kill数、時間が表示される
- ResultScene から「Retry」「Title」へ戻れる
- GameManager がスコアと時間を保持している
📝 課題
- ResultSceneからTitleへ戻るか、Game画面をリスタートさせてみよう
- ResultSceneで「Victory」or「GameOVer」のUI演出を表示しよう
- ResultSceneに敵を倒した数(killCount)を正しく表示しよう
- ResultScene に「ハイスコア」を記録する仕組みを追加しよう
- 例)PlayerPrefs を用いた保存・読み出し
- 例)JSONファイルでのスコア保存(応用)
// 保存
PlayerPrefs.SetInt("HighScore", GameManager.Instance.KillCount);
PlayerPrefs.Save();
// 読み込み
int best = PlayerPrefs.GetInt("HighScore", 0);
// JSON保存(応用)
[System.Serializable]
public class ScoreData
{
public int highScore;
}
public void SaveToJson()
{
ScoreData data = new() { highScore = GameManager.Instance.KillCount };
string json = JsonUtility.ToJson(data);
File.WriteAllText(Application.persistentDataPath + "/score.json", json);
}