13
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

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

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

OnControllerHit
OnControllerColliderHit はキャラクターコントローラーが移動中にコライダーに衝突した際に、呼び出されます。
[Unity-スクリプトリファレンス: CharacterController](https://docs.unity3d.com/jp/540/ScriptReference/CharacterController.html)

どうやら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-マニュアル:イベント関数の実行順]
(https://docs.unity3d.com/jp/540/Manual/ExecutionOrder.html)

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

OnControllerHitのコールバック

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

RigidBodyのColliderの計算

OnCollisionEnterのコールバック

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

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

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

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

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

13
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?