这两个算法来源于一个3DRTS游戏的需求,回顾了一下高中数学知识,这里简单介绍下。
这两个算法需求均是出自3D游戏,但是计算方式都是投射到X,Z平面来求解。
所以先拓展Vector3向量的方法。
/// <summary>
/// 去掉三维向量的Y轴,把向量投射到xz平面。
/// </summary>
/// <param name="vector3"></param>
/// <returns></returns>
public static Vector2 IgnoreYAxis(this Vector3 vector3)
{
return new Vector2(vector3.x, vector3.z);
}
Ps: 这个方法是已有库Vector3的拓展方法,可以直接使用Vector3.IgnoreYAxis()调用。 在此建议各位还不理解拓展方法的程序员仔细查找一下资料,因为这也有利于理解理论:csharp 的面向对象,其实都是面向过程,只是隐蔽传递了 “this”指针,所以看起来像是面向对象。
求点到直线的距离。
需求来源:这个需求来源于求路径点的时候,对于玩家在屏幕上画出的任意形状的路径,我们先取首尾两个点连线,形成一条向量,再从中间所有点中选出到这条向量中距离最长的点。最后用总共三个点画出游戏所需路径。
需求分析:需要求点到向量的距离,可以根据向量的两点得出直线方程,再根据点到直线的距离公式,求出每个点到直线的距离,再通过比对剔除掉不符合要求的点。
先来数学公式推导:
直线一般公式 AX + BY + C = 0,
直线外一点 (x0,y0) 到直线距离公式 d = Ax0 + By0 + C / sqrt(A ^ 2 + B ^ 2)
假设首尾两点 p1(x1, y1), p2(x2, y2).待计算点 p3(x3, y3).
把p1,p2带入直线一般公式得
Ax1 + By1 + c = 0 ~1
Ax2 + By2 + c = 0 ~2
Ax1 + By1 = Ax2 + By2
A(x1 - x2) = B(y2 - y1)
设A = y2 - y1, B = x1 - x2.可得解。
把A, B带入方程 ~1中
x1y2 - x1y1 + x1y1 - x2y1 + c = 0
求得 C = x2y1 - x1y2
最后把A,B,C带入点到直线距离公式可得值。
C#代码实现:
/// <summary>
/// 求点到直线的距离,采用数学公式Ax+By+C = 0; d = A*p.x + B * p.y + C / sqrt(A^2 + B ^ 2)
/// 此算法忽略掉三维向量的Y轴,只在XZ平面进行计算,适用于一般3D游戏。
/// </summary>
/// <param name="startPoint">向量起点</param>
/// <param name="endPoint">向量终点</param>
/// <param name="point">待求距离的点</param>
/// <returns></returns>
public static float DistanceOfPointToVector(Vector3 startPoint, Vector3 endPoint, Vector3 point)
{
Vector2 startVe2 = startPoint.IgnoreYAxis();
Vector2 endVe2 = endPoint.IgnoreYAxis();
float A = endVe2.y - startVe2.y;
float B = startVe2.x - endVe2.x;
float C = endVe2.x * startVe2.y - startVe2.x * endVe2.y;
float denominator = Mathf.Sqrt(A * A + B * B);
Vector2 pointVe2 = point.IgnoreYAxis();
return Mathf.Abs((A * pointVe2.x + B * pointVe2.y + C) / denominator);;
}
判断目标点在源向量的哪一侧
需求来源:这个需求来源于一个战斗单位,左右两边都有火炮,在战斗中会根据目标点在标战斗单位的左侧或者右侧开启对应的火炮进行伤害。
需求分析:我一开始只是简单的把目标单纳入到战斗单位的X,Z平面坐标系中,再求出目标点在这个平面坐标系的象限区域,但是这个有个很大的缺点,即时战斗单位的点不变,但是朝向却是任意角度。
于是我再次翻开了伟大的高中数学。 我们知道单位向量的角度和点积值得关系式 [0, 90, 180] = [1, 0, -1]
*没有画图工具,暂时用鼠绘替代哈,^_^ 见谅
假设战斗单位的坐标点为O ,正方向是Z轴,那么P在第一和第二象限时向量OP和向量OZ的点积是 [1, 0],在第三和第四向限时OP和OZ的点积是[0, -1].如果我们仔细观察,会发现当OP和OX向量点积为[1,0]的时候,P点位于第一和第四向限,也就是P位于向量OZ的右侧,如果OP和OX的点积为[0,-1]时,P点位于第二和第三向限也就是位于OZ的左侧。
那么以此为基础,我么就可以得出任意一点位于向量OZ的方向。
首先 我们计算出目标向量OZ的垂直向量OX,再用目标点P减去原点O,得到向量OP,换算出两个向量的单位向量,再求点积值,当值>0时,目标点位于向量的右侧反之为左侧。
设向量OZ(x1,y1),向量OX(x2,y2).
向量OZ垂直于向量OX,所以得出 x1x2 + y1y2 = 0;
因为点O是战斗单位的原点,也是OZ向量上的点,所以我们根据O的坐标求出经过O点的垂直向量。
带入O(x3, y3)坐标得出: x2 = x3, y2 = (-x1 * x3) / y1.
O点到P(x4,y4)向量为OP(x4 - x3, y4 - y3).
C#代码:
/// <summary>
/// 判断目标点是否位于向量的左边
/// </summary>
/// <param name="startPoint">向量起点</param>
/// <param name="endPoint">向量终点</param>
/// <param name="point">目标点</param>
/// <returns>True is on left, false is on right</returns>
public static bool PointOnLeftSideOfVector(this Vector3 vector3, Vector3 originPoint, Vector3 point)
{
Vector2 originVec2 = originPoint.IgnoreYAxis();
Vector2 pointVec2 = (point.IgnoreYAxis() - originVec2).normalized;
Vector2 vector2 = vector3.IgnoreYAxis();
float verticalX = originVec2.x;
float verticalY = (-verticalX * vector2.x) / vector2.y;
Vector2 norVertical = (new Vector2(verticalX, verticalY)).normalized;
float dotValue = Vector2.Dot(norVertical, pointVec2);
return dotValue < 0f;
}
完整源代码地址: https://github.com/CodeSuperHero/UnityFrame.git