状態に応じた切り替え
CoddyのUnityジャーニー「本格的なゲームを作る」セクションの一部。レッスン 15/56。
ゲームの各状態は、フレームごとに異なる処理を行います。状態フィールドに対するswitchを使うと、状態ごとに1つのケースに処理をまとめられ、各フレームでは必ず1つのケースだけが実行されます。状態の変更は1つのメソッドを通じて行うため、画面は常に状態と一致します。このボス戦には、導入、戦闘、勝利画面があります。
public enum Phase { Intro, Fight, Won }
public class BossFight : MonoBehaviour
{
public GameObject introPanel;
public GameObject fightPanel;
public GameObject winPanel;
public int bossHp = 10;
private Phase phase;
private float timer = 3f;
void Start()
{
Enter(Phase.Intro);
}
void Update()
{
switch (phase)
{
case Phase.Intro:
timer -= Time.deltaTime;
if (timer <= 0f) Enter(Phase.Fight);
break;
case Phase.Fight:
if (bossHp <= 0) Enter(Phase.Won);
break;
case Phase.Won:
break;
}
}
void Enter(Phase next)
{
phase = next;
introPanel.SetActive(next == Phase.Intro);
fightPanel.SetActive(next == Phase.Fight);
winPanel.SetActive(next == Phase.Won);
}
}C#では、次のケースへ続けて実行されるケースはコンパイルできないため、各ケースの末尾にbreakを置きます。導入のタイマーが切れると、Enter(Phase.Fight)によってフェーズが変わりますが、このフレームではすでに実行するケースが決まっているため、Fightケースが実行されるのは次のフレームです。ifブロックを続けて個別に書いた場合、同じフレーム内に戦闘が始まり、戦闘のコードも実行されることがあります。
Enterだけがphaseを変更する場所です。next == Phase.Fightは戦闘パネルに対してのみtrueとなるため、1回の呼び出しでそのパネルを表示し、ほかの2つを非表示にします。タイマーのリセットなど、状態に入るときに行う処理もEnterに記述すると、毎フレームではなく1回だけ実行されます。
チャレンジ
簡単連打レース:5秒間にSpaceキーを何回押せるでしょうか? GameManager には、GameState 列挙型(Ready、Playing、GameOver)、各状態用のパネルがあり、StartではSetState(GameState.Ready)を呼び出します。UpdateとSetStateを記述しましょう。
Updateでは、stateに応じて分岐します。
- Ready:Spaceキーを押すとラウンドが始まります。このときの押下は連打回数に含めません。
- Playing:Spaceキーを押すたびに
tapsに1を加算し、tapsTextにTaps: 3のように表示します。roundTime秒(5秒)後にゲームが終了します。 - GameOver:Spaceキーを押すとReadyに戻ります。
SetState(GameState next)はstateを設定し、新しい状態のパネルだけを表示します。Playingに入ると、連打回数を0にして、画面にTaps: 0と表示し、残り時間をroundTime秒にしてラウンドを開始します。GameOverに入ると、結果をresultTextにTaps: 3のように表示します。
Playを押して、2回レースをしてからCheckを押してください。
自分で試してみよう
using UnityEngine;
using TMPro;
public enum GameState { Ready, Playing, GameOver }
public class GameManager : MonoBehaviour
{
public GameObject readyPanel;
public GameObject playPanel;
public GameObject gameOverPanel;
public TMP_Text tapsText;
public TMP_Text resultText;
public float roundTime = 5f;
public GameState state;
private int taps;
private float timeLeft;
void Start()
{
SetState(GameState.Ready);
}
void Update()
{
// state に応じて処理を分岐する:
// Ready: Space でラウンドを開始する
// Playing: Space でタップ数を増やす。制限時間が過ぎるとゲーム終了
// GameOver: Space で Ready に戻る
}
void SetState(GameState next)
{
state = next;
// 新しい状態のパネルだけを表示する
// 新しいラウンドはタップ数 0、制限時間 roundTime 秒で開始し、終了時に resultText にタップ数を表示する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。