タイルマップとの当たり判定を実装する

ページ作成日 :

検証環境

Windows
  • Windows 11
Unity エディター
  • 2021.3.3f1
入力システムパッケージ
  • 1.3.0

この Tips の前提設定

この Tips の説明の前提として以下の設定を事前に行っています。

この Tips の前提知識

マップ素材の借用について

以下のサイトからお借りしたものを加工しています。

歩行キャラクターの実装

まずはマップと当たり判定を行うキャラクターを実装します。このキャラクターはキーボードのカーソルキーで動かせます。 ただちゃんとした歩行を実装すると長くなってしまい本 Tips の本質とは離れてしまいますので、今回は単純な1枚絵を移動させるだけにとどめます。

歩行アニメーションを実装したい場合は以下の Tips を参照してください。

プロジェクトを作成したら動かすオブジェクトのスプライト画像をプロジェクトに追加します。

画像を選択しインスペクターから「ユニット毎のピクセル数」を 32 にします。 これは今回マップチップの1マスのサイズが 32 ピクセルなのでそれに合わせるためです。 設定したら下にある「適用する」ボタンをクリックします。

スプライトをビューに配置します。

スプライトを移動するスクリプトを作成します。これは以下の Tips の内容とほぼ同じです。

スクリプト名は Player としておきます。

コードを以下のようにします。

using UnityEngine;
using UnityEngine.InputSystem;

public class Player : MonoBehaviour
{
  // 一定時間ごとに呼ばれます
  void FixedUpdate()
  {
    // キーボードの情報を取得
    var keyboard = Keyboard.current;

    // スプライトの移動処理
    if (keyboard.leftArrowKey.isPressed)
    {
      transform.Translate(-0.1f, 0, 0, Space.World);
    }
    if (keyboard.rightArrowKey.isPressed)
    {
      transform.Translate(0.1f, 0, 0, Space.World);
    }
    if (keyboard.upArrowKey.isPressed)
    {
      transform.Translate(0, 0.1f, 0, Space.World);
    }
    if (keyboard.downArrowKey.isPressed)
    {
      transform.Translate(0, -0.1f, 0, Space.World);
    }
  }
}

動かすスプライトにアタッチします。

ゲームを実行してキーボード操作で移動するか確認してください。

マップチップの準備

まずはマップチップを準備してマップを作成することまでやります。 この手順は以下の Tips とほぼ同じです。 2D Tilemap Extras を適用したマップチップでも衝突判定は可能ですが、手順が長くなるので今回は使用しません。

マップチップの元画像を用意してプロジェクトに追加します。 動作を分かりやすくするためにマップチップ素材は「歩ける場所」と「歩けない場所」を用意しておいてください。

インスペクターでマップチップ用に設定し、適用するボタンをクリックします。 設定したら Sprite Editor を開きます。

32 ピクセルのマップチップなので 32 で分割しておきます。

タイルパレットタブを選択します。ない場合はメニューから「ウィンドウ -> 2D -> タイルパレット」で表示してください。 新しいパレットを作成します。名前は任意ですがここでは「MapChipPalette」としておきます。

Scenes に MapChipPalette フォルダを作成しそこを指定します。

マップチップをパレットにドラッグして追加します。保存場所は先ほど作成した MapChipPalette フォルダにしておきます。

マップの作成

今回マップの当たり判定は「Tilemap」単位で行います。 そのため「当たり判定のない Tilemap」と「当たり判定のある Tilemap」の2つを作成します。

まずはヒエラルキーに2つ Tilemap を作成します。 Grid の中に2つ作って構いません。名前はそれぞれ分かりやすく「TilemapObeject」「TilemapGround」としておきます。

まずは「TilemapGround」を作成します。移動可能範囲なので単純に地面を敷き詰めればOKです。 地面であれば模様を変えてもOKです。

ちなみにキャラクターが隠れてしまう場合はキャラクターオブジェクトを選択してインスペクターの Sprite Renderer のレイヤーの順序を 2 にします。 レイヤーの順序はデフォルト 0 で Tilemap も 0 なのでこの値より大きくすれば手前に表示されます。

次に「TilemapObject」を選択して移動不可にするマップチップを配置します。 先にレイヤーの順序を 1 に設定しておいてください。これは地面よりも手前に表示させるためです。

地面を非表示にした場合はこんな感じです。

もちろんこの時点では衝突判定に関する設定は何もしていないので高台に普通に乗れたりします。 この後衝突判定の設定をしていきます。

衝突判定設定

まずはキャラクターの設定を行います。この設定は以下の Tips とほぼ同じなので詳細は省き簡単に手順を説明していきます。

ヒエラルキーからキャラクターオブジェクトを選択し、「コンポーネントを追加」から「Physics 2D -> Circle Collider 2D」を選択します。 オブジェクトの形が矩形なら「Box Collider 2D」でも構いません。

同様に「Physics 2D -> Rigidbody 2D」を追加します。

自動落下を防ぐために Rigidbody 2D の重力スケールを 0 にします。

衝突したときにスプライトが回転しないように Constraints の「回転を固定」で Z にチェックを入れます。

次に当たり判定をつけたい「TilemapObject」を選択して「コンポーネントを追加」から「Tilemap -> Tilemap Collider 2D」を選択します。

設定はこれだけです。プログラムも一切必要ありません。 ゲームを実行して壁になるオブジェクトに当たりに行ってみてください。きちんと止まることを確認できると思います。

ただ、実際に当たってみると挙動で気になる点が出てくると思います。 その解決方法をいくつか紹介します。

マップとの当たり判定の不都合な挙動と解決方法

壁にぶつかってみるといくつか気になる点が出てくると思います。

壁ずり移動をすると途中で引っかかる

Box Collider 2D を使用しているオブジェクトだと発生すると思います。

例えば下図の場面から左下にキーを押したときに壁に沿って左に移動してほしいと思うはずです。 しかしある程度進んだ後に何かに引っかかってしまい止まってしまうと思います。

位置によって壁のへこみを認識する

Circle Collider 2D をアタッチしていると分かりやすいと思います。 壁は平らなはずなのに壁ずり移動をすると壁がでこぼこしているような動きをすると思います。

ひっかかりとでこぼこの原因と解決方法

これらが発生する理由としては、「当たり判定がマップチップ単位で行われる」為によるものを思われます。 実際 TilemapObject を選択すると、当たり判定のある場所に緑の線が表示されるのですが、これがマップチップ単位で線が引かれていることが分かります。

なので、移動するオブジェクトがちょうどマップチップ1つ分のライン上であれば1つのマップチップとの当たり判定を行いますが、 マップチップの境目で衝突すると2つのマップチップと当たり判定を行うので跳ね返りの強さが上がります。

これを解決するにはくっついているマップチップは当たり判定を結合してしまうのがベストだと思います。 やり方は以下のようにします。

インスペクターから当たり判定のある「TilemapObject」を選択し、Tilemap Collider 2D から「コンポジットで使用」にチェックを入れます。

警告文にあるとおりこれだけでは効果がないので、「コンポーネントを追加」から「Physics 2D -> Composite Collider 2D」を選択します。

Composite Collider 2D と一緒に Rigidbody 2D も追加されます。

するとちょっとわかりにくいですが、当たり判定のあるマップチップ同士の境目の緑色の線が消えていることを確認できると思います。

タイルマップが動かないようにしたいので Rigidbody 2D コンポーネントのボディタイプは「静的」にしておきます。

ではゲームを実行して動かしてみてください。壁に沿って斜め移動しても引っかかることもなくでこぼこ移動もなくスムーズに移動ができるようになっていると思います。

キャラクターが壁から押し返される挙動をなくす

キャラクターを壁を押し込むように移動した後移動を止めると分かるのですが、壁から少し弾き返されるような挙動をすると思います。

これを解決するにはキャラクターオブジェクトを選択し、Rigidbody 2D コンポーネントから「衝突判定」を「連続的」に変更すれば解決します。

「非連続的」と「連続的」では何が違うのかというと以下のようになります。

非連続的

衝突判定は移動してから考える仕組みです。移動して壁に埋め込まれた状態から衝突判定を行うような形です。 トランポリンやゴムなどを連想すると分かりやすいですが、埋め込まれればそれだけ反発の力が上がるので跳ね返りも強くなります。 その代わり移動してから計算をするので処理コストは低くなります。

連続的

移動する途中に何があるかを判定しながら処理します。移動前の位置と移動後の位置の間に壁があるかどうかを判定し、 壁があればそこで衝突判定をして止まるので実質反発はないです。 その代わり移動する途中の計算が入るので処理コストは少し上がります。

マップオブジェクトの角でひっかかる

これについてはプログラムや位置によって引っかかるものと引っかからないものがあり、原因がよく変わっていません。 ちなみに Box Collider 2D のような角が角ばっているもので発生し、丸みを持っている Circle などでは発生しません。

こういった謎現象が発生してもひっかかりを回避するにはコライダーの角に丸みを持たせると良いです。 Box Collider 2D 限定ですが、丸みをもたせるにはキャラクターオブジェクトの Box Collider 2D コンポーネントに「エッジの半径」があるのでそこに値を入れます。 数値の単位としては1を入れるとオブジェクトサイズ分の丸みが発生します。 サイズ感にもよりますが 0.1 程度入れると良いでしょう。

ちなみに丸みのサイズだけ当たり判定が大きくなるので大きい場合は調整してください。 Box Collider 2D に「コライダー編集」ボタンがあるので、ボタンをクリックするとビューで直接サイズを変更できます。 外側の緑色の線が実際の当たり判定なので不自然にならないサイズを設定します。

後はゲームを実行してひっかからないか確認し調整してください。