Unity で テトリス風ゲームを作ってみる(実装編)#3
Unity Version 2022.3.4
前回の続き
前回テトリミノの生成から落下までを説明したので、今回は入力イベントの受け取り方とテトリミノの移動を説明する。
入力イベントとテトリミノの移動
まずはテトリミノが移動、回転を開始するトリガーとなるイベント受信について。
Unity では、ユーザからの入力イベントを受け取る方法としてInput System というものを提供してくれている。キーボードやコントローラー、マウス、タッチパネルなどからの入力をAction という抽象化されたイベントに紐づけて、それをGameObject にアタッチしたPlayer Input コンポーネントを経由して、コールバック関数で受け取ることができる。
入力イベントを一旦Action という抽象化されたイベントに変換することで、例えばキーボードのWASDキーとコントローラーの上下左右キーを同一のイベントとして扱うことができるようになっている。つまりスクリプトレベルではキーボードかコントローラーかといったデバイスを意識する必要がなくなる。
入力イベントを設定するまでの大まかな流れは、
"Input Action Asset" の作成 → "Action Map"の作成 → "Action"の作成 → 入力デバイスをバインド → "Player Input" コンポーネントをアタッチ → コールバックメソッドを設定
Input System は、デフォルトではインストールされていないため、Package Manager からインストールしておく必要がある("Unity Registry" の "Input System"をインストールする)。
インストールができたら、Assets フォルダで右クリックから"Create"→"Input Actions"を選択して、Input Actions Asset を作成する。下図の例では、"TetrisControl" という名前に変更。
Input Actions Assets のInspector から"Edit asset" を選択して、Action Maps を設定する。Action Map でそれぞれのAction と入力イベントを紐づける。"Action Maps"の"+"ボタンを押して、Map を作成(下図では"PlayerMap" という名前を設定)。次にそのMap に含めるAcion を"Actions" の"+"ボタンを押して作成する。一つのMap とその中に複数のAction が存在するように作成する。Map 自体も複数作成することができる。例えば、オープンワールドゲームなどで、ユーザキャラクターがフィールドにいるとき、車に乗っているとき、海の中にいるときなどで、Action の種類を切り替えられるようにするためである(歩く、車のアクセル、泳ぐなど状態によって同じ入力イベントでもAction が異なる)。
下図の例では、左右下移動のAction と左右回転のAction の5 つのAction を作っている。
次に作成した各Action に割り当てるキーイベントを設定する。Action を作成するとデフォルトで1 つのBinding アイテムが作成されている(青色の帯のアイテム)。このBinding アイテムに紐づけるキーイベントを指定する。"Path" のドロップダウンリストから"Keyboard"→"By Character Mapped key"→処理したいキーを選択すればOK。下図の例では、左回転のAction(TurnLeft)にキーボードの"Q"キーを紐づけている。
残りの左右下 Action、右回転 Action も同様にそれぞれのキーを紐づけるように設定する。
Input Actions Asset の設定が終わったら、Tetrimino オブジェクトにPlayer Input コンポーネントをアタッチしてコールバックの設定を行う。まず最初に、アタッチしたPlayer Input コンポーネントの"Actions" に先ほど作成した "Input Actions Asset" を指定する(ここでは"TetrisControl"という名前)。
イベントのコールバック関数を設定するには、"Behavior" ドロップダウンで、"Invoke Unity Events" を選択し、"Events"→"Action Map"(これは先ほど作成したMap の名前で表示される)→"Action(これも先ほど作成したAction の名前で表示される)"を開き、"+"キーを押してコールバック関数を設定する(もちろん事前にスクリプト上でコールバック関数を作っておく必要がある)。スクリプト名を直接していするのではなく、スクリプトをアタッチしているオブジェクトを選択する必要があるので注意。
下図の例では、先ほど作成した"PlayerMap"の"TurnLeft"に Tetrimino クラスのOnTurnLeftEvent 関数を指定している。これで、Q キーを押された時にTetrimino.OnTurnLeftEvent 関数が呼ばれるようになる。
次はコールバック関数の詳細について。
左右移動は、OnMoveLeftEvent とOnMoveRightEvent メソッドで処理を行う。両者の違い向きだけなので、当たり判定と移動処理は共通化してOnMoveHorizontal 関数にて処理を行う。
コールバック関数は、void 型でInputAction.CallbackContext 型の引数を1 つとるような宣言にしておかなければならない。また、"using UnityEngine.InputSystem" として、InputSystem をUsing宣言しておく必要があるので注意。
using UnityEngine.InputSystem
public void OnMoveLeftEvent (InputAction.CallbackContext context) {
// イベント処理
}
キーが押された時にこのコールバック関数が呼ばれるが、実際にはボタンが押されたとき(InputActionPhase.Started)、ボタンの押し具合が特定の閾値を超えたとき( InputActionPhase.Performed)、ボタンを離したとき(InputActionPhase.Canceled)の3 種類のイベントがそれぞれ呼ばれることになる。今回は単にキーボードが押された時のイベントだけで処理を行えばよいので、引数のcontext.phase でStarted かどうかを判定して、イベントをフィルタリングしている。
Tetrimino.cs の抜粋(左右キーイベントコールバック)
- public void OnMoveLeftEvent (InputAction.CallbackContext context) {
- if (!IsOperatable() || context.phase != InputActionPhase.Started)
- return;
- OnMoveHorizontal(Vector2.left);
- }
- public void OnMoveRightEvent (InputAction.CallbackContext context) {
- if (!IsOperatable() || context.phase != InputActionPhase.Started)
- return;
- OnMoveHorizontal(Vector2.right);
- }
水平移動は、各ブロックの移動先に他のブロックがないことをCollisionCheck メソッドで確認し、移動可能な場合は、Tetrimino オブジェクトを左右のどちらかにlocalPosition を使って1 Unit 移動させることで実現可能。CollisionCheck は、落下時に作成したものと同一の関数を使う。
Tetrimino.cs の抜粋(水平移動)
- private bool OnMoveHorizontal (Vector2 direct) {
- var isCollision = false;
- foreach (var block in _blockPrefabs) {
- if (CollisionCheck(block.transform, direct)) {
- isCollision = true;
- break;
- }
- }
- if (isCollision) return false;
- transform.localPosition = new Vector3 (
- transform.localPosition.x + direct.x,
- transform.localPosition.y,
- transform.localPosition.z);
- return true;
- }
以上で、ユーザのキー入力イベントを受け取って、テトリミノを左右に移動するという処理が実現できる。
次の記事