Unity で スイカ風ゲームを作ってみる#4
Unityでゲームを作る
Unity でスイカ風ゲームを作る!
Unity Version 2022.3.16
前回は、フルーツオブジェクトの物理演算について確認した。今回はInputSystem を使ったマウスポジションの検出方法を整理する。
InputSystem でマウスポジションを検知する
InputSystem については、テトリス風ゲーム作成時に以下でざっと説明している。
テトリス風ゲームの時との違いは、キーボードやゲームパッドのボタンが"押された"というON/OFFのイベントではなく、マウスの座標(X, Y)を取得する必要がある点である。マウス座標を取得するには、Input Action の"Action Properties" で"Action Type" に"Value" を選択し、"Control Type" に"Vector 2" を選択する。こうすることで、マウス座標がVector2 型のパラメータで取得できるようになる。
次にPlayer オブジェクト(Prefab)に"Player Input" コンポーネントをアタッチして、Input Action のコールバックを取得できるように設定する。
Input Action に設定したマウスの"Move" Action は、Player クラスのOnMoveEvent メソッドで受け取るように設定する。
Player.cs の抜粋
マウス座標の情報は、コールバックに設定したメソッドの引数InputAction.CallbackContext から取得できる。ただし、取得できる座標情報は、ディスプレイ上のScreen 座標であるため、利用する場合はWorld 座標に変換する必要がある。World 座標への変換は、Camera クラスのScreenToWorldPoint メソッドを利用する。プレーヤーの移動は、物理演算に従う必要はないので、World 座標に変換した座標をTransform.localPosition に直接設定してオブジェクトを移動させる。
InputAction クラスを利用するには、UnityEngine.InputSystem をUsing 宣言しておく必要があるので注意。
- public void OnMoveEvent (InputAction.CallbackContext context)
- {
- if (_pause) return;
- var screenPos = context.ReadValue<Vector2>();
- var worldPos = Camera.main.ScreenToWorldPoint(screenPos);
- Move(worldPos.x);
- }
- private void Move (float x)
- {
- var min = _leftWallX + _fruitSize.x * this.transform.localScale.x * 0.5f;
- var max = _rightWallX - _fruitSize.x * this.transform.localScale.x * 0.5f;
- if (x <= min)
- {
- x = min +0.01f;
- }
- else if (max <= x)
- {
- x = max -0.01f;
- }
- this.transform.localPosition =
- new Vector3(x, this.transform.localPosition.y, 0);
- DrawLine();
- }
Polygon Collider で箱オブジェクト
フルーツが入る箱オブジェクト(以下Field オブジェクト)には、PolygonCollider2D をアタッチして、フルーツとの衝突判定を実現する。Polygon Collider は、四角や丸と言った単純な図形ではなく、任意の数の頂点を自由に定義してその頂点をつなぎ合わせた複雑な図形にCollider の境界線を設定することができる。この特性を活かしてField のテクスチャに合わせた形にCollider 境界線を作ることにする。
PolygonCollider で使うCollider の境界線(頂点)情報は、Sprite Editor 使って視覚的に編集することができる。まずはFiled オブジェクトに使うスプライトのInspector から、"Sprite Editor" ボタンを押してEditor を起動する。
Sprite Editor が起動したら、左上のメニューボタンから"Custom Physics Shape" を選択し、Collider 編集モードに切り替える。その後、画面上部にある"Generate" ボタンを押して、一度Colliderの頂点を自動生成させる。
自動生成で大まかな頂点が描けたら、後は自分で頂点(白いポイント)を選択して位置を修正したり、頂点を追加(境界線上をクリック)したり、頂点を削除(頂点にフォーカスして"Delete"キー押し)したりして、テクスチャに合うようにCollider を編集する。
※頂点編集機能は、Unity Version 2022.3.4 あたりでバグが発生し、一時期使えない状態となっていた。この機能を使う場合は、最新のバージョンを取得して試すか、Unity のリリース情報を確認するとよい。
編集し終わったCollider は、PolygonCollider コンポーネントのInspector から頂点の座標情報を確認することができる。今回のFiled テクスチャは, 1024 x 1024 px の画像であり 128 px を 1Unit と設定しているため、一辺は8 Unit の長さとなっている。
編集したCollider 頂点がうまく反映されていない場合は、一度PolygonCollider オブジェクトを削除して、再度アタッチしなおすと反映される。
実際にField オブジェクトのCollider を設定し終わった結果がこちら。
Player の動作範囲の特定
Player が動ける範囲は、Field の箱型のテクスチャの内側の範囲である。この位置を決めるために、Collider の頂点情報を利用する。PolygonCollider コンポーネントの頂点情報をInspector で確認し、箱型のテクスチャの内側のX 座標の位置をFIELD_LEFT_WALL_X_RATIO とFIELD_RIGHT_WALL_X_RATIO の定数として定義する。Field オブジェクトは、画面サイズに合わせて拡大させるため、最終的には拡大率とオフセット位置とを合わせてLeftWallX、RightWallX のプロパティに保存しておくようにする。Player クラスからこのプロパティを使って、左右の移動可能範囲を割り出している。
- public float LeftWallX {get; private set;}
- public float RightWallX {get; private set;}
- public float LimitY {get; private set;}
- public Vector2 Size {get; private set;}
- public const float FIELD_PER_CHERRY = 14.0f; // 14 cherry = 1 field width
- private const float FIELD_UNIT = 1024f / ScreenConfig.PX_PER_UNIT; // Field Texture = 1024 x 1024 px
- private const float FIELD_LEFT_WALL_X_RATIO = 0.430f;
- private const float FIELD_RIGHT_WALL_X_RATIO = 7.57f;
- private const float FIELD_BOTTOM_Y_RATIO = 0.156f;
- private const float LIMIT_Y_VS_FIELD_WIDTH_RATIO = 1.1f;
- private Dictionary<int, FruitBase> _fruits;
- public void Init()
- {
- // Field size is calculated from screen size and margin
- var fieldHightUnit =
- ScreenConfig.Instance.OrthographicSize*2f
- - ScreenConfig.TOP_MARGIN - ScreenConfig.BOTTOM_MARGIN;
- // scale to calculate field size
- var scale = fieldHightUnit / FIELD_UNIT;
- this.transform.localScale = new Vector3(scale, scale, 1f);
- // Actual size of field
- Size = new Vector2(FIELD_UNIT * scale, FIELD_UNIT * scale);
- // field texture pivot is bottom-left
- var offset_x = ScreenConfig.Instance.CameraCenter.x - FIELD_UNIT * scale * 0.5f;
- this.transform.localPosition =
- new Vector3(offset_x, ScreenConfig.BOTTOM_MARGIN, 0f);
- // define field wall inside and limit line
- LeftWallX = FIELD_LEFT_WALL_X_RATIO * scale + offset_x;
- RightWallX = FIELD_RIGHT_WALL_X_RATIO * scale + offset_x;
- var height = (RightWallX - LeftWallX) * LIMIT_Y_VS_FIELD_WIDTH_RATIO;
- var offsetY = this.transform.position.y + this.transform.localScale.y * FIELD_BOTTOM_Y_RATIO;
- LimitY = height + offsetY; // Gameover line
- DrawLimitLine();
- _fruits = new Dictionary<int, FruitBase>();
- }
次の記事