Unity客户端开发(初级)面试总结--几何计算


一、向量

1. 基本概念及加、减、取模计算

先上图:
在这里插入图片描述
两个向量A、B:

Vector2 A = new Vector2(1, 2);
Vector2 B = new Vector2(0, 1);

得到向量AB:

  • 方向:A(起点)指向B(终点),如上图;
  • B(终点)减去A(起点),如下。
Vector2 AB = B - A;	// new Vector2(B.x - A.x, B.y - A.y) = (-1, -1)

向量减(加)法:两个向量的x分量和y分量分别相减(加)

顺便说一下:
在这里插入图片描述

Vector2 BA = A - B;	// new Vector2(A.x - B.x, A.y - B.y) = (1, 1)

在平面坐标系中,A - B得到的向量(图中红色向量所示)与向量BA(图中绿色向量所示)是相等向量(长度和方向都相同的向量)

向量的模:向量的长度

float a = A.magnitude;	// Mathf.Sqrt(A.x * A.x + A.y * A.y) = 2.236
float b = B.magnitude;	// Mathf.Sqrt(B.x * B.x + B.y * B.y) = 1

得到向量AB的模:

float ab = Vector2.Distance(A, B); // 一般使用Distance方法求两个点之间的距离,等价于下面
ab = AB.magnitude; // Mathf.Sqrt((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y)) = 1.414

单位向量:模为1的向量,有某个方向
单位向量 = 向量 / 该向量的模(非单位向量可通过归一化得到与原向量方向相同的单位向量)
向量归一化:

Vector2 UA = A.normalized;	// A / a = (1 / 2.236, 2 / 2.236)
Vector2 UB = B.normalized;	// B / b = (0 / 1, 1 / 1)
Vector2 UAB = AB.normalized;// AB / ab = (-1 / 1.414, -1 / 1.414)

零向量:模为0的向量,方向任意

Vector2 O = Vector2.zero; // new Vector2(0, 0)
  • 零向量不可被归一化:模为0,0不能做除数(在unity中,有做特殊处理,零向量归一化还是零向量,不会报错)。
    在这里插入图片描述

1.5. 弧度与角度

获取一下两个向量的夹角
在这里插入图片描述

float angle = Vector2.Angle(A, B); // 26.565

看看内部是怎样实现的:
在这里插入图片描述

暂时不细看每一行代码,只看结果乘了一个57.29578f,这个值与Mathf.Rad2Deg一样,代表着弧度转化为角度的常量:
在这里插入图片描述
这意味着System.Math.Acos(num2)即反余弦函数arccosθ的返回值是弧度;弧度与角度的转化,根据Mathf.Deg2Rad能看出:

  • 1° = π / 180 ≈ 0.01745弧度
  • 1弧度 = 180 / π ≈ 57.29578°

2. 向量的点乘与差乘

1.点乘(点积、内积)

A · B,结果为标量, 表示两个向量方向上的相似度,点乘得到的值:1,方向完全相同;0,方向正好垂直;-1,方向完全相反

点积运算具有交换性:A·B = B·A

float dot = Vector2.Dot(A, B); // = Vector2.Dot(B, A) = A.x * B.x + A.y * B.y = 2

若两个向量均为单位向量,则它们点乘的结果就是它们夹角的余弦值;

游戏开发中,最重要的是知道并利用其几何意义: 求两个向量的夹角,得到两个向量的相对位置

A · B = |A| * |B| * cosθ

=>
cosθ = A · B / |A| * |B|

  • cosθ > 0 ,夹角 < 90°;
  • cosθ = 0 ,夹角 = 90°;
  • cosθ < 0 ,夹角 > 90°。
    在这里插入图片描述
public string CalRelativePosition(Transform character, Transform enemy)
{
    Vector2 charFrontDir = character.forward.normalized;
    Vector2 enemyDir = (enemy.position - character.position).normalized;
    float dot = Vector2.Dot(charFrontDir, enemyDir); // 当两个向量的长度都为1时,点乘的结果就是夹角的余弦值

    if (dot > 0)
    {
        return "Front";
    }
    else if (dot < 0)
    {
        return "Behind";
    }
    else // dot == 0
    {
        return "Side";
    }
}
  • 点乘可以判断敌人在角色的前后,得到角色与敌人的夹角

=>
θ = arccos(A · B / |A| * |B|)

回到上面“1.5.弧度与角度”的求两个向量夹角的方法:
在这里插入图片描述

  • 向量与本身点积结果为向量的模的平方:A·A = |A|^2;利用此性质也可求向量的模。

2.叉乘(叉积、外积)

A × B,结果为矢量,叉积运算不满足交换性:A×B = -B×A

Vector3 cross = Vector3.Cross(A, B);
// = new Vector3(A.y * B.z - A.z * B.y, A.z * B.x - A.x * B.z, A.x * B.y - A.y * B.x) = new Vector3(0, 0, 1)

// 叉乘只在三维空间中使用
// 二维向量(z为0)叉乘得到一个垂直与二维平面的向量

一个向量与自己做叉乘,得到零向量
其几何意义:|A × B| = |A| * |B| * sinθ,同理也可以求两个向量的夹角角度,但不常用。
更多的,是根据叉乘得到的向量的 某一个分量的值,及 左手定则 判断两个向量的相对位置
在这里插入图片描述

  • 某一分量的值:判断两个向量的相对位置是在二维平面上,需要指定在哪两个坐标轴构成的平面上判断位置关系,在unity三维场景中,一般是以X-Z平面定位,需要看叉乘结果的Y分量
  • 左手定则:unity使用的是左手坐标系 ;所以判断顺时针或逆时针要使用左手螺旋定则
    • Y > 0,B在A的顺时针方向(右侧);
    • Y < 0,B在A的逆时针方向(左侧);
    • Y = 0,B在A的方向平行。
      在这里插入图片描述

完善一下上面计算相对位置,可以精确地知道敌人与角色的相对位置了:

public string CalRelativePosition(Transform character, Transform enemy)
{
    Vector3 charFrontDir = character.forward.normalized;
    Vector3 enemyDir = (enemy.position - character.position).normalized;

    float dot = Vector3.Dot(charFrontDir, enemyDir); // 当两个向量的长度都为1时,点乘的结果就是夹角的余弦值
    Vector3 cross = Vector3.Cross(charFrontDir, enemyDir); // 敌人相对角色的位置,注意参数顺序

    if (dot > 0)
    {
        if (cross.y > 0)
        {
            return "Right Front";
        }
        else if (cross.y < 0) 
        {
            return "Left Front";
        }
        else
        {
            return "Front";
        }
        
    }
    else if (dot < 0)
    {
        if (cross.y > 0)
        {
            return "Right Behind";
        }
        else if (cross.y < 0)
        {
            return "Left Behind";
        }
        else
        {
            return "Behind";
        }
    }
    else
    {
        if (cross.y > 0)
        {
            return "Right";
        }
        else if (cross.y < 0)
        {
            return "Left";
        }
        else
        {
            return "Same";
        }
    }
}
  • 叉乘可以判断敌人在角色的左右
  • 叉乘可以判断点是否在三角形内部;
    在这里插入图片描述

AB与AP叉乘,BC与BP叉乘,CA与CP叉乘,若结果符号都相同,说明P在AB、BC、CA同侧;
则P在三角形ABC内部,否则,P在三角形ABC外部。

二、图形相交

前面举的例子都是将对象抽象成一个点,但在实际的游戏中,每个对象都是有一定范围的:可视范围、技能攻击范围,这些范围的情况比较多:

暂时不讨论有Z轴的情况(这里的Z轴是指玩家感知的高度的概念,不是指unity坐标系里的Z轴)

  • 一般角色的物理范围为圆形;可视范围为扇形等;
  • 角色的技能影响范围则更加多样:扇形、圆形、矩形等;

1.定义图形

  • 圆形
public struct Circle
{
	public Vector2 Center; 	// 圆心
    public float Radius; 	// 半径
}
  • 扇形
public struct Sector
{
	public Vector2 Center;	// 圆心
    public float Radius;	// 半径
    public Vector2 Direction;	// 方向
    public float Angle;	// 角度 0 - 180
}
  • 矩形(OBB)
public struct OBB
{
    public Vector2 Center; // 中心点
    public Vector2 Extents; // 半长度矢量(x:长的1/2,y:宽的1/2)
    public float Angle; // 旋转角度(定向信息)
}

在这里插入图片描述

2.点到线段的最短距离

在这里插入图片描述

/// <summary>
/// 线段与点的最短距离的平方
/// </summary>
/// <param name="startPoint">线段起点</param>
/// <param name="segment">线段向量</param>
/// <param name="targetPoint">求解点</param>
/// <returns></returns>
public static float SegmentPointSqrDistance(Vector2 startPoint, Vector2 segment, Vector2 targetPoint)
{
    // 起始点到求解点的投影与线段的比值
    float t = Vector2.Dot(targetPoint - startPoint, segment) / segment.sqrMagnitude;
    // 求解点在线段上的投影点
    Vector2 projection = (startPoint + Mathf.Clamp01(t) * segment);
    return (targetPoint - projection).sqrMagnitude;
}

代码详细解析参考扇形与圆盘相交测试浅析

3.圆形相交

1.点与圆形相交

满足以下条件(不考虑点在圆上的情况):

  • 点到圆形圆心的距离小于圆形半径
public static bool CirclePoint(Circle circle, Vector2 point)
{
    float distance = (circle.Center - point).sqrMagnitude;
    float radius = Mathf.Pow(circle.Radius, 2);
    if (Mathf.Approximately(distance, radius))
    {
        return false; // 浮点数精确度原因,无法直接用“==”比较,近似相等
    }
    return distance < radius;
}

2.圆形与圆形相交

需满足以下条件(不考虑两圆相切的情况):

  • 两个圆形圆心的距离小于两个圆形半径之和
public static bool Circles(Circle origin, Circle target)
{
    float distance = (origin.Center - target.Center).sqrMagnitude;
    float radius = Mathf.Pow(origin.Radius + target.Radius, 2);
    if (Mathf.Approximately(distance, radius))
    {
        return false; // 浮点数精确度原因,无法直接用“==”比较,近似相等
    }
    return distance < radius;
}

4.扇形相交

1.点与扇形相交

同时满足以下条件(不考虑点在扇形上的情况):

  • 点距离扇形圆心的长度小于扇形的半径
  • 扇形圆心与点组成的向量与扇形方向向量夹角小于扇形角度的一半
public static bool SectorPoint(Sector sector, Vector2 point)
{
    Vector2 distance = point - sector.Center;
    if (distance.sqrMagnitude < Mathf.Pow(sector.Radius, 2))
    {
        if (Vector2.Angle(distance, sector.Direction) < sector.Angle * 0.5f)
        {
            return true;
        }
    }
    return false;
}

2.圆形与扇形相交

同时满足以下条件(不考虑圆形与扇形相切的情况):

  • 圆形圆心距离扇形圆心的长度小于扇形的半径加圆形的半径的长度
  • ① 扇形圆心与圆形圆心组成的向量与扇形方向向量夹角小于扇形角度的一半
    如果条件①不满足,需要圆形与扇形的边界相交(如下图)。
    在这里插入图片描述
public static bool SectorCircle(Sector sector, Circle circle)
{
    Vector2 distance = circle.Center - sector.Center;
    if (distance.sqrMagnitude < Math.Pow(sector.Radius + circle.Radius, 2))
    {
        if (Vector2.Angle(distance, sector.Direction) < sector.Angle * 0.5f)
        {
            return true;
        }
        else
        {
            float halfAngle = sector.Angle * 0.5f * Mathf.Deg2Rad; // 获得一半的弧度
            float targetSectorInnerAxisX = Vector2.Dot(distance, sector.Direction);
            float targetSectorInnerAxisY = Mathf.Abs(Vector2.Dot(distance, new Vector2(-sector.Direction.y, sector.Direction.x)));

            if (targetSectorInnerAxisX > distance.magnitude * Mathf.Cos(halfAngle))
            {
                return true;
            }
            Vector2 targetSectorInnerAxis = new Vector2(targetSectorInnerAxisX, targetSectorInnerAxisY);
            Vector2 directionSectorInnerAxis = sector.Radius * new Vector2(Mathf.Cos(halfAngle), Mathf.Sin(halfAngle));
            return SegmentPointSqrDistance(Vector2.zero, directionSectorInnerAxis, targetSectorInnerAxis) < circle.Radius * circle.Radius;
        }
    }
    return false;
}

代码详细解析参考笔记——两种游戏常用几何判定

5.矩形相交

1.点与矩形相交

public static bool OBBPoint(OBB obb, Vector2 target)
{
    Vector2 p = target - oBBArea.Center;
    p = Quaternion.AngleAxis(oBBArea.Angle, Vector3.forward) * p; // 旋转到对应的矩形空间
    Vector2 v = Vector2.Max(p, -p);
    return v.x < obb.Extents.x && v.y < oBBArea.Extents.y;
}

2.圆形与矩形相交

public static bool OBBCircle(OBBArea obb, Circle circle)
{
    Vector2 p =  circle.Center - obb.Center;

    p = Quaternion.AngleAxis(obb.Angle, Vector3.forward) * p; // 旋转圆心到矩形坐标系的点中

    Vector2 v = Vector2.Max(p, -p); // 利用对称原理移动圆心到第一象限
    Vector2 u = Vector2.Max(v - obb.Extents, Vector2.zero);
    return u.sqrMagnitude < circle.Radius * circle.Radius;
}

代码详细解析参考笔记——两种游戏常用几何判定


参考:
Unity Mathf【Deg & Rad】- 关于数学运算中的度与弧度
扇形与圆盘相交测试浅析
笔记——两种游戏常用几何判定

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值