距離を見るヒット処理を試してみる

#はじめに
純粋に計算式からヒット判定を求めてみたら
RaycastとかColliderとの負荷の違いはどうなんだろう。
ということでやってみました。

Unity2017.2.0f3で動作確認しています

#参考
[点と線の距離を求める](http://www.sousakuba.com/Programming/gs_dot_line_distance.html)
※ソースもそのまま流用できそうです。

説明はサイトを確認したほうが良いと思いますが、一応そのまま解説

直線とそれぞれのObjectの距離を求める場合。
直線の始点＞終点と、直線の始点＞Objectの二つの線と捉えると、平行四辺形の形になります。
ここで、平行四辺形の高さが、今回求めたい距離そのものとみなせます。

平行四辺形の高さは面積がわかれば以下の式で求まります。

高さｈ＝面積/底辺の長さ

ということで直線距離A～Bと、その近くにあるPがあるとして
ベクトルABとベクトルAPの外積で面積Dが出てきます。
直線A～Bの長さが底辺の長さL
ということで直線とPとの距離Hは

H=D/L

あとはそのままScriptに落とし込んでみました。

#実装
下記Scriptを準備します。
機能は、二点、マウスの左クリックは計算で求め、右クリックは`RaycastAll`で計算
それぞれカウンターでHitが1000回あつまるまでの時間を計測します。
※Scriptそのものは一番下参照

※Enemy（名前は何でも良いけど）Objectを作り`EnemyController`と`SphereCollider`をAddしておきます。
　EnemyはPrefabとし、`PlayerScript`の`EnemyObj`にアタッチしておいてください。

![player.png](https://qiita-image-store.s3.amazonaws.com/0/212169/ded927be-4bcb-8d2a-139a-649b547dc2e4.png)

![enemy.png](https://qiita-image-store.s3.amazonaws.com/0/212169/f4ead52b-8758-74c6-b777-8a7e2591c100.png)

#結果
1000個のObjectを計算するのにどれくらいの時間がかかるのか求めました。
![editor.png](https://qiita-image-store.s3.amazonaws.com/0/212169/a69c1d0d-0ab8-42b0-d239-01eeec67cae5.png)

動かします。
直線状にEnemyObjectが並んでいます。　さあ計算してみましょう！！
![kekka.png](https://qiita-image-store.s3.amazonaws.com/0/212169/ac889ab6-202c-af2b-d578-30aef863f2f9.png)
計算にかかった時間が表示されました！！
んー同じ？・・・
時間の計測方法に問題がありそうですけど、一旦置いておきます。

![Profile.png](https://qiita-image-store.s3.amazonaws.com/0/212169/26d8a880-a281-c615-2bb1-626c840be18d.png)
Profileを見てみると変化が出ていました！

`RaycastAll`はPhysicsが動いています。
Scriptだけだと計算式の方が処理負荷が重そうですが、Physicsも含めると計算式の方が軽そうです。

単純にこれだけでどうこうという事はありませんが、
まるっとヒット判定する場合は効果があるかもしれません。


#Scriptサンプル
距離の算出計算
DistancePtoL.cs

    public class DistancePtoL  {
        public float Distance(Vector3 Point,Vector3 Start,Vector3 End)
        {
            Vector3 StartToPoint, StartToEnd;
            StartToPoint = Point - Start;// スタート地点からPointまでのベクトル
            StartToEnd = End - Start;//スタート地点からEndまでのベクトル

            //StartToEndとStartToPointを外積して求められたベクトルの長さが、平行四辺形の面積になる
            float Area = Vector3.Magnitude( Vector3.Cross(StartToEnd, StartToPoint));

            //StartToEndの距離
            float Line = Vector3.Distance(Start, End);

            return Area / Line;//距離の算出
        }
    }

本体はこんな感じ
PlayerScript.cs

    [System.Serializable]
    public class EnemyCore
    {
        public GameObject EnemyBody;
        public EnemyController Controller;
    }

    //GameController
    public class PlayerScript : MonoBehaviour {

        [SerializeField]
        private GameObject EnemyObj;//敵の基本Prefab

        [SerializeField]
        private List<EnemyCore> EnemyList = new List<EnemyCore>();//管理する敵リスト

        [SerializeField]
        private int EnemyCount = 1000;//作る仮想敵の数

        [SerializeField]
        private Vector3 _EnemyRange = new Vector3(5,5,1000);


        void Start()
        {
            //敵量産
            for (var i=0;i< EnemyCount; i++)
            {
                EnemyCore _Enemy = new EnemyCore();
                var EnemyPosition = new Vector3(0f,0f,UnityEngine.Random.Range(1f, _EnemyRange.z));
                _Enemy.EnemyBody = Object.Instantiate(EnemyObj, EnemyPosition, Quaternion.identity) as GameObject;
                _Enemy.EnemyBody.name = "Enemy[" + i + "]";
                _Enemy.Controller = _Enemy.EnemyBody.GetComponent<EnemyController>();
                _Enemy.Controller.PL = this.transform.GetComponent<PlayerScript>();
                EnemyList.Add(_Enemy);
            }
        }

        void Update () {
            if (Input.GetMouseButtonUp(0))
            {
                Debug.Log("PointToLine Time");
                //マウスクリック
                _HitCount = 0;
                _Timer = 0;
                StartCoroutine(TimerCount());
                HitTestUpdate(2);
            }

            if (Input.GetMouseButtonUp(1))
            {
                Debug.Log("RayCast Time");
                //マウスクリック
                _HitCount = 0;
                _Timer = 0;
                StartCoroutine(TimerCount());
                HitTestUpdate(1);
            }



        }

        private int _HitCount = 0;
        private float _Timer = 0;
        public void HitCount()
        {
            _HitCount++;
        }

        IEnumerator TimerCount()
        {
            while (true)
            {
                _Timer += Time.deltaTime;

                if (_HitCount >= 999)
                {
                    Debug.Log("All Hit Timer :" + _Timer);
                    yield break;
                }

                yield return null;
            }
        }


        void HitTestUpdate(int mode)
        {
            Vector3 Start = this.transform.position;
            Vector3 End = new Vector3(this.transform.position.x, this.transform.position.y, this.transform.position.z + 1000);
            RaycastHit[] hits;

            switch (mode) {
                case 1:
                    hits = Physics.RaycastAll(Start, Vector3.forward, 1000);
                    for(var i=0;i < hits.Length; i++)
                    {
                        RaycastHit hit = hits[i];
                        HitCount();
                    }


                    break;
                case 2:
                    for (var i = 0; i < EnemyList.Count; i++)
                    {
                        EnemyList[i].Controller.HitUpdate(Start, End);
                    }
                    break;
            }
        }

    }

最後に画面に並べられるスクリプト側
EnemyController.cs

    public class EnemyController : MonoBehaviour {

        [SerializeField]
        public DistancePtoL HitCheck;
        public PlayerScript PL;

        void Start()
        {
            HitCheck = new DistancePtoL();
        }

        public void HitUpdate(Vector3 Start,Vector3 End)
        {
            var Dist = HitCheck.Distance(this.transform.position,Start,End);
            if (Dist < 10f)
            {
                PL.HitCount();
            }
        }
    }

#追記
コメントで教えていただいたのでProfileを正しく計測してみました。
・参考
[Unityでスクリプトの一部分の処理をProfilerに表示してもらう](http://naokirin.hatenablog.com/entry/2016/01/11/025615)

修正箇所はこちら `PlayerScript.cs`の中になります。
ちなみに'Unity2017.2.0f3'では、`Profiler.BeginSample`は`UnityEngine.Profiling`に移動したようです。

        switch (mode) {
            case 1:
                UnityEngine.Profiling.Profiler.BeginSample("RaycastAllProcess");
                hits = Physics.RaycastAll(Start, Vector3.forward, 1000);
                for(var i=0;i < hits.Length; i++)
                {
                    //RaycastHit hit = hits[i];
                    HitCount();
                }
                UnityEngine.Profiling.Profiler.EndSample();

                break;
            case 2:
                UnityEngine.Profiling.Profiler.BeginSample("PointToLine Process");
                for (var i = 0; i < EnemyList.Count; i++)
                {
                    EnemyList[i].Controller.HitUpdate(Start, End);
                }
                UnityEngine.Profiling.Profiler.EndSample();
                break;
        }


結果はこんな感じになりました。
![profilekekka.png](https://qiita-image-store.s3.amazonaws.com/0/212169/af692310-0fd5-0e17-57ee-8b2ef5feaa4b.png)
Timeの数値が結果になります。
結構違いますね。