OnCollisionEnter、OnTriggerEnterが呼ばれない問題【検証】

<衝突、侵入を検知するメソッド>

MonoBehaviourを継承することによって特別な機能を持つ衝突、侵入を検知するメソッド達があります。

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

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

3D侵入検知
OnTriggerEnter, OnTriggerStay, OnTriggerExit

2D侵入検知
OnTriggerEnter2D, OnTriggerStay2D, OnTriggerExit2D

とこんな感じです。

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

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

<呼ばれない時、まず凡ミスがないか確認しよう>

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

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

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

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

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

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

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

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

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にチェックが入っていなかったりしませんか?

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

box-collider-is-trigger

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

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

inspector_rigidbody

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

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

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

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

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

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

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

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

<呼ばれない場合を検証してみた>

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

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

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

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

bound sphere

そして、この球に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("イグジット");
     }
}

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

bound check

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

enter stay exit

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

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

enter exit

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

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

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

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

bound check more spped

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

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

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

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

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

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

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

trigger judge

要は、物体が速すぎる場合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("コリジョンイグジット");
         }
 }

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

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

collision enter exit

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

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

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

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

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

<いやそうじゃない>

いやいやいやいや、そんな当たり前のロジック的にこうなるって話ではない。俺が出会ったのはそんなのじゃぁなかったはずだ。

例えばEnterが呼ばれていないのにStayがいきなり呼ばれるとか、Exitが呼ばれないとかそういう類、エゲツない奴に出会ったんだよ。

再現性のかなり低い、たまたまの一瞬だけ起きるような事象があるはず!

検証を続けます。

処理をバリ重くした場合

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

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

many cube

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

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

↓結果です。

enter exit

なん・・・・だと・・・っ!?た、正しい!(いい事だけど)

Collisionは?

collision-enter

た、正しい!

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

ジャストExitをキメた場合

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

やってみます。

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

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

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

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

1.02000123262405406999999・・・ ←Stay
1.02000123262405407 ←Exit

となりました。

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

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

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

↓結果です。

just exit

た、正しい。

というかtransfom直いじりしてもExitを検知している。優秀

Enterも正しく動きました。

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

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

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

んー。現在まだ調査中です。

タイトルとURLをコピーしました