Unity3D
Unity

CharacterControllerでOnCollisionEnterが呼ばれない件について

はじめに

CharacterControllerは接地判定や、移動が簡単にできるのでとても便利です。
しかし、衝突の判定を行うとき、コールバックが行われないことがあったので調べてみました。

実験

準備

CharacterControllerにはOnControllerColliderHitというコールバック関数があります。


OnControllerHit

OnControllerColliderHit はキャラクターコントローラーが移動中にコライダーに衝突した際に、呼び出されます。


Unity-スクリプトリファレンス: CharacterController

どうやらCharacterControllerにもOnCollisionEnterが実装されているようで、
衝突したときにはどちらが呼ばれるのでしょうか?

PlayerCtrl.cs
using UnityEngine;

public class PlayerCtrl : MonoBehaviour {

    private CharacterController cc;
    private float gravity = 100.0f;

    void Start()
    {
        cc = GetComponent<CharacterController>();
    }

    void FixedUpdate()
    {
        // 重力を計算
        var moveDirection = new Vector3(0f, -gravity * Time.fixedDeltaTime, 0f);

        // CharacterControllerに渡す
        cc.Move(moveDirection * Time.fixedDeltaTime);

    }

    private void OnCollisionEnter(Collision collision)
    {
        // 衝突した対象の名前を取得
        var colName = collision.gameObject.name;

        if (colName == "Enemy")
        {
            Debug.Log("OnCollisionEnter(Player)");
        }

    }

    private void OnControllerColliderHit(ControllerColliderHit hit)
    {
        // 衝突した対象の名前を取得
        var colName = hit.gameObject.name;

        if (colName == "Enemy")
        {
            Debug.Log("OnControllerHit(Player)");
        }
    }
}
EnemyCtrl.cs
using UnityEngine;

public class EnemyCtrl : MonoBehaviour {

    private void OnCollisionEnter(Collision collision)
    {
        // 衝突した対象の名前を取得
        var colName = collision.gameObject.name;

        if(colName == "Player")
        {
            Debug.Log("OnCollisionEnter(Enemy)");
        }
    }
}

Playerをぶつけてみる

スクリーンショット 2017-10-10 10.37.10.png

赤がPlayer、青がEnemyです。
PlayerにはCharacterControllerとPlayerCtrlのみアタッチされています。
EnemyにはRigidBodyとBoxCollider、EnemyCtrlがアタッチされています。
もちろんIsTriggerはfalseです。

PlayerをCharacterControllerで落下させ、Enemyにぶつけてみると...

スクリーンショット 2017-10-10 10.38.51.png

上記のようにOnControllerHitが接触しているフレームの間呼び出され、
PlayerとEnemyのOnCollisionEnterは呼び出されていません。

Enemyをぶつけてみる

スクリーンショット 2017-10-16 0.15.27.png

次はEnemyをぶつけてみようと思います。
IsGravityをTrueにしてPlayerの上に落下させます。

スクリーンショット 2017-10-16 0.16.13.png

するとPlayerとEnemyのOnCollisionEnterが呼び出されました。
こちらではPlayerのOnControllerHitは呼び出されず、一度しか呼び出されません。

推測

実行順序

ここでUnityのイベント関数の実行順を確認すると、
FixedUpdateの次にInternal physics Updateという処理があり、
ここでRigidBodyの計算がされるようです。
おそらくここでCharacterControllerの処理も計算されているのではないでしょうか?

Unity-マニュアル:イベント関数の実行順

おそらく計算の順番が
CharacterControllerのColliderの計算

OnControllerHitのコールバック

めり込まないよう位置の修正

RigidBodyのColliderの計算

OnCollisionEnterのコールバック

めり込まないよう位置の修正

つまり、Playerをぶつけたときは、
OnControllerHitのコールバック後に位置が修正されているため、
RigidBodyの計算の際、衝突していないことになる(はず)
Enemyをぶつけたときは、
RigidBodyで移動が計算されるので、もちろんOnControllerHitは呼ばれず、
OnCollisionEnterのコールバックが呼ばれる。

結論

対処法としては、
・そもそもCharacterControllerを使わない
・CharacterControllerと併用してIsTriggerのコライダを別に用意し、OnCollisionTriggerを呼ぶ
・CharacterControllerのキャラが移動した時のCollisionのコールバックしか呼ばないようにする

色々と調べたのですが、CharacterControllerの処理タイミングなどについての記事は見つけられませんでしたので、検証結果として残したいと思います。

間違っている点などがありましたらコメントをお願いいたします。