向量的几何意义
几何意义上说,向量是有大小和方向的有向线段。
点和向量的关系
“点”有位置,但没有大小和方向,“向量”有大小和方向,但没有位置。所以使用“点”和“向量”的目的完全不同。”点”描述位置,“向量”描述位移。
零向量
零向量非常特殊,因为它是唯一大小为零且没有方向的向量。
负向量
几何解释:向量变负,将得到一个和向量大小相等,方向相反的向量。
向量大小
向量的大小就是向量各分量平方和的平方根
设三维向量A(x,y,z),则A向量的大小为:|A|=√(x^2 +y^2 +z^2)
二维向量同理
Unity中代码实现
Vector3 pos;//向量
// 开平方计算
Mathf.Sqrt(Mathf.Pow(pos.x, 2) + Mathf.Pow(pos.y, 2) + Mathf.Pow(pos.z, 2));
// UNITY现成的API计算
pos.magnitude;
标量与向量的乘法
标量与向量不能相加,但它们可以相乘。结果将得到一个向量。与原向量平行,但长度不同或者方向相反。
同理标量与向量也能相除
标准化向量
对于许多向量,我们只关心向量的方向不在乎向量的大小,如:“我面向的是什么方向?”,在这样的情况下,使用单位向量非常方便,单位向量就是大小为1的向量,单位向量经常也被称作为标准化向量或者法线。
要标准化向量,将向量除以它的大小(模)即可。
Unity中代码实现
Vector3 pos;//向量
// UNITY现成的API计算
pos.normalized;
向量的加法和减法
两个向量的维数相同,那么它们能相加,或者相减。结果向量的维数与原向量相同。向量加减法的记发和标量加减法的记法相同。例如:[x,y,z] + [a,b,c] = [x+a,y+b,z+c]
图像表示
向量点乘
向量点乘就是对应分量乘积的和。其结果是一个标量. [x,y,z] · [a,b,c] = ax+by+cz;
几何解释:
一般来说,点乘结果描述了两个向量的“相似”程度,点乘结果越大,两个向量越相近,也就是两个向量之间的夹角越小
通过反余弦函数 Acos 可以将点积的结果转换为两向量之间的夹角 θ 的角度,取值范围 [0, 180)
案例代码
void Start()
{
Vector3 up = Vector3.up;
Vector3 right = Vector3.right;
float dot = Vector3.Dot(up, right);
Debug.Log("点积结果:" + dot);
float rad = Mathf.Acos(dot);
Debug.Log("反余弦函数获取到的弧度值:" + rad);
float angle = Mathf.Rad2Deg * rad;
Debug.Log("弧度值转换后的角度值:" + angle);
}
点积示例
判断物体的前后关系
using UnityEngine;
public class Test : MonoBehaviour
{
public Transform Cube1;
public Transform Cube2;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Vector3 direction = Cube2.position - Cube1.position;
float dot = Vector3.Dot(direction.normalized, Cube1.forward);
if (dot > 0)
{
Debug.Log("Cube2在Cube1的前方");
}
else if (dot < 0)
{
Debug.Log("Cube2在Cube1后方");
}
else
{
Debug.Log("Cube2和Cube1在相同的位置");
}
}
}
}
判断敌人是否在技能的攻击范围之内
using UnityEngine;
public class Test : MonoBehaviour
{
public Transform Player;//玩家
public Transform Enemy;//敌人
public float AttackDistance = 2;//攻击距离
public float AttackRange = 90;//攻击角度
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
if(Vector3.Distance(Player.position,Enemy.position) <= AttackDistance)
{
//在攻击距离内,继续判断是否在攻击范围内
float dot = Vector3.Dot((Enemy.position - Player.position).normalized, Player.forward);
float angle = Mathf.Acos(dot) * Mathf.Rad2Deg;
Debug.Log("玩家和敌人之间的夹角:" + angle);
if(angle <= AttackRange)
{
Debug.Log("敌人在玩家的攻击范围内,可以进行攻击");
}
else
{
Debug.Log("敌人在玩家的攻击范围外");
}
}
else
{
Debug.Log("敌人在玩家的攻击距离外");
}
}
}
}
向量叉乘
向量叉乘得到一个向量,并且不满足交换律。 它满足反交换律 a × b = -(b × a)
叉乘公式:[x,y,z] × [a,b,c] = [yc-zb , za-xc , xb-ya]
几何解释:叉乘得到的向量垂直于原来的两个向量。
上图中向量a 和向量b 在一个平面上,向量c 垂直该平面,向量c 垂直于a和b
向量c 的长度 等于 向量a、向量b 模与夹角 sin值的积如下
c=axb = |a||b|sinθ
通过 |a||b|sinθ公式也可以推算出|axb|等于以a和b为两边的平行四边形面积
h = |a|sinθ
平行四边形面积=|b|h = |b|(|a|sinθ)
平行四边形面积=|b|h = |a||b|sinθ
也就是说a、b向量的叉积结果等于平行四边形的面积
那么垂直于平面的向量有两个,如何确认叉乘结果向量的朝向呢
通过计算向量c在坐标轴正方向 normal = (1, 1, 1).normalized 单位向量上的投影长度,判断向量a 到向量b是顺时针还是逆时针
令 value = c· normal = (y1z2 - z1y2, z1x2 - x1z2, x1y2 - y1x2) · (1, 1, 1).normalized
在右手坐标系中
当 value > 0 时,向量a到向量b是逆时针方向, θ 取值范围是 [0, 180)
当 value = 0 时,向量a 与向量b 平行共线, θ 值是 0
当 value < 0 时,向量a到向量b 是顺时针方向, θ 取值范围是 [0, -180)
在左手坐标系中(Unity 使用的是左手坐标系)
当 value > 0 时,向量a到向量b是顺时针方向, θ 取值范围是 [0, 180),
当 value = 0 时,向量a 与向量b 平行共线, θ 值是 0
当 value < 0 时,向量a到向量b是逆时针方向, θ 取值范围是 [0, -180),
叉乘符合右手定则
在左手坐标系中四根手指的旋转方向是顺时针的,所以在左手坐标系中(Unity 使用的是左手坐标系),如果向量a和向量b呈顺时针,那么叉乘结果c 指向大拇指方向,如果呈逆时针,则c指向大拇指的反方向
在右手坐标戏中四根手指的旋转方向是逆时针的,所以在右手坐标系中,如果向量a和向量b呈逆时针,那么叉乘结果c指向大拇指方向,如果呈顺时针,则c指向大拇指的反方向
叉积示例
叉积判断物体的左右关系
using UnityEngine;
public class Test : MonoBehaviour
{
public Transform Cube1;
public Transform Cube2;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Vector3 direction = Cube2.position - Cube1.position;
Vector3 cross = Vector3.Cross(direction, Cube1.forward);
if (cross.y > 0)
{
Debug.Log("Cube2在Cube1的左方");
}
else if (cross.y < 0)
{
Debug.Log("Cube2在Cube1右方");
}
else
{
Debug.Log("Cube2和Cube1在相同的位置");
}
}
}
}
叉积判断鼠标是否在UI矩形内
using UnityEngine;
public class Test : MonoBehaviour
{
private RectTransform mRectTrans;//UI
public Camera UICamera;//UI摄像机
Vector3[] points = new Vector3[4];//矩形的四个顶点
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
//获取UI坐标系中的鼠标位置
Vector2 mousePos = Input.mousePosition;
Vector2 localPos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(mRectTrans, mousePos, UICamera, out localPos);
//获取矩形的四个顶点位置
mRectTrans.GetLocalCorners(points);
if (IsInRect(points[0], points[1], points[2], points[3], localPos))
{
Debug.Log("在UI矩形内");
}
else
{
Debug.Log("不在UI矩形内");
}
}
}
private bool IsInRect(Vector2 A, Vector2 B, Vector2 C, Vector2 D,Vector2 E)
{
//true:水平方向在矩形内,false:水平方向在矩形外
bool h = Cross(A - B, A - E) * Cross(C - D, C - E) >= 0;
//true:垂直方向在矩形内,false:垂直方向在矩形外
bool v = Cross(A - D, A - E) * Cross(C - B, C - E) >= 0;
return h && v;
}
// Start is called before the first frame update
void Start()
{
mRectTrans = GetComponent<RectTransform>();
}
// 计算两个向量的叉积
public static float Cross(Vector2 a, Vector2 b)
{
return a.x * b.y - b.x * a.y;
}
}
总结
Unity中常用点乘和叉乘来计算两个物体之间的位置关系
例如:敌人在你身后的时候,叉乘可以判断你是往左转还是往右转更好的转向敌人,点乘得到你当前的面朝向的方向和你到敌人的方向之间的夹角
转向敌人代码实现
using UnityEngine;
public class Test : MonoBehaviour
{
public Transform Player;//玩家
public Transform Enemy;//敌人
public float RotateSpeed = 20f;
private void Start()
{
}
private void Update()
{
//计算夹角
float dot = Vector3.Dot((Enemy.position - Player.position).normalized, Player.forward); ;
float angle = Mathf.Acos(dot) * Mathf.Rad2Deg;
//夹角不为0
if(angle != 0 && angle > 1f)
{
//**使Player一直朝向敌人**
//计算方向
Vector3 direction = Vector3.Cross(Enemy.position - Player.position, Player.forward);
if (direction.y < 0)
{
//左转
Player.Rotate(Player.up, RotateSpeed * Time.deltaTime);
}
else if (direction.y > 0)
{
//右转
Player.Rotate(Player.up, -RotateSpeed * Time.deltaTime);
}
}
}
}