主要介绍AABB碰撞检测下,矩形该如何和矩形进行碰撞检测/和圆形进行碰撞检测。
直接看图,主要是比较两个矩形的边界线的前后关系。
矩形和矩形
分析什么时候重合
分析什么时候不重合
代码
private bool Detect_RectangleAndRectangle(Vector3 leftDownPoint1, Vector3 rightUpPoint1, Vector3 leftDownPoint2, Vector3 rightUpPoint2)
{
if (rightUpPoint1.x < leftDownPoint2.x || rightUpPoint2.x < leftDownPoint1.x || rightUpPoint1.y < leftDownPoint2.y || rightUpPoint2.y < leftDownPoint1.y)
{
return false;
}
return true;
}
矩形和圆
圆心和矩形的最近距离
圆是一个所有点到中点距离相同的点的集合,所以求任何东西和圆碰撞就相当于圆心和该物体的最近距离小于圆的半径。
将圆心和矩形的距离拆分成x轴方向上的距离和y轴方向上的距离,我们将矩形通过边所做的直线划分成不同区域,很容易观察x轴距离和y轴距离的求法。
圆心的x轴坐标在矩形区域x1到x2内,则xDistance为0,否则为圆心坐标x轴离区域左右端点的距离
圆心的y轴坐标在矩形区域y1到y2内,则YDistance为0,否则为圆心坐标y轴离区域左右端点的距离
private bool Detect_RectangleAndCircle_Method1(Vector3 circlePos, float circleRadius, Vector3 leftDownpoint, Vector3 rightUppoint)
{
float minX = 0;
float minY = 0;
if (circlePos.x < leftDownpoint.x || circlePos.x > rightUppoint.x)
{
minX = Mathf.min(Mathf.Abs(leftDownpoint.x - circlePos.x), Mathf.Abs(circlePos.x - rightUppoint.x));
}
if (circlePos.y < leftDownpoint.y || circlePos.x > rightUppoint.y)
{
minY = Mathf.min(Mathf.Abs(leftDownpoint.y - circlePos.y), Mathf.Abs(circlePos.y - rightUppoint.y));
}
if (minX * minX + minY * minY < circleRadius * circleRadius)
{
return true;
}
return false;
}
利用clamp可以进一步缩短代码
private bool Detect_RectangleAndCircle_Method1(Vector3 circlePos, float circleRadius, Vector3 leftDownpoint, Vector3 rightUppoint)
{
//clamp减少代码行
float minX = Mathf.Clamp(circlePos.x, leftDownpoint.x, rightUppoint.x) - circlePos.x;
float minY = Mathf.Clamp(circlePos.y, leftDownpoint.y, rightUppoint.y) - circlePos.y;
if (minX * minX + minY * minY < circleRadius * circleRadius)
{
return true;
}
return false;
}
更聪明的做法
大佬的做法,这里给个链接,看是容易看懂,但是具体是怎么推导出来就很离谱了。
https://www.zhihu.com/question/24251545/answer/27184960
private bool Detect_RectangleAndCircle_Method2(Vector3 circlePos, float circleRadius, Vector3 leftDownpoint, Vector3 rightUppoint)
{
//获得矩形中心
Vector3 rectangleCenter = (leftDownpoint + rightUppoint) / 2;
//矩形右边界相对于中心的坐标
rightUppoint = rightUppoint - rectangleCenter;
//将圆心坐标投射到矩形的第一象限,即求出圆心坐标和矩形中心的距离,然后绝对值
Vector3 absCirclePos = new Vector3(Mathf.Abs(circlePos.x - rectangleCenter.x), Mathf.Abs(circlePos.y - rectangleCenter.y));
//矩形第一象边界点到圆心的向量
Vector3 p2c = absCirclePos - rightUppoint;
//将p2c的x轴和y轴的小于0部分都变成0,即为圆心到矩形的距离
if (Mathf.Pow(Mathf.Max(0, p2c.x), 2) + Mathf.Pow(Mathf.Max(0, p2c.y), 2) < circleRadius * circleRadius)
{
return true;
}
return false;
}
圆和旋转的矩形进行碰撞检测
矩形如果不和世界坐标平行,好像我们前面的计算就失效了,那么怎么办呢
这里实际上只需要做一个小处理,就是将矩形的右上角的世界坐标以及圆心的世界坐标坐标转换为矩形的局部坐标,然后继续利用前面的向量法计算,
这里坐标轴变化不用平移和不用缩放只需旋转,但是可能有些同学没有学过旋转矩阵,所以可以来手撕一下。
推导旋转矩阵
几何看过旋转轴来推导点的办法,直接两个点连起来很难推导,这里推导一下极坐标和三角函数
-
三角函数
-
极坐标
几何有空研究一下怎么推,会补上
那么我们现在就有了旋转之后的圆形坐标了,
碰撞检测的步骤如下:
1.得到圆心相对于矩形的向量
2. 得到矩形的旋转角度,得到圆心在矩形坐标系下的坐标
3. 通过将相对坐标的x和y取绝对值将圆心转到矩形的第一象限
4. 求出矩形右上角的点坐标,得到圆心到右上角点的向量
这样我们就做好了向量法的前置准备,然后通过向量法求长度
/// <summary>
/// 在矩形和坐标不对齐的情况下进行矩形和圆形的检测
/// </summary>
/// <param name="circlePos">圆心坐标</param>
/// <param name="circleRadius">圆的半径</param>
/// <param name="rectanglePos">矩形中心坐标</param>
/// <param name="rectangleAngle">矩形旋转角度</param>
/// <param name="rectangleWidth">矩形宽度</param>
/// <param name="rectangleHeight">矩形高度</param>
/// <returns></returns>
private bool Dectect_RectangleAndCircle_Plus(Vector3 circlePos, float circleRadius, Vector3 rectanglePos, float rectangleAngle, float rectangleWidth, float rectangleHeight)
{
//求圆心相对于矩形的坐标
Vector3 localCirclePos = circlePos - rectanglePos;
float circleRotateAngle = -rectangleAngle * Mathf.Deg2Rad;
//圆心在矩形坐标系下的坐标
float localX = localCirclePos.x * Mathf.Cos(circleRotateAngle) - localCirclePos.y * Mathf.Sin(circleRotateAngle);
float localY = localCirclePos.x * Mathf.Sin(circleRotateAngle) + localCirclePos.y * Mathf.Cos(circleRotateAngle);
//圆心在矩形坐标系下的坐标转换到第一象限的坐标
Vector3 absCirclePos = new Vector3(Mathf.Abs(localX), Mathf.Abs(localY));
//右上角的点在矩形坐标系下的坐标
Vector3 rightUppoint = new Vector3(rectangleWidth / 2, rectangleHeight / 2);
Vector3 p2c = absCirclePos - rightUppoint;
//将p2c的x轴和y轴的小于0部分都变成0,即为圆心到矩形的距离
if (Mathf.Pow(Mathf.Max(0, p2c.x), 2) + Mathf.Pow(Mathf.Max(0, p2c.y), 2) < circleRadius * circleRadius)
{
return true;
}
return false;
}