独習 Unity アプリ開発

独習でスマフォ向けアプリ開発を勉強中

Unity で8番出口風ゲームを作る!#7

Unityでゲームを作る

Unity で8番出口風ゲームを作る!

 Unity Version 2022.3.16

 

タイトル画面


タイトル画面やゲームクリア画面のUI は、以前テトリス風ゲームでUIを作った時と同様の方法で実装する。

タイトル画面は、下図のように透明なパネル上にテキストでタイトルを表示し、その下にボタンを配置するだけのシンプル構造。

タイトル画面

タイトルUI パネルの構造は、Panel オブジェクトにText オブジェクトと Button オブジェクトを子オブジェクトとして配置する。ボタンオブジェクトにはコールバックとして、GameControl.OnStartGame メソッドを登録しておく。

タイトルUIオブジェクトの構成

GameControl.cs の抜粋

スタートボタンのコールバックメソッドでは、Player クラスのStartPlayer を呼び出して、ユーザ操作(マウスやWASDキー)ができるように設定を変更する。タイトル画面中は、逆にPlayer が動かないようにするためユーザ操作を無効にしている。

  •   public void OnStartGame()
  •   {
  •     _state = State.Playing;
  •     player.StartPlayer();
  •     uiControl.HideTitleUI();
  •   }

 

UIControl.cs

UIControl クラスは、タイトルUI とゲームクリアUI 画面の表示/非表示を制御するためのクラス。スタートボタンが押された時は、タイトルUI画面(Panel UI オブジェクト)を消してゲーム画面のみにする。表示/非表示の仕方は、対象のGameObject のSetActive メソッドを使ってオブジェクトの有効/無効を切り替えることで実現する。 

  • public class UIControl : MonoBehaviour
  • {
  •   [SerializeField] private GameObject titleUI;
  •   [SerializeField] private GameObject gameClearUI;
  •  
  •   public void ShowTitleUI()
  •   {
  •     gameClearUI.SetActive(false);
  •     titleUI.SetActive(true);
  •   }
  •  
  •   public void ShowGameClearUI()
  •   {
  •     titleUI.SetActive(false);
  •     gameClearUI.SetActive(true);
  •   }
  •  
  •   public void HideTitleUI()
  •   {
  •     titleUI.SetActive(false);
  •   }
  •  
  •   public void HideGameClearUI()
  •   {
  •     gameClearUI.SetActive(false);
  •   }
  • }

 

Player.cs の抜粋

UI 画面表示中は、後ろで表示されているゲーム画面のユーザ操作を止める必要がある。ユーザ操作の有効/無効を切り替えるには、シンプルには入力イベントを受信しないようにすればよい。今回は、入力イベントの大元であるPlayerInput コンポーネントのenabled メンバを使って実現する。例えば、このメンバにfalse を設定すれば、入力イベントを受信しないようにすることができる。

  •   public void StartPlayer()
  •   {
  •     var playerInput = GetComponent<PlayerInput>();
  •     playerInput.enabled = true;
  •     _isStarted = true;
  •   }
  •  
  •   public void StopPlayer()
  •   {
  •     var playerInput = GetComponent<PlayerInput>();
  •     playerInput.enabled = false;
  •     _isStarted = false;
  •   }

 

下のGIFが実装した結果をキャプチャしたもの。タイトル画面のスタートボタンが押されるまでは、マウスやキーを押しても画面は動かないが、スタート後は操作できるようになっているのがわかる。

タイトル画面からスタート

 

ゲームクリア


8番出口風ゲームのクリア条件は、異変のあり/なしを特定の回数連続して当てた後に出口から脱出することである。異変あり/なしの正解を確認するタイミングは、プレーヤーが通路B を進んで、又は通路B から戻ってA1 ゲートから通路A に入る時と(下図の左側)、通路B を進んで、又は通路B から戻ってA2 ゲートから通路A に入るタイミング(下図の右側)の2ヵ所とする。(前方に進んでいるパターンと、後方に進んでいるパターンがあるので2パターンが必要)

異変検出タイミング

 

 

プレーヤーがどのゲートを通過したかは、以前説明したとおりPlayer クラスで衝突検知(OnTriggerEnter)使って行い、それをGameControl クラスのOnPlayerEvent メソッドでコールバックする。プレーヤーがどの経路を歩いてきたかは、RootChecker クラスのCheckRoot メソッドで判定する。判定結果から看板の表記変更をUpdateKanban メソッドの中で行っている。また、正解が特定回数(MAX_COUNT)以上になった場合は、通路A の代わりに出口を生成するようにしている(PrepareExit)。

GameControl.cs の抜粋

  •   public void OnPlayerEvent(PlayerEventParams param)
  •   {
  •     GameObject path = null;
  •     PlayerRoot root = PlayerRoot.None;
  •     switch(param._type)
  •     {
  •       case PlayerEventParams.Type.TouchGateA2Out:
  •         Debug.Log("GateA2 Out");
  •         if (!_rootChecker.CheckRoot(RootChecker.GateRoot.A2Out))
  •         {
  •           UpdateKanban(false, param._path);
  •         }
  •  
  •         if (_count >= MAX_COUNT)
  •         {
  •           PrepareExit(true);
  •           ihenControl.DeleteObjects();
  •         }
  •         else
  •         {
  •           PreparePathA(true);
  •         }
  •         
  •         break;
  •  
  •       case PlayerEventParams.Type.TouchGateA1In:
  •         Debug.Log("GateA1 In");
  •         UpdateKanban(
  •           _rootChecker.CheckRoot(RootChecker.GateRoot.A1In),
  •           param._path,
  •           true);
  •         PreparePathB(true);
  •         break;
  •  
  •       case PlayerEventParams.Type.TouchGateA2In:
  •         Debug.Log("GateA2 In");
  •         UpdateKanban(
  •           _rootChecker.CheckRoot(RootChecker.GateRoot.A2In),
  •           param._path,
  •           false);
  •         PreparePathB(false);
  •         break;
  •  
  •       case PlayerEventParams.Type.TouchGateA1Out:
  •         Debug.Log("GateA1 Out");
  •         if (!_rootChecker.CheckRoot(RootChecker.GateRoot.A1Out))
  •         {
  •           UpdateKanban(false, param._path);
  •         }
  •  
  •         if (_count >= MAX_COUNT)
  •         {
  •           PrepareExit(false);
  •           ihenControl.DeleteObjects();
  •         }
  •         else
  •         {
  •           PreparePathA(false);
  •         }
  •         break;
  •       
  •       case PlayerEventParams.Type.TouchGateExit:
  •         Debug.Log("Exit");
  •         _state = State.Title;
  •         GameClear();
  •         break;
  •  
  •       case PlayerEventParams.Type.TouchGateRobotFWD:
  •         ihenControl.IhenStart();
  •         break;
  •  
  •       case PlayerEventParams.Type.TouchGateRobotBWD:
  •         ihenControl.IhenStart();
  •         break;
  •     }
  •   }
  •  
  •   private void UpdateKanban(bool isSuccess, GameObject path, bool isForward)
  •   {
  •     UpdateKanban(isSuccess, path);
  •     var kanban = path.GetComponent<KanbanControl>();
  •     kanban.SetDirection(isForward);
  •   }
  •  
  •   private void UpdateKanban(bool isSuccess, GameObject path)
  •   {
  •     _count = isSuccess ? _count + 1 : 0;
  •     Debug.Log($"CountUp {_count}");
  •     var kanban = path.GetComponent<KanbanControl>();
  •     kanban.SetTexture(_count);
  •   }

 

RootChecker.cs の抜粋

CheckRoot メソッドでは、前回の通過ゲートと今回の通過ゲートの2 つを使ってプレーヤーの歩いてきた経路を判断する。

  •   public bool CheckRoot(GateRoot current)
  •   {
  •     var root = CheckRoot(_prevGateRoot, current);
  •     if (root == PlayerRoot.Hikikaeshi)
  •     {
  •       SetGateRoot(GateRoot.None);
  •     }
  •     else
  •     {
  •       SetGateRoot(current);
  •     }
  •     return VerifyRoot(root, _isExistIhen);
  •   }
  •  
  •   private PlayerRoot CheckRoot(GateRoot prev, GateRoot curr)
  •   {
  •     if (prev == GateRoot.A1In)
  •     {
  •       if (curr == GateRoot.A2Out)
  •       {
  •         Debug.Log("Passing Root A");
  •         return PlayerRoot.Passing;
  •       }
  •       else if (curr == GateRoot.A1Out)
  •       {
  •         Debug.Log("Hikikaeshi");
  •         return PlayerRoot.Hikikaeshi;
  •       }
  •     }
  •     else if (prev == GateRoot.A1Out)
  •     {
  •       if (curr == GateRoot.A2In)
  •       {
  •         Debug.Log("Backword No Ihen");
  •         return PlayerRoot.BackwardNoIhen;
  •       }
  •       else if (curr == GateRoot.A1In)
  •       {
  •         Debug.Log("Backword Ihen");
  •         return PlayerRoot.ForwardIhen;
  •       }
  •     }
  •     else if (prev == GateRoot.A2In)
  •     {
  •       if (curr == GateRoot.A1Out)
  •       {
  •         Debug.Log("Passing Root A");
  •         return PlayerRoot.Passing;
  •       }
  •       else if (curr == GateRoot.A2Out)
  •       {
  •         Debug.Log("Hikikaeshi");
  •         return PlayerRoot.Hikikaeshi;
  •       }
  •     }
  •     else if (prev == GateRoot.A2Out)
  •     {
  •       if (curr == GateRoot.A1In)
  •       {
  •         Debug.Log("Forward No Ihen");
  •         return PlayerRoot.ForwardNoIhen;
  •       }
  •       else if (curr == GateRoot.A2In)
  •       {
  •         Debug.Log("Forward Ihen");
  •         return PlayerRoot.ForwardIhen;
  •       }
  •     }
  •     return PlayerRoot.None;
  •   }
  •  
  •   private bool VerifyRoot(PlayerRoot root, bool isIhen)
  •   {
  •     if (root == PlayerRoot.Hikikaeshi || root == PlayerRoot.None)
  •     {
  •       return false;
  •     }
  •  
  •     if (root == PlayerRoot.ForwardIhen || root == PlayerRoot.BackwardIhen)
  •     {
  •       if (isIhen)
  •       {
  •         return true;
  •       }
  •       else
  •       {
  •         return false;
  •       }
  •     }
  •     else if (root == PlayerRoot.ForwardNoIhen || root == PlayerRoot.BackwardNoIhen)
  •     {
  •       if (isIhen)
  •       {
  •         return false;
  •       }
  •       else
  •       {
  •         return true;
  •       }
  •     }
  •     return true;
  •   }

 

 

正解数を2回にして出口を試作した結果がこちら。

www.youtube.com

 

 

次の記事


(準備中)

 

 


目次に戻る