视频讲解
一、背景
在游戏开发中,我们经常会遇到如何计算玩家和其它玩家的位置、视野关系;比如我前面有哪些玩家、我的后面有哪些玩家、在我的视野范围内有哪些玩家。这些都是平时会遇到的,那么怎么计算玩家之间的位置关系、视野问题就是本节内容的一个记录。
二、如何计算玩家之间的位置关系
2.1、位置关系
在计算玩家与玩家的位置关系之前,需要线确定计算的玩家位置关系,有以下几个:
- 前方【敌人在我的前方】
- 后面【敌人在我的后面】
- 左边【敌人在我的左边】
- 右边【敌人在我的右边】
上面只是基本的说明4个方位,但是远远不止这些;左前方、右前方、右后方等等的方位组合都是位置关系;甚至可以划分的更细一些也是可以的。
如下图说明:
动画中的A显示出了A的前方和右方向,通过旋转可以看出A的的前方和右方的实际指向是不一样的。当然这里也可以显示出更多的方向箭头,这里只显示了前方和右方。
2.2、确定要计算的方向
我们所说的要计算我和某人的方向,这里是有一个方向的选择前提。比如我要确定我和你的位置关系,首先要进行以下的方向确定:
- 是从我的什么方向作为参考方向,是我的前面、后面、左边、右边;这里需要选择;比如选择我的前面,判断你是不是在我的前面。
- 要计算出我到玩家的方向;要判断两个玩家的位置关系,这里是需要计算出我到这个玩家的方向是什么;通过玩家的位置减去我的位置就可以计算我到玩家的方向;
如下图:
从上图中的玩家A到敌人B的连接线,也就是向量AB就是A到B的方向。用AB=B-A
2.3、计算公式
上面我们准备好了要计算的相关参数,那么计算公式是什么呢?
计算公式就是 【点乘】
a.b=|a||b|cos角度
上面是点乘的计算公式,我们先来了解一下这几个参数代表的是什么:
- a在这里代表上文中玩家A的正前方,也就是transformA.forward
- b在这里代表A到B的方向,也就是transformB.position - transformA.position
- |a|就表示a向量的长度,这里就是transformA.forward.magnitude
- |b|就表示b向量的长度,这里就是aToB.magnitude
- 角度就表示向量A和向量B形成的角度
这里我们可以直接通过
float dotAToB = Vector3.Dot(transformA.forward, aToB);
计算出两个向量的点积结果;这里的结果分为以下几种情况:
- 结果>0:表示它们的方向是基本相同的,也就是基本在同一个方向
- 结果=0:表示玩家A和玩家B当前的位置关系是垂直关系
- 结果<0:表示玩家B在玩家A的后面,方向基本相反
通过上面结果我们可以计算出玩家A和玩家B之间的角度:
float aToBAngel = Mathf.Acos(dotAToB / (aTobLength * aForwardLenght)) * Mathf.Rad2Deg;
我们也可以通过Unity提供的API直接计算出两个向量的角度:
float aToBAngel_ = Vector3.Angle(transformA.forward, aToB);
这里计算的角度结果是一样的;
这的方向、角度都计算出来;就可以根据点积的结果判断玩家在我的什么位置,是在前方还是后方;是不是在我的视野范围内,这里的方向和视野范围要注意以下问题:
- 在我的前后用transform.forward计算,如果判断左右可以用transform.right来计算
- 在我的视野范围内首先要确定是不是在你规定的视野角度,比如你的视野角度是在45-90度之间,看看上面计算的角度在这个范围;另外还要计算你可以看到的视野最远距离,如果不在你的视野距离之内,即使角度满足也是不行的;视野角度和视野距离这两个条件要同时满足。
三、动画演示
下面是基本的动画演示,详细内容可以访问:
四、源码
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class ValakiObjPosDemos : MonoBehaviour
{
[SerializeField] private Transform transformA;
[SerializeField] private Transform transformB;
private string[] forwardTip = new[] {
"前", "后"};
private string[] leftTips = new[] {
"右", "左"};
private float directionLength = 15;
[Header("坐标显示")] [SerializeField] private bool showCoordinate = false;
[Header("显示Plyaer-Enemy原点连接向量")] [SerializeField]
private bool showZeroPoint = false;
[Header("AB方向显示")] [SerializeField] private bool showDirection = false;
[Header("正前方角度显示")] [SerializeField] private bool showForwardAngel = false;
[Header("右方角度显示")] [SerializeField] private bool showRightAngel = false;
[Header("计算公式")] [SerializeField] private bool shorDotFormula = false;
[Header("A到B方向显示")] [SerializeField] private bool showAToBDirection = false;
[Header("计算显示")] [SerializeField] private bool showCalcuDesc = false;
[SerializeField] private Material material;
private Vector3 zeroPoint = Vector3.zero;
[SerializeField] private MeshFilter forwardMeshFilter;
[SerializeField] private MeshFilter rightMeshFilter;
private void OnDrawGizmos()
{
GUI.skin.label.fontSize = 35;
DrawLabel(transformA.position, "A玩", Color.white, new Vector2(200, 70));
DrawLabel(transformB.position, "B玩", Color.white, new Vector2(200, 70));
//计算出a到b方向
Vector3 aToB = transformB.position - transformA.position;
float dotAToB = Vector3.Dot(transformA.forward, aToB);
/*
* 1 > 0 0-90 基本方向相同
* 2 = 0 90 垂直
* 3 < 0 > 90 积本相反
*
*
*/
float aTobLength = aToB.magnitude;
float aForwardLenght = transformA.forward.magnitude;
//前后计算
float aToBAngel = Mathf.Acos(dotAToB / (aTobLength * aForwardLenght)) * Mathf.Rad2Deg;
float aToBAngel_ = Vector3.Angle(transformA.forward, aToB);
//左右计算
float dotRight = Vector3.Dot(transformA.right, aToB);
float aRightLength = Vector3.right.magnitude;
float aRightAngel = Mathf.Acos(dotRight / (aRightLength * aTobLength)) * Mathf.Rad2Deg;
float aRightAngel_ = Vector3.Angle(transformA.right, aToB);
if (showDirection)
{
GUI.skin.label.fontSize = 18;
Debug.DrawLine(transformA.position, transformA.position + transformA.right * directionLength, Color.green);
Debug.DrawLine(transformA.position, transformA.position + transformA.forward * directionLength, Color.blue);
DrawLabel(transformA.position + transformA.forward * directionLength, "Z[正前方]", Color.yellow,
new Vector2(1000, 100));
DrawLabel(transformA.position + transformA.right * directionLength, "X[正右方]", Color.yellow,
new Vector2(1000, 100));
}
if (showAToBDirection)
{
//绘制出偏移的方向
Debug.DrawLine(transformA.position, aToB + transformA.position, Color.magenta);
}
GUI.skin.label.fontSize = 22;
if (showCalcuDesc)
{
CalculateShowTips(dotAToB, aToBAngel, aToBAngel_, dotRight, aRightAngel, aRightAngel_);
}
if (showZeroPoint)
DrawZeroLine();
if (showCoordinate)
ShowCoordenate3();
forwardMeshFilter.mesh = null;
rightMeshFilter.mesh = null;
if (showForwardAngel)
ShowForwardAngel(aToB);
if (showRightAngel)
ShowRightAngel(aToB);
if (shorDotFormula)
ShowDotFormula();
}
private void ShowForwardAngel(Vector3 aToB)
{
Mesh mesh = new Mesh();
Vector3 vertext1 = transformA.position + transformA.forward * 2;
Vector3 vertext2 = transformA.position;
Vector3 vertext3 = transformA.position + aToB * 0.2f;
mesh.vertices = new[]
{
vertext1, vertext2, vertext3, vertext1, vertext2,
vertext3
};
mesh.SetIndices(new[] {
0, 1, 2, 0, 2, 1}, MeshTopology.Triangles, 0);
forwardMeshFilter.sharedMesh = mesh;
}
private void ShowRightAngel(Vector3 aToB)
{
Mesh mesh = new Mesh();
Vector3 vertext1 = transformA.position + transformA.right * 3;
Vector3 vertext2 = transformA.position;
Vector3 vertext3 = transformA.position + aToB * 0.3f;
mesh.vertices = new[]
{
vertext1, vertext2, vertext3, vertext1, vertext2,
vertext3
};
mesh.SetIndices(new[] {
0, 1, 2, 0, 2, 1}, MeshTopology.Triangles, 0);
rightMeshFilter.sharedMesh = mesh;
}
/// <summary>
/// 描述计算
/// </summary>
/// <param name="dotAToB"></param>
/// <param name="aToBAngel"></param>
/// <param name="aToBAngel_"></param>
/// <param name="dotRight"></param>
/// <param name="aRightAngel"></param>
/// <param name="aRightAngel_"></param>
private void CalculateShowTips(float dotAToB, float aToBAngel, float aToBAngel_, float dotRight, float aRightAngel,
float aRightAngel_)
{
string forwardDesc = dotAToB > 0 ? "敌人在前面" : "敌人在后面";
string axb = forwardDesc + "\n" + "forward.AB=" + dotAToB + " forward与AB夹角=" + aToBAngel +
" forward:" +
transformA.forward.ToString();
string rightdDesc = dotRight > 0 ? "敌人在右边" : "敌人在左边";
string rightDesc = rightdDesc + "\n right.AB=" + dotRight + " right与AB夹角=" + aRightAngel +
" right:" +
transformA.right.ToString();
Handles.BeginGUI();
GUI.skin.label.normal.textColor = Color.blue;
GUI.Label(new Rect(0, 0, 1500, 200), axb);
GUI.skin.label.normal.textColor = Color.magenta;
GUI.Label(new Rect(0, 100, 1500, 200), rightDesc);
HandleUtility.Repaint();
Handles.EndGUI();
}
void DrawLabel(Vector3 pos, string text, Color color, Vector2 size)
{
Vector2 pos2D = HandleUtility.WorldToGUIPoint(pos);
Handles.BeginGUI();
GUI.skin.label.normal.textColor = color;
GUI.Label(new Rect(pos2D.x, pos2D.y, size.x, size.y), text);
HandleUtility.Repaint();
Handles.EndGUI();
}
void DrawZeroLine()
{
Debug.DrawLine(Vector3.zero, transformA.position, Color.white);
Debug.DrawLine(Vector3.zero, transformB.position, Color.white);
DrawLabel(Vector3.zero, "O", Color.white, new Vector2(40, 40));
}
private Color zColor = new Color(58 / 255.0f, 122 / 255.0f, 248 / 255.0f, 237 / 255.0f);
private Color xColor = new Color(219 / 255.0f, 62 / 255.0f, 29 / 255.0f, 237 / 255.0f);
private Color yColor = new Color(154 / 255.0f, 243 / 255.0f, 72 / 255.0f, 237 / 255.0f);
void ShowCoordenate3()
{
Color zColor = new Color(58 / 255.0f, 122 / 255.0f, 248 / 255.0f, 237 / 255.0f);
Color xColor = new Color(219 / 255.0f, 62 / 255.0f, 29 / 255.0f, 237 / 255.0f);
Color yColor = new Color(154 / 255.0f, 243 / 255.0f, 72 / 255.0f, 237 / 255.0f);
Debug.DrawLine(Vector3.forward * -150f, Vector3.forward * 150f,
zColor);
Debug.DrawLine(Vector3.right * -150f, Vector3.right * 150f,
xColor);
Debug.DrawLine(Vector3.up * -150f, Vector3.up * 150f, yColor);
}
void ShowDotFormula()
{
Handles.BeginGUI();
string desc = "公式:a.b=|a||b|cos角度";
GUI.skin.label.fontSize = 40;
GUI.skin.label.normal.textColor = Color.magenta;
GUI.Label(new Rect(800, 0, 500, 200), desc);
HandleUtility.Repaint();
Handles.EndGUI();
}
}