Appearance
第09回 UIとシーン遷移
前回: 第08回 GameManagerとゲーム状態 / 次回: 第10回 ビルドと公開
今回の授業内容
- ゲームルール、表示、Scene遷移の責務を分ける
- TextMeshProの初期設定と日本語Font Assetの作成
- GameManagerのイベントと現在値からUIを更新する
- HUDとResult Panelを役割ごとに構成する
- Title SceneとScene遷移用コンポーネントを実装する
- Title、Game、Result、Retryを循環し、状態が初期化されることを確認する
今回の配布コード
09.zipは、第08回終了時点のコードへUIとScene遷移を追加した一式です。
配布ファイル一覧
| ファイル | 今回の変更 |
|---|---|
| GameUI.cs | 撃破数とゲーム結果を表示する |
| SceneNavigation.cs | Sceneの読み込み、再挑戦、終了を扱う |
ゲームルール・表示・Scene遷移の分担
前回ゲームの状態を管理するGameManagerを実装しました。今回はゲーム状態の表示を行うHUDと、ゲーム終了後の結果表示とScene遷移を行う結果パネルのUIを作ります。
Enemy / Player
ゲーム内で起きた事実を通知する
GameManager
GameManager
状態と撃破数を通知する
GameUI
GameUI
表示へ反映する
Text / Panel
Button
クリックを受け渡す
SceneNavigation
SceneNavigation
Sceneの読み込みを依頼する
SceneManager
| コンポーネント | 責務 | 持たせない処理 |
|---|---|---|
GameManager | 撃破数と勝敗を決める | Textの書き換え、Scene読み込み |
GameUI | 現在値を画面へ反映する | 勝敗条件の判定 |
SceneNavigation | Sceneを読み込む | 撃破数やHPの管理 |
イベント通知と現在値の取得の使い分け
イベントは、ある瞬間に起きたことを通知するための仕組みです。 GameUIが有効になる前にイベントが発行されていた場合、その過去の通知は受け取れません。 つまり、初期化にイベントを使えるとは限りません。
ここでの初期化はイベントに頼らず、GameManagerの現在値を直接読み取ってUIへ反映しています。
| タイミング | 方法 | 目的 |
|---|---|---|
| GameUIの開始時 | プロパティから現在値を読む | 初期表示を必ず現在状態にそろえる |
| 開始後に値が変わるとき | イベントを受け取る | 毎フレーム確認せず、その瞬間だけ更新する |
TextMeshProの初期設定と日本語フォントの準備
TextMeshProとは
TextMeshPro(TMP)は高品質な文字表示を行うためのアセットです。 Unityの標準機能ではありますが、フォントを扱うためには初期設定が必要です。 以下の手順でセットアップを済ませてください。
TMP Essential Resourcesをインポートする

TMPを初めて使用するProjectでは、シェーダーや初期設定などの基本リソースをインポートします。
- Unityのメニューから
Window > TextMeshPro > Import TMP Essential Resourcesを選択します Importを押し、表示されたファイルをProjectへ追加しますAssets/TextMesh Proフォルダが作成されたことを確認します
初めてText - TextMeshProを作成したときにTMP Importerが表示された場合は、そこからImport TMP Essentialsを選んでも構いません。
Import TMP Examples & Extras について
Import TMP Examples & Extrasはサンプル集のため今回の制作には必要ありませんが、TMPの使い方を学ぶ上で参考になるサンプルが含まれています。 ぜひ時間のあるときにインポートして、Assets/TextMesh Pro/Examples & Extrasの中身を確認してみてください。
Font Assetの準備
TextMeshPro(TMP)は、文字を表示するためにFont Assetを使用します。
WindowsやMacOSといったOSが標準で扱うフォントは.ttfや.otf形式のフォントファイルです。 フォントの形状を定義するグリフ情報を持つファイルで、OSやアプリケーションが文字を描画する際に参照します。
一方、UnityのFont AssetはTMPが文字を描画するために必要な情報をまとめたアセットで、内部的には画像(Font Atlas)と文字の形状情報を持つデータファイルで構成されます。 こうしたFont Assetは、TMPのツール経由で.ttfや.otf形式のフォントファイルから作成することになります。
日本語フォントからFont Assetを作成する
TMPの既定Font Assetに日本語の文字が含まれていない場合、□のような代替文字で表示されることになります。 日本語を表示するには、日本語のグリフを持つフォントファイルからFont Assetを作成しなければなりません。
- ゲームへの組み込みが許可された日本語フォントのライセンスを確認します
- Projectウィンドウの
Assets内にFontsフォルダを作成します .ttfまたは.otf形式のフォントファイルをAssets/Fontsへドラッグします- 取り込んだフォントファイルを選択します
Assets > Create > TextMeshPro > Font Asset > SDFを選択します- 作成されたFont Assetを選択し、Inspectorの
Atlas Population ModeをDynamicにします Source Font Fileに元のフォントファイルが設定されていることを確認します
Dynamicは表示に必要になった文字がFont Atlasへ追加されます。 StaticはFont Asset作成時に指定した文字だけがFont Atlasに含まれます。 扱いやすいため、今回の授業ではDynamicでFont Assetを作成します。
Font AssetのDynamicとStaticの違い
Dynamicは必要な文字を都度追加するため管理が容易で、消費メモリの量も少なくて済む利点があります。 ですがグリフ追加時に画像の再生成が発生するため、処理落ちを起こす危険もあります。
ゲームでは処理落ちを許容できない場合がほとんどのため、Staticで必要な文字をすべて含めてFont Assetを作成する方が安全です。 ただしフォント画像作成の手順はやや煩雑で、画像生成にも時間がかかります。
この授業内ではDynamicでFont Assetを作成し、必要な文字が表示されることを確認するだけに留めます。
フォントのライセンスを確認する
WebサイトやOSから取得できるフォントがゲームで利用できるとは限りません。 特にゲームでの利用は再配布にあたると判断され、規約上の禁止事項に含まれる場合があります。 利用条件を確認し、ゲームへの組み込みが許可されたフォントを使用するようにしてください。
フォントに限らず、利用許諾の確認は外部アセットを使用する際の基本です。 必ず確認し、利用条件に従って使用してください。
TMPのTextにFont Assetを設定する
作成した日本語Font Assetは、自動ですべてのTextへ適用されるわけではありません。 各TextMeshPro - Text (UI)コンポーネントのFont Assetへ設定します。
今回作成するDefeat Count Text、Result Text、Button内のText、Title SceneのTextには、同じ日本語Font Assetを設定します。 Textに撃破数 0 / 10と入力し、Gameビューで日本語、数字、記号が欠けずに表示されることを確認しましょう。
Game SceneのUIを作成する
UI構造を決める
第04回で作成したPlayerとEnemyのHPゲージは、どちらも同じHealthGaugeを使用しています。 機能が実装されているという意味ではこのままでも構わないのですが、通常のゲームではプレイヤーのHPゲージは特別なものとして画面の定位置に表示されるものです。
今回の制作でもプレイヤーのHPゲージは画面の左上に固定して表示することにします。
| ゲージ | CanvasのRender Mode | 理由 |
|---|---|---|
| Player HP Gauge | Screen Space - Overlay | 画面の一定位置に表示し、常に確認できるようにする |
| Enemy HP Gauge | World Space | Enemyの頭上に置き、どのEnemyのHPか判別できるようにする |
CanvasのRender ModeとHierarchy上の配置によって、画面に固定するUIとゲーム世界に置くUIを使い分けます。
HealthGaugeのスクリプトは共通のままで構いません。 一方、カメラの方を向かせるFaceCameraはWorld Space UIのためのコンポーネントですので、画面に固定するPlayer HP Gaugeには使用しません。
今回作成する画面UIと、Enemy Prefabに残すワールド空間UIは次のように分けます。
- Game UI Canvas(Screen Space - Overlay)
- HUD
- Player HP Gauge
- Defeat Count Text
- Result Panel
- Result Text
- Retry Button
- Title Button
- HUD
- EventSystem
- Enemy Prefab
- Enemy UI Canvas(World Space)
- Enemy HP Gauge
- Enemy UI Canvas(World Space)
HUDはプレイ中に常時見る情報を、Result Panelは終了時だけ必要な操作をまとめます。 同じUIではありますが、目的がまるで異なるものです。 こうしたUI部品は役割ごとに親を分け、Panel単位で表示を切り替えると管理しやすくなります。
既存のHPゲージはHUDへ移動させない
ここまでの実装では、Player HP GaugeはPlayerの子ObjectとしてPrefab化されています。 Prefabインスタンス内の子Objectを外側のHUDへ直接移動することはできません。
今回は既存のゲージやCanvasを移動・変換せず、Game UI Canvas > HUDの中にPlayer用のScreen Space HP Gaugeを新規作成します。 既存ゲージは削除しても構いませんし、無効化しても構いません。
Game UI CanvasとHUDを作成する
- Game Sceneを開きます
GameObject > UI > CanvasからCanvasを作成し、Game UI Canvasと名付けますGame UI Canvasの子に空のGameObjectを作り、HUDと名付けます- Hierarchyに
EventSystemが1つあることを確認します。なければGameObject > UI > Event Systemから作成します
Game UI Canvasの各コンポーネントを次のように設定します。
| コンポーネント | 項目 | 設定値 |
|---|---|---|
| Canvas | Render Mode | Screen Space - Overlay |
| Canvas Scaler | UI Scale Mode | Scale With Screen Size |
| Canvas Scaler | Reference Resolution | 1920 x 1080 |
| Canvas Scaler | Match | 0.5 |
Player HP Gaugeを作成する
まず、HPゲージを構成するGameObjectとImageを作成します。
HUDの子に空のUI用GameObjectを作成し、Player HP Gaugeと名付けます- Player HP Gaugeへ
HealthGaugeを追加します - Player HP Gaugeの子に背景用の
Backと前景用のFrontのImageを作成します
BackとFrontは同じ大きさに重ね、次のように設定します。
| 対象 | 項目 | 設定値 |
|---|---|---|
| Back / Front | Image Type | Filled |
| Back / Front | Fill Method | Horizontal |
| Back / Front | Origin | Left |
| HealthGauge | Front | 作成したFront |
| HealthGauge | Back | 作成したBack |
FaceCameraはWorld Space UIのためのコンポーネントなので、Player HP Gaugeには追加しません。
画面左上へ配置する
Player HP GaugeのRectTransformを次のように調整します。
| 項目 | 設定 |
|---|---|
| Rotation | 0 |
| Scale | 1 |
| Anchor Presets | 左上 |
| Width / Height | HPゲージとして見やすい大きさ |
| Pos X / Pos Y | 左上から適度に余白を空けた位置 |
Playerへ接続する
- Scene上のPlayerを選択します
PlayerコンポーネントのHealth Gaugeへ、新しいPlayer HP Gaugeを設定します- HierarchyでPlayerを展開し、子にある
Health Gauge 3Dを探します Health Gauge 3DのルートGameObjectを選択し、Inspector上部のチェックを外して無効にします
テンプレートによって名前が異なる場合は、Canvas、Canvas Scaler、FaceCameraを持つPlayer用のWorld Space HP Gaugeを探してください。
PrefabへApplyしない
Scene上に配置したPlayerインスタンスからは、同じSceneのPlayer HP Gaugeを参照できます。ただし、PrefabアセットからScene上のObjectは参照できません。
Health Gaugeの設定はScene上のPlayerに保存し、PrefabへApplyしないでください。
PlayしてHPを変化させ、新しいPlayer HP Gaugeへ反映されることを確認します。
撃破数Textを作成する
HUDの子にUI > Text - TextMeshProを作成します- 名前を
Defeat Count Textにします
TextMeshProとRectTransformを次のように設定します。
| 項目 | 設定 |
|---|---|
| Text | 撃破数 0 / 10 |
| Font Asset | 準備した日本語Font Asset |
| Anchor Presets | 左上 |
| 配置 | Player HP Gaugeと重ならない位置 |
Result Panelを作成する
Game UI Canvasの子にUI > Panelを作り、Result Panelと名付けます- Panelの子に
Result Text、Retry Button、Title Buttonを作成します
作成したTextを次のように設定します。
| 対象 | Text | Font Asset |
|---|---|---|
| Result Text | 結果表示用の仮の文字 | 準備した日本語Font Asset |
| Retry Button内のText | RETRY | 準備した日本語Font Asset |
| Title Button内のText | TITLE | 準備した日本語Font Asset |
PanelはあとでGameUIが非表示にするため、Editor上では表示したまま配置を調整して構いません。 邪魔な場合は適時Result PanelのルートGameObjectを無効にしましょう。
画面比率を変えて配置を確認する
16:9の例
4:3の例
縦長の例
Gameビューを16:9、4:3、Free Aspectへ順に切り替え、次の点を確認します。
- Player HP GaugeとDefeat Count Textが画面左上に表示される
- UIが画面外へ出ず、左上からの余白が大きく変わらない
最後にPlayし、Player HP Gaugeが画面左上に固定されることを確認します。 Enemy HP Gaugeは、対応するEnemyと一緒に移動していれば問題ありません。
GameUIを実装する
Assets/Scripts/GameUI.csを作成します。
cs
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class GameUI : MonoBehaviour
{
[SerializeField] private GameManager gameManager;
[SerializeField] private TMP_Text defeatCountText;
[SerializeField] private GameObject resultPanel;
[SerializeField] private TMP_Text resultText;
[SerializeField] private Button retryButton;GameUIが扱うのは表示だけです。 撃破数のカウントなどのゲームルールには干渉しないことに注目してください。
イベントの購読と解除
cs
private void OnEnable()
{
gameManager.DefeatCountChanged += OnDefeatCountChanged;
gameManager.StateChanged += OnStateChanged;
}
private void OnDisable()
{
gameManager.DefeatCountChanged -= OnDefeatCountChanged;
gameManager.StateChanged -= OnStateChanged;
}GameManagerと同様、イベントの購読と解除を対で設定します。
開始時は現在値で初期化する
cs
private void Start()
{
Refresh();
}
private void Refresh()
{
OnDefeatCountChanged(
gameManager.DefeatCount,
gameManager.ClearDefeatCount);
resultPanel.SetActive(false);
OnStateChanged(gameManager.State);
}先述の通り、イベントでの更新は購読前の情報を知ることができません。 開始時はGameManagerが現在持っている値で初期化を行います。
撃破数を文字列に変換する
cs
private void OnDefeatCountChanged(int count, int goal)
{
defeatCountText.text = $"Defeat {count} / {goal}";
}文字列補間
$"..."は文字列補間です。{}で囲まれた式の値を文字列に変換して埋め込みます。 例えば上のコードはcountとgoalの値を文字列に変換してDefeat 0 / 10のように出力します。
終了状態をResult Panelに表示する
cs
private void OnStateChanged(GameState state)
{
if (state != GameState.GameClear &&
state != GameState.GameOver)
return;
resultPanel.SetActive(true);
resultText.text = state == GameState.GameClear
? "GAME CLEAR"
: "GAME OVER";
EventSystem.current.SetSelectedGameObject(
retryButton.gameObject);
}GameOverとGameClearの2つの状態をResult Panelに表示します。 Panelを表示した直後にRetry Buttonを選択することで、方向キーやコントローラーからButton操作を始められるようにします。
三項演算子
三項演算子は条件によって2つの値から1つを選ぶ式です。 例えば、このコードは、次のif文と同じ意味です。
cs
// 三項演算子の例
resultText.text = state == GameState.GameClear
? "GAME CLEAR"
: "GAME OVER";
// if文の例
if (state == GameState.GameClear)
resultText.text = "GAME CLEAR";
else
resultText.text = "GAME OVER";GameUIをInspectorで接続する
Game UI CanvasへGameUIを追加しますGame ManagerへHierarchyのGame Managerを設定しますDefeat Count Textへ作成したTMP Textを設定しますResult PanelへPanelを設定しますResult TextへPanel内のTMP Textを設定しますRetry ButtonへPanel内のRetry Buttonを設定します- Playして、開始時にPanelが隠れることを確認します
- Enemyを倒し、撃破数が更新されることを確認します
- ClearとOverで異なる文字が表示されることを確認します
5つの参照欄のいずれかがNoneのままの場合、イベント購読や表示更新でNullReferenceExceptionになります。Consoleの最初の例外から確認します。
Title SceneとScene遷移を作成する
Title Sceneを作成する
- Game Sceneを保存します
File > New Sceneで新しいSceneを作成します- SceneをプロジェクトのScenesフォルダへ
Titleという名前で保存します - Canvasを作成し、タイトル、ゲーム目的、操作方法などをTextで配置します
- 作成した各Textの
Font Assetへ、準備した日本語Font Assetを設定します Start Buttonを作成し、表示をSTARTにします- Start Button内のTextにも同じFont Assetを設定します
- 空のGameObjectを作成し、
Scene Navigationと名付けます
SceneNavigationを実装する
Assets/Scripts/SceneNavigation.csを作成します。
cs
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneNavigation : MonoBehaviour
{
public void LoadScene(string sceneName)
{
SceneManager.LoadScene(sceneName);
}
public void Retry()
{
SceneManager.LoadScene(
SceneManager.GetActiveScene().buildIndex);
}
public void Quit()
{
Application.Quit();
}
}LoadScene()は指定名のScene、Retry()は現在のSceneを読み直します。 Sceneを読み直すとGameObjectと各コンポーネントの状態が作り直されるため、PlayerControllerとEnemySpawnerは初期状態になります。
Scene名を文字列で指定する方法は小規模な教材では分かりやすい一方、名前変更やスペルミスをコンパイル時に検出できません。 注意して扱いましょう。
発展:Sceneのビルドインデックスをenumとして自動生成する
シーン名をenum化できれば、シーンの名前を直接文字列で指定する必要がなくなります。 有効なSceneのビルド順をenumの整数値として生成し、SceneManagerへビルドインデックスを渡す運用であれば、Scene名の変更やスペルミスをコンパイル時に検出できます。
Unityの標準機能では対応できませんが、Editor拡張で自動生成できます。 以下のAssets/Editor/SceneIdGenerator.csを作成してみましょう。
Editorフォルダ内に置かれたスクリプトはUnity Editorでだけ実行されゲーム本体のビルドには含まれないため、ゲームの実行速度や容量に影響しません。 安心して追加してください。
SceneIdGenerator.cscs
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
public static class SceneIdGenerator
{
private const string OutputPath =
"Assets/Scripts/SceneId.cs";
[MenuItem("Tools/Generate Scene Id")]
private static void Generate()
{
var usedNames = new HashSet<string>();
var members = EditorBuildSettings.scenes
.Where(scene => scene.enabled)
.Select((scene, buildIndex) =>
{
var sceneName =
Path.GetFileNameWithoutExtension(scene.path);
var baseName = ToIdentifier(sceneName);
var memberName = baseName;
var suffix = 2;
while (!usedNames.Add(memberName))
{
memberName = $"{baseName}_{suffix}";
suffix++;
}
return $" {memberName} = {buildIndex}";
});
var source =
$"public enum SceneId\n{{\n" +
$"{string.Join(",\n", members)}\n}}\n";
File.WriteAllText(OutputPath, source);
AssetDatabase.Refresh();
}
private static string ToIdentifier(string sceneName)
{
var builder = new StringBuilder();
foreach (var character in sceneName)
{
if (char.IsWhiteSpace(character))
builder.Append('_');
else if (char.IsLetterOrDigit(character) ||
character == '_')
builder.Append(character);
}
var identifier = builder.ToString();
if (string.IsNullOrEmpty(identifier))
identifier = "Scene";
if (char.IsDigit(identifier[0]))
identifier = $"_{identifier}";
return identifier;
}
}Tools > Generate Scene Idを実行すると、現在有効なSceneから次のようなファイルが生成されます。
SceneId.cscs
public enum SceneId
{
Title = 0,
Game = 1
}enumの各値は、ビルド対象に並んだ有効Sceneのインデックスと一致します。 生成時には空白を_へ置き換え、英数字と_以外の記号を削除します。
例えばMain Menu!はMain_Menu、Stage-01はStage01になります。 変換後の名前が重複した場合は、Stage01_2のように連番を加えます。 先頭が数字になる場合は_を加えます。 classやnamespaceなどのC#の予約語はScene名に使用しないでください。
以下のSceneNavigationではenumをintへ変換し、ビルドインデックスとして読み込みます。 Buttonから呼ぶ引数なしのメソッドを用意すれば、InspectorへScene名を直接入力する必要もありません。
SceneNavigation.cscs
private static void LoadScene(SceneId sceneId)
{
SceneManager.LoadScene((int)sceneId);
}
public void LoadGame()
{
LoadScene(SceneId.Game);
}
public void LoadTitle()
{
LoadScene(SceneId.Title);
}Sceneを追加、削除、改名、並べ替えた場合は、ビルド対象の登録を更新してから生成を再実行します。 生成されたSceneId.csは直接編集しないでください。
Sceneをビルド対象へ登録する
使用中のUnityのメニューからBuild ProfilesまたはBuild Settingsを開きます。
TitleとGameをScene一覧へ追加しますTitleを先頭、Gameを次に並べます- 両方のSceneが有効になっていることを確認します
- Sceneファイル名が
TitleとGameであることを確認します
INFO
SceneManagerはビルド対象に登録されていないSceneを読み込めません。 シーン切り替えがうまくいかない場合は、まずビルド対象の登録を確認してください。
Buttonに処理を接続する
Title SceneのStart Button

- Scene Navigationへ
SceneNavigationを追加します - Start Buttonの
On Click()で+を押します - Scene NavigationをObject欄へドラッグします
SceneNavigation > LoadScene(string)を選択します- 引数へ
Gameと入力します
Game SceneのResult Panel
- Game Sceneにも
Scene Navigationを作り、同じコンポーネントを追加します - Retry Buttonへ
SceneNavigation.Retry()を設定します - Title Buttonへ
SceneNavigation.LoadScene(string)を設定し、引数をTitleにします - Game Sceneを保存します
Quit()は任意で追加してください。 Application.Quit()はEditor上では動作しませんが、ビルド版ではアプリケーションを終了できます。
画面遷移を通しで確認する
| 手順 | 期待結果 |
|---|---|
| TitleのSTARTを押す | Game Sceneが読み込まれ、移動できる |
| Enemyを倒す | 撃破数が1 / 10のように更新される |
| 目標数を倒す | GAME CLEARと2つのButtonが表示される |
| RETRYを押す | Game Sceneが初期状態で始まり、時間が進む |
| Playerを死亡させる | GAME OVERが表示される |
| TITLEを押す | Title Sceneへ戻る |
| 再度STARTを押す | Game Sceneをもう一度正常に開始できる |
実習:表示とゲームルールの境界を確認する
- 撃破数の表示を
ENEMY 1 / 10などへ変更します - 表示変更だけではクリア条件が変わらないことを確認します
- GameManagerの
Clear Defeat Countを変更します - 表示される目標数と実際のクリア条件が同時に変わることを確認します
- どの値をGameManagerとGameUIのどちらが所有するか説明します
動作しない場合のチェックリスト
Textが更新されない
- GameUIの5つの参照が設定されているか
- Consoleに
NullReferenceExceptionがないか - GameUIが有効でイベントを購読しているか
- GameManager側の撃破数が増えているか
日本語が□で表示される
- 日本語を収録したフォントファイルからFont Assetを作成したか
- TMPの
Font Assetへ作成した日本語Font Assetを設定したか - Font Assetの
Atlas Population ModeがDynamicになっているか Source Font Fileに元のフォントファイルが設定されているか
Result Panelが表示されない
Result PanelへPanel本体を設定したか- GameManagerのStateが終了状態へ遷移しているか
- Panelの親Canvasが有効か
- Hierarchyに有効な
EventSystemが1つあるか
Player HPゲージが表示・更新されない
Game UI CanvasのRender ModeがScreen Space - Overlayか- Player HP Gaugeが
Game UI Canvas > HUDの子にあるか - Playerインスタンスの
Health Gaugeに画面用ゲージを設定したか HealthGaugeのFrontとBackにImageを設定したか- 画面用のPlayer HP Gaugeに
FaceCameraが付いていないか - Player HP GaugeのAnchorとRectTransformが画面外になっていないか
Enemy HPゲージが画面に固定されてしまう
- Enemy HP Gaugeを
Game UI Canvasへ移動していないか - Enemy Prefab内のCanvasが
World Spaceのままか - Enemy Prefab内の
FaceCameraが有効か - Enemyの
Health Gaugeに、同じEnemy Prefab内のゲージを設定したか
Buttonを押してもSceneが変わらない
- ButtonのOn ClickへScene Navigationを設定したか
- SceneをBuild ProfilesまたはBuild Settingsへ登録したか
TitleとGameの大文字小文字が一致しているか- 透明なUIがButtonのRaycastを遮っていないか
Retry後にゲームが止まっている
- Retry先がGame Scene自身になっているか
- PlayerControllerとEnemySpawnerがSceneの初期状態で有効か
- GameManagerのInspector参照が設定されているか
完成条件
- GameManager、GameUI、SceneNavigationの責務を説明できる
- Player用のScreen Space UIとEnemy用のWorld Space UIを使い分けられる
- 画面比率を変えてもPlayer HPゲージが画面左上に残る
- 初期値取得とイベント更新の両方でUIを現在状態へ同期できる
- Title、Game、ClearまたはOver、Retry、Titleを循環できる
- HP、撃破数、勝敗、操作方法を画面から判断できる
- UIの表示変更がゲームルールを直接変更しない
まとめ
- GameManagerを情報源、GameUIを表示担当として分離した
- 開始時に現在値を取得し、その後はイベントで表示を更新した
- PlayerのHPゲージはScreen Space、EnemyのHPゲージはWorld Spaceに配置した
- HUDとResult Panelを用途ごとに整理した
- Sceneを読み直し、各ゲームプレイコンポーネントを初期状態から再開できるようにした