#はじめに
純粋に計算式からヒット判定を求めてみたら
RaycastとかColliderとの負荷の違いはどうなんだろう。
ということでやってみました。
Unity2017.2.0f3で動作確認しています
#参考
点と線の距離を求める
※ソースもそのまま流用できそうです。
説明はサイトを確認したほうが良いと思いますが、一応そのまま解説
直線とそれぞれのObjectの距離を求める場合。
直線の始点>終点と、直線の始点>Objectの二つの線と捉えると、平行四辺形の形になります。
ここで、平行四辺形の高さが、今回求めたい距離そのものとみなせます。
平行四辺形の高さは面積がわかれば以下の式で求まります。
高さh=面積/底辺の長さ
ということで直線距離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
にアタッチしておいてください。
#結果
1000個のObjectを計算するのにどれくらいの時間がかかるのか求めました。
動かします。
直線状にEnemyObjectが並んでいます。 さあ計算してみましょう!!
計算にかかった時間が表示されました!!
んー同じ?・・・
時間の計測方法に問題がありそうですけど、一旦置いておきます。
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に表示してもらう
修正箇所はこちら 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;
}