衝突処理を標準機能の物理演算で行う (2D)

ページ作成日 :

検証環境

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

この Tips の前提設定

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

はじめに

本 Tips では2つのオブジェクトを配置し、片方のオブジェクトを操作してもう片方のオブジェクトに接触したときに Unity 標準機能の物理演算を使用して相手を押し込むという動作を実装できるようにします。

ちなみに大部分の設定は前回の「オブジェクト同士が衝突しているかどうかを判定する (2D)」で説明した手順と同じになります。

下準備

ここは前回の Tips と手順がほぼ同じなのでさらっと流します。

プロジェクトを作成したらオブジェクトとなるスプライトの画像を2つ用意して追加します。 名前はそれぞれ「UnityTips」「UnityTips_2」にしています。

2つの画像ファイルをビューにドロップして追加します。

左側のオブジェクトをキーボードで動かすためにスクリプトを追加します。スクリプト名は 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);
    }
  }
}

スクリプトを動かす方のオブジェクトにアタッチします。

ゲームを実行してキーボードのカーソルキーで動くか確認してください。

最後に衝突判定状態を表示するためのテキストオブジェクトを配置しておきます。 オブジェクト名は TextState としています。

衝突処理設定

ここから衝突に関連する設定を行います。

まずは動かす方のオブジェクト「UnityTips」を選択し、インスペクターから「コンポーネントを追加」をクリックします。 一覧から Physics 2D を選択します。 Physics は 3D 対象なので間違えないでください。

Physics 2D の中から Box Collider 2D を選択します。今回のスプライトは矩形なので Box を選択していますが、例えば円状であれば Circle など他の形を選択します。

前回は当たったかどうかだけの情報を取得したかったので「トリガーにする」をチェックしましたが今回はしません。 これにより物理演算での衝突処理が有効になります。

続いて Rigidbody 2D のコンポーネントを追加します。「Physics 2D -> Rigidbody 2D」から追加できます。 Rigidbody はオブジェクトに物理演算の情報を与え、Collider を持っているオブジェクトとの衝突判定も行うことができるようになります。

下に自由落下しないように「重力スケール」を 0 に設定しておきます。

同様にもう一つのオブジェクト「UnityTips_2」にも当たり判定をつけるために Box Collider 2D コンポーネントを追加しておきます。

さらに今回は相手の力を受けて動くという動作が必要になるので、Rigidbody 2D を追加します。重力スケールは 0 にしてください。

設定はこれだけです。現時点でプログラムは不要です。ゲームを実行してもう一つのオブジェクトに接触してみてください。

こんな感じで相手を押し込むと相手が押されるような挙動をすることを確認できると思います。 標準機能の物理演算を使用するとプログラムが一切必要ないので非常にお手軽です。

オブジェクトが回転しないようにする

接触すると分かりますが当たり所がちょっとでもずれるとそれぞれのオブジェクトが回転してしまいます。 ゲームとしては不自然になることが多いので回転しないように設定する必要があります。

Rigidbody 2D コンポーネントには「Constraints」の中に「回転を固定」とい項目があるので Z にチェックを入れます。 これで回転しないように動作することができます。2つのオブジェクトに設定してゲームを実行し確認してみてください。

どのような角度から押しても回転しないことを確認できると思います。

オブジェクト同士が当たったときに何らかの処理をする

例えば弾が敵に当たったときに相手を押しつつも敵のライフを減らすなどを実装したい場合があると思います。 これは前回の Tips と同様にイベントが発生するのでそれで対応することができます。

Player スクリプトを開いて以下のように using と各フィールド、メソッドを追加します。

using System;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;

public class Player : MonoBehaviour
{
  /// <summary>状態表示用テキストオブジェクト。</summary>
  [SerializeField] private Text TextState;

  // 中略

  /// <summary>衝突した瞬間に呼ばれます。</summary>
  /// <param name="partner">衝突した相手のコリジョン情報。</param>
  private void OnCollisionEnter2D(Collision2D partner)
  {
    TextState.text = $"OnCollisionEnter2D : {partner.collider.tag} {DateTime.Now:HH:mm:ss.fff}{Environment.NewLine}{TextState.text}";
  }

  /// <summary>衝突している間呼ばれます。ただしスリープモードになった場合は呼ばれません。</summary>
  /// <param name="partner">衝突した相手のコリジョン情報。</param>
  private void OnCollisionStay2D(Collision2D partner)
  {
    TextState.text = $"OnCollisionStay2D : {partner.collider.tag} {DateTime.Now:HH:mm:ss.fff}{Environment.NewLine}{TextState.text}";
  }

  /// <summary>衝突状態でなくなったタイミングで呼ばれます。</summary>
  /// <param name="partner">衝突した相手のコリジョン情報。</param>
  private void OnCollisionExit2D(Collision2D partner)
  {
    TextState.text = $"OnCollisionExit2D : {partner.collider.tag} {DateTime.Now:HH:mm:ss.fff}{Environment.NewLine}{TextState.text}";
  }
}

当たったかどうかの情報を表示したいので以下のようにテキストオブジェクトに表示できるようにしておきます。

/// <summary>状態表示用テキストオブジェクト。</summary>
[SerializeField] private Text TextState;

衝突判定の処理を以下のように記述します。

/// <summary>衝突した瞬間に呼ばれます。</summary>
/// <param name="partner">衝突した相手のコリジョン情報。</param>
private void OnCollisionEnter2D(Collision2D partner)
{
  TextState.text = $"OnCollisionEnter2D : {partner.collider.tag} {DateTime.Now:HH:mm:ss.fff}{Environment.NewLine}{TextState.text}";
}

/// <summary>衝突している間呼ばれます。ただしスリープモードになった場合は呼ばれません。</summary>
/// <param name="partner">衝突した相手のコリジョン情報。</param>
private void OnCollisionStay2D(Collision2D partner)
{
  TextState.text = $"OnCollisionStay2D : {partner.collider.tag} {DateTime.Now:HH:mm:ss.fff}{Environment.NewLine}{TextState.text}";
}

/// <summary>衝突状態でなくなったタイミングで呼ばれます。</summary>
/// <param name="partner">衝突した相手のコリジョン情報。</param>
private void OnCollisionExit2D(Collision2D partner)
{
  TextState.text = $"OnCollisionExit2D : {partner.collider.tag} {DateTime.Now:HH:mm:ss.fff}{Environment.NewLine}{TextState.text}";
}

前回の Tips と似ていますがメソッド名は「OnTrigger------2D」ではなく「OnCollision------2D」になります。

Rigidbody 2D を持っているオブジェクトが Collider 2D を持っている他のオブジェクトに衝突すると上記の各イベントが発生します。 これにより当たっているかどうかの判別と処理ができるようになります。

イベントは3つありますがそれぞれ以下のタイミングで呼ばれます。

OnCollisionEnter2D オブジェクト同士が当たったタイミング
OnCollisionStay2D オブジェクト同士が当たっている間
OnCollisionExit2D オブジェクト同士が衝突状態から離れたタイミング

いずれも相手オブジェクトがもつ Collision2D を引数で受け取るので Collider2D.collider.tag で当たった対象の種類(敵とかアイテムとか)を調べたり Collider2D.gameObject で相手のオブジェクトを取得して処理することができます。

上記3つのイベントのうち OnCollisionEnter2DOnCollisionExit2D は一回の衝突ごとに一回呼ばれます。

OnCollisionStay2D は衝突している間毎フレーム呼ばれますが、両オブジェクトが停止してから一定時間が経過するとイベントが呼ばれなくなります。 これはスリープ状態に入ったとみなされ、動いていないものに毎フレーム衝突計算するのは処理の無駄であると判断されるためです。

スリープ状態になってほしくない場合は Rigidbody 2D のパラメータのスリープモードを「スリープしない」に変更してください。

コードを変更したら情報表示用のテキストをセットします。

ゲームを実行しキーボードでオブジェクトを動かしてもう一つのスプライトに接触してみてください。 それぞれ3つの関数が呼ばれる事を確認できると思います。

ちなみにオブジェクトを接触させてから移動を停止すると、少しの間は OnCollisionStay2D メソッドが呼ばれますが、 一定時間経過後に OnCollisionStay2D メソッドが呼ばれなくなることを確認できると思います。

これがスリープ状態に入ったことを示すのですが、このスリープに入るまでの時間を変更したい場合はプロジェクトの設定から「2D 物理」を選択して「スリープ時間」パラメータを秒で指定することにより変えることができます。 0 に設定することは出来ませんが、限りなく小さい値にすると OnCollisionStay2D メソッド自体呼ばれなくなります。