OnCollisionEnter、OnTriggerEnterが呼ばれない場合を検証

この記事はOnCollisionやOnTriggerがたまに呼ばれない時があるので、どういった状態の時呼ばれないのかを検証してみました。

<OnCollisionとOnTriggerとは>

衝突、侵入を検知するメソッドです。

OnCollisionは衝突を、OnTriggerは侵入を検知します。

MonoBehaviourを継承することによって特別な機能を持つこれらのメソッドが使えます。

3Dの衝突検知
OnCollisionEnter, OnCollisionStay, OnCollisionExit

2Dの衝突検知
OnCollisionEnter2D, OnCollisionStay2D, OnCollisionExit2D

3D侵入検知
OnTriggerEnter, OnTriggerStay, OnTriggerExit

2D侵入検知
OnTriggerEnter2D, OnTriggerStay2D, OnTriggerExit2D

同じ名前、同じ引数、同じ返り値で定義する事によってUnity側から呼ばれます。

Enterで衝突、侵入し始め、Stayで衝突中、侵入中、Exitで衝突状態から離れた、侵入状態から出たですね。

これらがうまく呼ばれない時があるので調査してみました。

<反応しない時、まず凡ミスがないか確認しよう>

まず、凡ミスしてないかちょっと確認してみましょう。

正直これはメインではないので、ボタンで閉じておきます。↓のボタンで開くので、呼ばれない状態に陥った方はまず凡ミスがないか確認してください。

クリックすると展開します

凡ミスは誰でもするので、ぇーと思わずとりあえず一回確認してみましょう。

1. メソッド名が間違っている

めっちゃ初歩的なミスですが、まぁやってしまう時はやってしまいます。

とりあえず確認してみてください。

OnCollisionEnter, OnCollisionStay, OnCollisionExit,OnCollisionEnter2D, OnCollisionStay2D, OnCollisionExit2D,OnTriggerEnter, OnTriggerStay, OnTriggerExit,OnTriggerEnter2D, OnTriggerStay2D, OnTriggerExit2D

2Dがついているかついてないか、綴りは間違っていないかを確認してみてください。

2. 引数が間違っている

OnCollision系は引数が「Collision」です。

つまり、

void OnCollisionEnter(Collision col)
{
}
こうです。

ですが、OnTrigger系の引数は「Collider」になっています。OnCollisionとは違うので注意してください。

つまり、

void OnTriggerEnter(Collider col)
{
}

となります。

2種類で違うのがややこしいので間違いの元になっています。注意しましょう。

3. Rigidbodyがついていない

衝突、もしくは侵入をするオブジェクトのどちらか一方、もしくは両方にRigidbodyかRigidbody2Dがくっついてないと検知することができません。

何故か両方ついていないといけないという情報をちらほら見ますが、片方で動きます。

少なくともどちらかにはRigidbodyをつけましょう。

また、衝突するコライダーと対応しているかも確認しましょう。

4. CollisionとTrigger間違っていないか

Collisionを取りたいのにコライダーのIsTriggerにチェックが入っていたり、Triggerを取りたいのにIsTriggerにチェックが入っていなかったりしませんか?

(多分これはオブジェクトがすり抜けたり、ぶつかってしまったりするので言われなくても分かるかも)

5. RigidbodyがKinematicになっていないか

3DならIs Kinematicにチェックが入っていると物理計算を行わないオブジェクトになります。

2DならBody TypeがKinematicになっていると物理計算を行わないオブジェクトになります。

オブジェクトのどちらか一方にRigidbodyがついている場合は、Kinematicになっていると検知できません。

オブジェクトの両方にRigidbodyがついている場合は、両方がKinematicになっていると検知できません。どちらか一方がKinematicではないと検知できます。

なお、OnCollisionを取れなくなりますが、OnTriggerは取れます。

6. 2Dと3D間違っていないか

メソッド名で確認した時にわかったと思いますが、2D用と3D用があります。

OnCollision○○、OnTrigger○○の3D用と

OnCollision○○2D、OnTrigger○○2Dの2D用と

2種類あるので気をつけましょう。

<Physics Layer Collision Matrixのチェックが外れている>

ProjectSettingsのPhysics、Physics2Dの項目の中にLayer Collision Matrixと言う項目があります。

ここにチェックがついているレイヤーのオブジェクト同士は衝突しますが、ここのチェックが外れていると衝突そのものが発生しません。

ここのチェックが、アセット等を導入した際に自動で外れてしまうケースがあるようです。バグや不具合ではなく、そのアセットを使用するために、自動でチェックを外してくれているようです。ただ、これに気づかず、チェックが外れてしまったレイヤーを使用してしまうと、衝突そのものが起きないので、チェックが外れていないかどうかを見ておきましょう。

<反応しない場合を検証してみた>

↑の凡ミスを確認してみたけど、どーもうまく動かない、呼ばれないそういった場合がありましたので、どういう状態の時呼ばれないか検証してみました。

OnTrigger○○2Dを球のバウンドで検証

とりあえず、なるべく簡素に検証できるようにするため、OnTrigger○○2Dで一旦検証してみようと思います。

↓の球にCircleCollider2Dがついています。これは衝突します。
球の下にある緑の四角がBoxCollider2DでIsTriggerにチェックを入れています。

そして、この球にRigidbody2Dをつけて、PhysicsMaterial2Dによりバウンドするようにします。

それでもって、下の床にスクリプトを貼り付けます。

使うスクリプトもバリ簡単です。

クリックすると展開します
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
     private void OnTriggerEnter2D(Collider2D collision)
     {
         Debug.Log("エンター");
     }
     private void OnTriggerStay2D(Collider2D collision)
     {
          Debug.Log("ステイ");
     }
     private void OnTriggerExit2D(Collider2D collision)
     {
          Debug.Log("イグジット");
     }
}

球の下についた接地判定をチェックする感じです。

↓このようにちゃんとEnter,Stay,Exitが呼ばれているのがわかります。

ここで、グラビティスケールを大きくして球の落下速度を速くしてみます。3倍です。

(絵面としては一緒なのでgif動画はこの部分はなしでいきます)

ステイが呼ばれなくなりました。

このことからEnterで入った時はStayは呼ばれない事がわかります。そして、留まっている時間がない場合呼ばれないようです。

ここまでは納得できます。

ここからさらにグラビティスケールを大きくし、球の落下速度を速くします。5倍です。

すると、コンソール画面に何も表示されなくなりました。

球が徐々に高く上昇しているのはバウンド係数が1で力が減衰せず、重力だけが加算されていくからです。

どうやら、物体の侵入速度が速すぎると検知できない感じになっています。これで合っているかどうか更に検証を進めます。

さらにもっと速くしてみます。10倍です。

すると今度は検知されました

どうやらこの事から、物理計算によって座標が計算された時、Trigger判定がコライダーに侵入しているかどうかでOnTrigger系が呼ばれるという事がわかります。

つまり↓のような感じだと思われます。

要は、物体が速すぎる場合OnTrigger系はタイミングによりけりになってしまうという事がわかります。

OnCollison○○2Dを球のバウンドで検証

ではOnCllision系はどうでしょう。スクリプトを↓のようにしてみます。

クリックすると展開します
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
         private void OnCollisionEnter2D(Collision2D collision)
         {
              Debug.Log("コリジョンエンター");
         }
         private void OnCollisionStay2D(Collision2D collision)
         {
              Debug.Log("コリジョンステイ");
         }
         private void OnCollisionExit2D(Collision2D collision)
         {
              Debug.Log("コリジョンイグジット");
         }
 }

球の接地判定部分ではなく、球自体の衝突判定を調べます。

すると、物理的に正しい場合、どのような場合でも↓のような結果になりました。

物理的に正しい場合というのは、グラビティスケールを余りに大きくしてしまった場合、衝突せずに突き抜ける現象が発生するのでこれは考慮に入れません。

また、グラビティスケールを余りに小さくしてしまった場合、球がバウンドしなくなったのでこれは除外して考えます。

つまり、バウンドする場合において、Enter,Exitが必ず呼ばれるがStayは呼ばれない事がわかります。

Stayは今回バウンド係数1なので力が減衰せずすぐ反発するので呼ばれないものと考えられます。

Collisionの場合は、Triggerの時のような気まぐれさはないみたいです。(結構色々な幅の速度で試してみましたが結果同様でした)

処理をバリ重くした場合

処理が重くなった場合の検証をするために大量のCubeを用意して、球にはぶつからないよう衝突にさせます。

球は2Dの衝突判定、Cubeは3Dの衝突判定にし↓これらを無作為に衝突させます。

細かい白い点が全部Cubeですね。この状態でTriggerの判定をみて見ます。

当然処理はバリ重い、カックカクです。この状態でどうなるのか

↓結果です。

正しく動いていることがわかります。

Collisionは

これも正しく動いている事がわかります。

処理が重い場合で変になるわけではないみたいです。

ジャストExitをキメた場合

数値的にジャストになるようにExitした場合はどうなるでしょうか。

やってみます。

まずジャストExitの位置がどこなのか調べます。

↓のようにCubeを二つ並べます。コライダーの大きさは1です。Scaleも1,1,1です。

なので、Exitが呼ばれるケースはCubeとCubeの距離が1になったら呼ばれそうですが、試した結果違いました。

少しずつ数値をずらして境界を調べた結果

1.02000123262405406999999・・・ ←Stay
1.02000123262405407 ←Exit

となりました。

Exitされる場合にちょっと余り幅があるっぽいです。

ジャストExitはピッタリの位置からプラス0.02000123262405407した位置みたいです。(Scale1,1,1の場合)

境界がわかったのでピッタリ重なった状態からジャストExitする位置に数値を一気に動かしてみます。

↓結果です。

正しい結果が帰ってきました。

というかtransfom直いじりしてもExitを検知しているのでとても優秀である事がわかります。

Enterも正しく動きました。

と、ここでふと思います。

判定の重なり幅が0.02000123262405406999999以下の場合どうなるのでしょう?

と、思ってコライダーのサイズを限りなく小さくしてみましたが、検知されました。

コライダーのサイズが限りなく小さくした場合でも衝突は検知できるみたいです。

Rigidbodyがついていない方が動く場合

今まで、Rigidbody2Dがついている方動かしてましたが、Rigidbody2Dがついていない場合はどうなるかみてみましょう。

↓左がRigidbody有り、右がRigidbody無しです。

ちゃんと動いている事がわかります。

↓ところがこれをRigidbody2Dではなく、Rigidbodyに変更した場合

↑左がRigidbody有りで右がRigidbody無しです。

左を動かした場合は衝突している事がわかりますが、右を動かしている時は衝突していません。

Rigidbody2Dの時はついてない方を動かしても衝突を検知していましたが、Rigidbodyの場合は検知できないっぽい。。。。

と、見せかけて、なんどもCubeを行ったりきたりしていたら、たまにぶつかる場合がありました。

Rigidbodyのスリープモードがなんらかの条件で解除されるみたいなのですが、条件がちょっと不明でした。(Cubeを何度も左右に行ったり来たりさせるとたまに衝突する)

よって、Rigidbodyをつけていない方を動かした場合、Rigidbody2Dは衝突するが、Rigidbodyは不安定な挙動をとる(条件がわかる方いたら教えてください)という結果になりました。

両方Is Triggerの場合

今度は両方Is Triggerにチェックが入っていた場合どうなるのかをチェックしてみました。

結果は↓のようになりました。

ちゃんと検知できていますが、動いていない状態になるとStayの呼び出しが止まっている事がわかります。

動かなくなったらピタッと止まっています。ちなみに、Rigidbody有無両方やってみましたが同じ結果になりました。

さらに、普通にIsTrigger有り無しでやっても、普通にCollisionでも止まったらStayは呼ばれなかったので、止まっているとStayは呼ばれないみたいです。

スリープ状態でオブジェクトが消失した場合

今度はStayが呼ばれなくなった瞬間に衝突しているオブジェクトを消した場合どうなるのかをやってみます。

↓のようになりました。

Exitが呼ばれていません。2Dも3Dも同じ結果になりました。

どうやらスリープモードの状態で衝突しているオブジェクトを非アクティブなどにして消失させるとExitが呼ばれないようです。

色々調べていたら、スリープモードの際に床を消すと、Rigidbodyがスリープなので球が空中に浮く的な資料が出てきたんですが、そんなことはありませんでした。

が、Exitが呼ばれませんでした。

Is Triggerの場合も調べてみましたが同じ結果になりました。

さらに、一瞬でtransformを遥か彼方にやった場合も検証しましたが、これは検知されました。

また、Scaleを0にした時も検証してみましたが、これは呼ばれず、コライダーをオフにした場合は呼ばれました。消失といっても、いろいろ種類があるようです。

よってExitが取れないのはオブジェクトの非アクティブおよびDestroy、それとScaleが0になった時みたいです。

また、非アクティブおよびDestroyはスリープ中でしたが、Scaleを0にするとスリープなど関係なしに呼ばれないみたいです

<まとめ>

OnCollision、OnTriggerが呼ばれない時は以下のような場合みたいです。

OnCollision、OnTriggerが呼ばれない場合
  1. 何らかの凡ミスをしている(スペルミス、Rigidbodyの有無,2Dと3Dの違いなど)
  2. 全てKinematicの設定になっている(OnCollision × , OnTrigger ○)
  3. Enterした次のフレームでExitするとStayは呼ばれない
  4. スピードが早すぎてOnTriggerの判定に当たっていない(OnCollisionはフレーム間の途中の過程を考慮するがOnTriggerは考慮されない)
  5. Rigidbody有りが止まっていてRigidbodyが無しが動いている(Rigidbody2Dの場合は検知される)(3Dの方は何故かたまに検知する。原因は不明)
  6. 止まっている場合はOnCollisionStayもOnTriggerStayの呼ばれない
  7. スリープ状態のRigidbodyに接触しているオブジェクトが消失(SetActive(false),Destroyなど)した場合Exitは呼ばれない
  8. 当たり判定のどちらかのScaleを0にした
Topic

コライダーのExitの判定はコライダーの大きさぴったりではなく

0.02000123262405406999999・・・

の余白が存在する(スケール1の場合)

深く調べるとまだありそうですし、不明な箇所もあるので何か情報をお持ちの方いらっしゃいましたら教えていただけるとありがたいです。