衝突"していない"ことを検知するメソッドはない
Unityにはコライダーの衝突を検知するものとして OnCollision と OnTrigger というメソッドがあり、それぞれ以下の3つの種類があります。
- OnCollision系
関数 | 内容 |
---|---|
OnCollisionEnter | 他のコライダーに触れ始めたとき |
OnCollisionStay | 他のコライダーに触れている間 |
OnCollisionExit | 他のコライダーに触れるのをやめたとき |
- OnTrigger系
関数 | 内容 |
---|---|
OnTriggerEnter | 他のコライダーに触れ始めたとき |
OnTriggerStay | 他のコライダーに触れている間 |
OnTriggerExit | 他のコライダーに触れるのをやめたとき |
しかしながら、「衝突した」「衝突している」ということは検知できても「衝突していない」ことを直接検知することはできません。
そこで本記事では「衝突していない」ことを検知する方法について紹介します。
方法① On〇〇Enter と On〇〇Exit を組み合わせる
単純でわかりやすいし、まずこれで困らないと思います。
Enterメソッドで衝突しているかどうかのフラグを立て、Exitメソッドでそのフラグを下ろすやり方です。
using UnityEngine;
public class DetectNoCollision_A() : MonoBehaviour
{
private bool isCollisionDetected = false; // 衝突判定用のフラグ
private void OnTriggerEnter(Collider other)
{
isCollisionDetected = true;
}
private void OnTriggerExit(Collider other)
{
isCollisionDetected = false;
}
private void Update()
{
if (isCollisionDetected)
{
Debug.Log("衝突してるよ");
}
else
{
Debug.Log("衝突してないよ");
}
}
}
この方法で困る場合
先ほど「まずこれで困らない」と述べましたが、じゃあどんな場面で困るのかというと
- 衝突を検知する物体が生成されたときにすでに衝突していて
- かつそれを FixedUpdate で検知する場合
です。そんな場面あるのかよというようなレアケースではありますが、実際にあったためその対策としてこの記事を書いておきます。
ここでちょっとした実験をしてみます。
半透明なキューブがあり、その中に別の緑のキューブが入っています。半透明なキューブは isTrigger を有効にしてあり、緑のキューブには Rigidbody をつけています。(落ちないように isKinematic を有効化)
この状態で緑のキューブに先ほどの DetectNoCollision_A をアタッチし、シーンを再生します。
ただし Update は FixedUpdate に変えておきます。
すると...
シーンを再生したあとの最初の1回だけ衝突していない判定になりました。
なんでこんなことになる?
Unity公式のイベント関数の実行順序のページを見るとわかります。
https://docs.unity3d.com/ja/2022.3/Manual/ExecutionOrder.html
一部抜粋した物理関連の処理はこのようになっています。
つまり FixedUpdate は 衝突検知系のメソッドよりも先に実行されるため、FixedUpdate の最初の1回だけはフラグを立てる前に衝突が判定され、衝突していない扱いになってしまうのです。
方法② yield WaitForFixedUpdate を使う
先ほどの図をもう一度見てもらうと、 Phisics の処理の最後に yield WaitForFixedUpdate というものがあります。
これをコルーチン内で使うと衝突判定の後まで待ってもらえます。
衝突判定が終わるまで待って、その時点で衝突したフラグが立っていなければ衝突したことにする、という方式です。
using System.Collections;
using UnityEngine;
public class DetectNoCollision_B : MonoBehaviour
{
private bool isCollisionDetected = false; // 衝突判定用のフラグ
private void FixedUpdate()
{
StartCoroutine(CollisionDetect());
}
IEnumerator CollisionDetect()
{
isCollisionDetected = false;
yield return new WaitForFixedUpdate();
if (!isCollisionDetected)
{
Debug.Log("衝突してないよ");
}
}
private void OnTriggerStay(Collider other)
{
isCollisionDetected = true;
Debug.Log("衝突してるよ");
}
}
これを同じ状況で実験してみると...
無事最初の1回から衝突している判定になりました!