寫在前面的話,前兩天有個朋友在QQ上問我 如何獲取主角面朝方向一定區域中的敵人對象。這個命題看似簡單,其實里面蘊含了很多數學方面的東西。今天剛好有時間我就徹底的把這個疑問寫在博客中。希望可以幫助到他。
在上代碼之前請大家跟我先做幾個簡單的練習題,角度向量的計算一定要學會,不然后面的東西會很難懂。
1.已知3D坐標,和一個旋轉角度,以及一段距離,求目標點的3D坐標。
已知當前點為Target,目標點沿着Target的Y軸旋轉30度,沿着Target的X軸延伸10米求目標點的3D坐標?
using System.Collections; public class Test : MonoBehaviour { public Transform Target; void LateUpdate () { Quaternion rotation = Quaternion.Euler(0f,30f,0f) * Target.rotation; Vector3 newPos = rotation * new Vector3(10f,0f,0f); Debug.DrawLine(newPos,Vector3.zero,Color.red); Debug.Log("newpos " + newPos +" nowpos " + Target.position + " distance " + Vector3.Distance(newPos,Target.position)); } }
輸出結果 :新坐標 (8.7, 0.0, -5.0) 當前坐標 (0.0, 0.0, 0.0)兩點之間的距離 10。
2.已知3D模型的角度求它的向量。
已知3D模型Target,Y軸旋轉30度后向前平移。
using UnityEngine; using System.Collections; public class Test : MonoBehaviour { public Transform Target; void LateUpdate () { if(Input.GetMouseButton(0)) { Quaternion rotation = Quaternion.Euler(0f,30f,0f) * Target.rotation; Vector3 newPos = rotation * Vector3.forward; Target.Translate(newPos.x,newPos.y,newPos.z); } } }
3.已知一個目標點,讓模型朝着這個目標點移動。
Target.transform.LookAt(new Vector3 (100f,200f,300f)); Target.Translate(Vector3.forward);
這里我要說的就是Vector3.forward ,它等價與 new Vector3(0,0,1);它並不是一個坐標,它是一個標准向量,方向是沿着Z軸向前。這樣平移一次的距離就是1米, 如果Vector3.forward * 100那么一次平移的距離就是100米。
在看看下面這段代碼
這是一個比較簡單的例子,大家應該都能看明白。
Vector3 vecn = (TargetCube.position - Target.position).normalized; Target.Translate(vecn *0.1f);
用向量減去一個向量求出它們的差值,normalized 是格式化向量,意思是把它們之間向量格式化到1米內。這樣就可以更加精確的計算一次平移的距離了 vecn *0.1f 就標示一次平移1分米,蛤蛤。
向量不僅可以進行X Y Z軸的移動,同樣可以進行旋轉 ,下面這段代碼就是讓向量沿着Y軸旋轉30度。
Vector3 vecn = (TargetCube.position - Target.position).normalized; vecn = Quaternion.Euler(0f,30f,0f) * vecn; Target.Translate(vecn *0.1f);
如果上述三道簡單的練習題 你都能了然於心的話,那么本文最大的難題我相信也不會是什么難事,繼續閱讀吧。
假設我們需要計算主角面前5米內所有的對象時。以主角為圓心計算面前5米外的一個點,為了讓大家看清楚我現將這條線繪制出來。
private float distance = 5f; void Update () { Quaternion r= transform.rotation; Vector3 f0 = (transform.position + (r *Vector3.forward) * distance); Debug.DrawLine(transform.position,f0,Color.red); }
如下圖所,我們已經將這兩個點計算出來了。此時你可以動態的編輯主角Y軸的坐標,這個點永遠都是沿着主角當前角度面前5米以外的點。
接下來,我們需要計算主角面前的一個發散性的角度。假設主角看到的是向左30度,向右30度在這個區域。
void Update () { Quaternion r= transform.rotation; Vector3 f0 = (transform.position + (r *Vector3.forward) * distance); Debug.DrawLine(transform.position,f0,Color.red); Quaternion r0= Quaternion.Euler(transform.rotation.eulerAngles.x,transform.rotation.eulerAngles.y - 30f,transform.rotation.eulerAngles.z); Quaternion r1= Quaternion.Euler(transform.rotation.eulerAngles.x,transform.rotation.eulerAngles.y + 30f,transform.rotation.eulerAngles.z); Vector3 f1 = (transform.position + (r0 *Vector3.forward) * distance); Vector3 f2 = (transform.position + (r1 *Vector3.forward) * distance); Debug.DrawLine(transform.position,f1,Color.red); Debug.DrawLine(transform.position,f2,Color.red); Debug.DrawLine(f0,f1,Color.red); Debug.DrawLine
}
如下圖所示,這時主角面前的區域就計算出來了。看起來就是兩個三角形之間的區域。
最后就是簡單的套用公式,計算一個點是否在三角形內,在本文中就是計算敵人的點是否在面前的這兩個三角形內。
using System.Collections; public class MyTest : MonoBehaviour { public Transform cube; private float distance = 5f; void Update () { Quaternion r= transform.rotation; Vector3 f0 = (transform.position + (r *Vector3.forward) * distance); Debug.DrawLine(transform.position,f0,Color.red); Quaternion r0= Quaternion.Euler(transform.rotation.eulerAngles.x,transform.rotation.eulerAngles.y - 30f,transform.rotation.eulerAngles.z); Quaternion r1= Quaternion.Euler(transform.rotation.eulerAngles.x,transform.rotation.eulerAngles.y + 30f,transform.rotation.eulerAngles.z); Vector3 f1 = (transform.position + (r0 *Vector3.forward) * distance); Vector3 f2 = (transform.position + (r1 *Vector3.forward) * distance); Debug.DrawLine(transform.position,f1,Color.red); Debug.DrawLine(transform.position,f2,Color.red); Debug.DrawLine(f0,f1,Color.red); Debug.DrawLine(f0,f2,Color.red); Vector3 point = cube.position; if(isINTriangle(point,transform.position,f1,f0) || isINTriangle(point,transform.position,f2,f0) ) { Debug.Log("cube in this !!!"); }else { Debug.Log("cube not in this !!!"); } } private float triangleArea(float v0x,float v0y,float v1x,float v1y,float v2x,float v2y) { return Mathf.Abs((v0x * v1y + v1x * v2y + v2x * v0y - v1x * v0y - v2x * v1y - v0x * v2y) / 2f); } bool isINTriangle(Vector3 point,Vector3 v0,Vector3 v1,Vector3 v2) { float x = point.x; float y = point.z; float v0x = v0.x; float v0y = v0.z; float v1x = v1.x; float v1y = v1.z; float v2x = v2.x; float v2y = v2.z; float t = triangleArea(v0x,v0y,v1x,v1y,v2x,v2y); float a = triangleArea(v0x,v0y,v1x,v1y,x,y) + triangleArea(v0x,v0y,x,y,v2x,v2y) + triangleArea(x,y,v1x,v1y,v2x,v2y); if (Mathf.Abs(t - a) <= 0.01f) { return true; }else { return false; } } }
如下圖所示,如果箱子對象是主角的視野中就會檢測到。
注意,上圖中我的視野選擇了兩個三角形,如果你需要視野目標點是橢圓形的話,那么可以多設置一些三角形。但是這樣就會非常消耗效率,我覺得這里完全可以使用1個三角形,,只是正對的目標點會出現一些偏差,影響其實並不會很大。如下圖所示
代碼簡單的修改一下即可。
public class MyTest : MonoBehaviour { public Transform cube; private float distance = 5f; void Update () { Quaternion r= transform.rotation; Vector3 f0 = (transform.position + (r *Vector3.forward) * distance); Debug.DrawLine(transform.position,f0,Color.red); Quaternion r0= Quaternion.Euler(transform.rotation.eulerAngles.x,transform.rotation.eulerAngles.y - 30f,transform.rotation.eulerAngles.z); Quaternion r1= Quaternion.Euler(transform.rotation.eulerAngles.x,transform.rotation.eulerAngles.y + 30f,transform.rotation.eulerAngles.z); Vector3 f1 = (transform.position + (r0 *Vector3.forward) * distance); Vector3 f2 = (transform.position + (r1 *Vector3.forward) * distance); Debug.DrawLine(transform.position,f1,Color.red); Debug.DrawLine(transform.position,f2,Color.red); Debug.DrawLine(f1,f2,Color.red); Vector3 point = cube.position; if(isINTriangle(point,transform.position,f1,f2)) { Debug.Log("cube in this !!!"); }else { Debug.Log("cube not in this !!!"); } } private float triangleArea(float v0x,float v0y,float v1x,float v1y,float v2x,float v2y) { return Mathf.Abs((v0x * v1y + v1x * v2y + v2x * v0y - v1x * v0y - v2x * v1y - v0x * v2y) / 2f); } bool isINTriangle(Vector3 point,Vector3 v0,Vector3 v1,Vector3 v2) { float x = point.x; float y = point.z; float v0x = v0.x; float v0y = v0.z; float v1x = v1.x; float v1y = v1.z; float v2x = v2.x; float v2y = v2.z; float t = triangleArea(v0x,v0y,v1x,v1y,v2x,v2y); float a = triangleArea(v0x,v0y,v1x,v1y,x,y) + triangleArea(v0x,v0y,x,y,v2x,v2y) + triangleArea(x,y,v1x,v1y,v2x,v2y); if (Mathf.Abs(t - a) <= 0.01f) { return true; }else { return false; } } }
上面我們介紹了三角形判斷,當然也可以通過矩形來判斷是否相交。。
public class MyTest : MonoBehaviour { public Transform cube; private float distance = 5f; void Update () { Quaternion r= transform.rotation; Vector3 left = (transform.position + (r *Vector3.left) * distance); Debug.DrawLine(transform.position,left,Color.red); Vector3 right = (transform.position + (r *Vector3.right) * distance); Debug.DrawLine(transform.position,right,Color.red); Vector3 leftEnd = (left + (r *Vector3.forward) * distance); Debug.DrawLine(left,leftEnd,Color.red); Vector3 rightEnd = (right + (r *Vector3.forward) * distance); Debug.DrawLine(right,rightEnd,Color.red); Debug.DrawLine(leftEnd,rightEnd,Color.red); Vector3 point = cube.position; if(isINRect(point,leftEnd,rightEnd,right,left)) { Debug.Log("cube in this !!!"); }else { Debug.Log("cube not in this !!!"); } } private float Multiply(float p1x , float p1y, float p2x,float p2y, float p0x,float p0y) { return ((p1x - p0x) * (p2y - p0y) - (p2x - p0x) * (p1y - p0y)); } bool isINRect(Vector3 point,Vector3 v0,Vector3 v1,Vector3 v2,Vector3 v3) { float x = point.x; float y = point.z; float v0x = v0.x; float v0y = v0.z; float v1x = v1.x; float v1y = v1.z; float v2x = v2.x; float v2y = v2.z; float v3x = v3.x; float v3y = v3.z; if (Multiply(x,y, v0x,v0y, v1x,v1y) * Multiply(x,y, v3x,v3y, v2x,v2y) <= 0 && Multiply(x,y, v3x,v3y, v0x,v0y) * Multiply(x,y, v2x,v2y, v1x,v1y) <= 0) return true; else return false; } }
這里我以角色左右個30度。 這樣就可以根據兩個模型的距離以及角度來判斷了。。
public class NewBehaviourScript : MonoBehaviour { public Transform target; void Update() { float distance = Vector3.Distance(target.position,transform.position); Quaternion right = transform.rotation * Quaternion.AngleAxis(30,Vector3.up); Quaternion left = transform.rotation * Quaternion.AngleAxis(30,Vector3.down); Vector3 n = transform.position + (Vector3.forward * distance); Vector3 leftPoint = left * n ; Vector3 rightPoint = right *n ; Debug.DrawLine(transform.position,leftPoint,Color.red); Debug.DrawLine(transform.position,rightPoint,Color.red); Debug.DrawLine(rightPoint,leftPoint,Color.red); } }
文章地址:http://www.xuanyusong.com/archives/1977