Unity,一种解决视角旋转后使用 "Physics.OverlapBox()"的方法

Unity2022.3官方"Physics.OverlapBox"说明

在这里先引用并复述一下官方对于这个API的说明:

public static Collider[] OverlapBox (Vector3 center, Vector3 halfExtents, Quaternion orientation= Quaternion.identity, int layerMask= AllLayers, QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal);

该方法将创建一个检测用的盒装立方体,并检测和获取该立方体范围内带有各类"Collider"碰撞体组件的物体。

"Vector3 center"用于确定这个盒状体的中心;

"Vector3 halfExtents"用于确认盒状体范围的向量,其xyz轴上的数值,应等于盒状体的长宽高方向上数值一半,该向量不能有负值,以避免未知的错误;

"Quaternion orientation= Quaternion.identity"用于确认盒状体的旋转角度;

"int layerMask= AllLayers”层遮罩,用于确认忽略哪些层(Layer)内的碰撞体;

"QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal",触发器相关。

 

这里提出在旋转摄像机视角后使用该API的一种方法。

 

首先,用于半径检测的"halfExtents"的向量是基于平行x,y,z轴方向获得的向量,其默认包围的

盒状体,当摄像机视角正向方向与z轴方向平行时,如图1,这是一个长宽高均为1个单位的立方体,其中心点坐标为(0,0,0),也就是处于世界坐标系的中心,现在这个物体自身的xyz轴方向与世界坐标系的xyz轴方向相同,此时使用"Physics.OverlapBox()"进行检测,我们输入盒状体的中心"center",同时输入用于确定检测范围的"halfExtents",其他参数留空。检测盒状体所覆盖范围将如图中橙色立方体。该结果与我们直觉相符。

图1

 

现在,让我们来看另外一种情况,当视角发生旋转时,如图2。现在蓝色的立方体是与我们视角面向相同的立方体,当我们使用"Physics.OverlapBox()"进行检测时,直觉上现在检测范围应该是蓝色的立方体,也就是参考蓝色立方体的本地坐标系进行范围检测。

图2

 

然而实际情况将如图3,检测范围依然是橙色的立方体。这是因为如前面所说该API设置半径时,其向量总是基于世界坐标系的xyz轴方向行进,所以即使我们视角方向改变了,但是检测的方向依旧不会改变。

图3

 

解决的办法是存在的,"Quaternion orientation"这个参数,控制着检测盒状体的旋转角度,如果我们输入对应视角旋转的角度,该旋转的中心为"center",也就是旋转橙色立方体的角度,使其与蓝色立方体重合,这个时候检测范围将对应上我们视觉上的直觉,如图4,但是如我们所知,改变的是检测盒状体的旋转角度,世界坐标系朝向是不会改变的。

图4

 

到这里原理看完了,现在我们换一个场景。图5,这是一个简单的RTS场景,我们的检测范围也简化为在一个平整场地上进行,即物体的布置以及检测范围都在Y轴为1的范围,暂不考虑Y轴上大的变动范围。

我们的视角现在依然与z轴平行。

 

图5

 

接下来如图6,使用框选的方式获得框选范围内的单位是我们常见的操作。

通常采用的方法是,在屏幕上绘制矩形线框作为框选范围的一种视觉表现,也就是紫色的矩形框。

我在图7上示意了框选的顺序,鼠标将从A点划向C点来框出选择框,也就是A点是起始点,C点是结束点,而B、D点位置是由A、C点沿着x、z轴方向推算出来的。

同时,在矩形框的起始点A,以及结束点C的屏幕坐标上,我们使用"Physics.Raycast()"发出射线指向地面进行射线检测,获得屏幕A、C点投射到地面时对应的世界坐标。

再通过这两点向量作为元素的求平均数计算,我们可以得到中心点O的坐标"center";

Vector3 center = new Vector3((C.point.x + A.point.x) / 2, 1, (C.point.z + A.point.z) / 2);   //这里我们y轴上统一取值1,方便说明。

然后求出我们需要的范围“halfExtents”。

Vector3 halfExtents = new Vector3(Mathf.Abs(C.point.x -  A.point.x) / 2, 1, Mathf.Abs(C.point.z -  A.point.z) / 2)    //"halfExtents"值不能有负数,这里使用"Mathf.Abs()"求绝对值。

这个时候我们视角前往与z轴平齐,情况与图1相同,所以当我们输入中心点"center",以及范围"halfExtents”时,将如图7选中检测盒状体范围内的单位。

图6

 

图7

 

同样,现在让我们旋转视角试试,如图8 。显而易见,当我们框选时没有指定检测盒状体的角度时,虽然我们视觉上认为将会框选紫色框线范围内的单位,然而实际上框选的却是橙色框线范围内的单位。

 

图8

 

那么这个时候是不是简单的输入旋转角度,就可以获得正确的结果呢,其实不然,接下来我们用一组简化的平面图来说明。

图9示意的是类似图1以及图7 的情况,当我们的视线与Z轴平行时,此时检测盒状体与视觉框线的方向是一致的,我们也能直观的获得框选结果。

 

图9

当我们旋转视角时,摄像机看到的将是如图10的情况,此时显然需要旋转检测盒状体,以让检测盒状体的框线与视觉框线重合。如果采用旋转各点坐标的或者求各边线斜率等办法,将涉及到多个点或线的旋转位移,较为复杂。

图10

 

现在让我们整理思路,在"Physics.OverlapBox()"中,其总是先取得中心点"center",再以平行于xyz轴方向作为基础向半径“halfExtents”延伸出检测盒状体,然后再根据"Quaternion orientation"对盒状体进行旋转。

在这里我们并不需要具体的旋转各个端点,而是先求得一个边线方向与xyz轴方向平行,同时各个边长与旋转前视觉框线相等的矩形,将其中心设置到与视觉检测(同时也是盒状检测体)的中心,再将其旋转到与视觉相同的角度,这样就完成了这个检测范围的转换,如图11所示。

 

图11

在开始前,我们需要使用"Physics.Raycast()"方法将取得屏幕上的点投射到世界中的坐标,以下说明的各点均是世界坐标系中的点。

现在我们来操作,首先我们要取得"center",因为旋转的中心点总是不变的,所以直接获得视觉框线的中心点即可,这里使用对角线向量取中间值的办法。

 

Vector3 center = new Vector3 ((A.point.x+Cponit.x)/2, 1, (A.point.z+Cponit.z)/2);

然后我们获得从C点往B点的向量,然后使用“.magnitude”方法求得它的模,同理求得D点往C点的模。

 

Vector3 dToC = D.point - C.point;

float dToCModulus = dToC.magnitude;

dToCModulus = Mathf.Abs(dToCModulus); //求绝对值

 

Vector3 cToB = C.point - B.point;

float cToBModulus = cToB.magnitude;

cToBModulus = Mathf.Abs(cToBModulus); //求绝对值

 

这个时候等于我们获得视觉框线的L与B长度,然后我们将cToBModulus与dToCModulus输入一个仅在x轴和z轴有数值的Vector3变量。

这个时候,我们已经将视觉框线还原成了一个长宽相等,并且与xz轴平行的矩形。

 

Vector3 setSelToHorizontal = new Vector3(dToCModulus/2, 0, 0);  //"halfExtents"指盒状检测器的一半,需要除以2。

Vector3 setSelToVertical = new Vector3(0, 0, cToBModulus/2); //"halfExtents"指盒状检测器的一半,需要除以2。

 

同时我们也得知了计算后的矩形线框的各点坐标。

图12

 

这个时候,我们会发现计算后的中心点O''与O以及O'的坐标是不同的,但是在计算中由O''点往B''点的向量方向与大小与检测盒状体框线的O'点往B'点是等价的。

所以我们最终求得的转换后的“halfExtents”为:

 

Vector3 halfExtents = new Vector3(setSelToHorizontal.x, 1, setSelToVertical.z) //该次测试将y轴数值设置为1。

 

然后我们需要获得一个用于旋转的角度,这里我们仅考虑摄像机绕着Y轴的旋转,所以我们现在仅需要取得摄像机绕Y轴旋转的数值。

 

Vector3 cameraEulerAngles = Camera.main.transform.eulerAngles;  //获得摄像机的旋转角度。

float rotationY = cameraEulerAngles.y;    //将摄像机旋转角度Y向值转换为float。

Quaternion selBoxRotation = Quaternion.Euler(0, rotationY, 0);  //重新获得一个仅在Y轴上旋转的Quaternion。

 

最终我们获得如下代码:

 

Physics.OverlapBox(center, halfExtents, selBoxRotation);

 

这里我们使用了视觉框线的中心点,半径使用了计算框线的结果,旋转角度采用摄像机的数值。这个时候已经能实现检测盒状体随着我们视角的旋转而旋转。

图13

 

最后,总结一下思路。

 

1、"Physics.OverlapBox()"是用于在确认中心点"Vector3 center"位置后,从中心点以半径"Vector3  halfExtents"延伸的检测盒状体,并根据"Quaternion orientation"进行旋转。并检测该盒状体内的碰撞体组件的API。

 

2、形成的检测盒状体是一个以"center"为中心,向左右同时扩展“halfExtent.x",上下同时扩展“halfExtent.y",前后同时扩展“halfExtent.z",最终形成长两倍于halfExtent.x/y/z的立方体,对其旋转参数赋予数值时,检测盒状体将以"center"为中心旋转。

 

3、当视角旋转时,视觉检测以及检测盒状体的中心点是不变的,但是视觉上的检测范围经旋转不与xyz轴平行,实际的检测盒状体仍然与xyz轴平行,进行符合视觉的检测需要对检测盒状体进行旋转。

 

4、通过求得视觉检测范围各边线向量的模,并使用模的值计算出在xyz轴沿轴方向上单一数值的向量,求得一个长宽高等于视觉检测范围且平行于xyz轴的计算检测范围。

 

5、计算检测范围的中心点与视觉检测、检测盒状体的中心点并不相等,但是从计算检测范围的中心点前往端点的向量与检测盒状体同点位指向的向量是相等的。

 

6、使用视觉检测的中心点"center",采用计算检测范围的半径"halfExtents",将获得一个长宽高、中心点与视觉检测范围相同,但是旋转角度不同的检测盒状体。

 

7、从摄像机获得旋转角度,赋予检测盒状体旋转变量,将获得一个长宽高、中心点、旋转角度与视觉检测范围相同的检测盒状体。

 

posted @ 2023-11-12 13:02  赭与秋  阅读(450)  评论(0编辑  收藏  举报