Unity版本2020.3.32f1c1
目录
Ray
RaycastHit
Physics.Raycast()
RaycastHit[]
Layer
应用
1.对Bad层级的物体进行着色
2.从相机发射射线与地面进行射线交互
3.运动的物体在场景中进行避障
总结
参考资料
Ray
原理是发射一条射线,传入起始点和起始方向当做射线的起点和方向。
Ray ray = new Ray(transform.position, transform.forward);
在OnDrawGizmos()函数中画出来
private void OnDrawGizmos()
{
Ray ray = new Ray(transform.position, transform.forward);
Gizmos.color = Color.blue;
Gizmos.DrawRay(ray);
}
RaycastHit
之后我们在场景中添加两个包含Collider的物体,我希望其中一个物体发出射线可以感知到另一个物体。Unity中通过RaycastHit结构体来存储射线的交互信息,结构如下所示:
Physics.Raycast()
Physics.Raycast()返回true,可以用它来表示射线是否与游戏对象发生交互.Physics.Raycast一共有16个重载方法,可以按需选择。
public static bool Raycast(Ray ray, out RaycastHit hitInfo, [Internal.DefaultValue("Mathf.Infinity")] float maxDistance, [Internal.DefaultValue("DefaultRaycastLayers")] int layerMask, [Internal.DefaultValue("QueryTriggerInteraction.UseGlobal")] QueryTriggerInteraction queryTriggerInteraction);
上面的参数分别是射线,射线交互结构体,检测的最大距离(默认最大),层模板(默认所有),查询触发器交互(默认全局)。
如果设置了LayerMask参数的话,射线只会和游戏对象是这个Layer的交互,其他的游戏对象不会与射线发生交互;QueryTriggerInteraction默认参数表示查询使用全局 Physics.queriesHitTriggers 设置,还有两个可选值,Ignore表示忽略,当游戏对象的Collider组件下的Is Trigger被勾选后,射线检测也将不会与其交互;Collider参数表示始终报告触发器命中。
if (Physics.Raycast(ray, out hit,Mathf.Infinity,badMask,QueryTriggerInteraction.Ignore))
{
Gizmos.color = Color.green;
Gizmos.DrawLine(transform.position, hit.point);
}
RaycastHit[]
除了声明单个RaycastHit外,还可以设置RaycastHit[]数组,Physcis.RaycastAll可以获得所有的与射线交互的信息,这里我传入了第三个参数LayerMask,将下图中红框内的物体的层级设置为了badMask,这样射线检测只会与这个层级的物体发生交互,最后打印出了三个物体的名称,以及RaycastHit[]数组的大小。
RaycastHit[] hits;
hits = Physics.RaycastAll(ray,Mathf.Infinity,badMask);
for (int i = 0; i < hits.Length; i++)
{
Debug.Log(hits[i].collider.gameObject.name);
}
Debug.Log(hits.Length);
Layer
在写的时候发现从不同地方获取到的LayerMask的int值不同。
[SerializeField] private LayerMask badMask这样通过面板获得的值是2^value ,value是在Unity面板中Layer的数值,插一嘴Layer中用四个字节也就是32位来表示,每一个Layer占据其中一位,如此来说badMask.value返回的就是十进制表示的数;通过结构体hit.collider.gameObject.layer获得的就是上文中说的value,也就是占据哪一位的数值。
如果想要比较二者是否相同,需要进行转换:
if (Mathf.Pow(2,(hit.collider.gameObject.layer)) == badMask.value)
应用
1.对Bad层级的物体进行着色
看效果图,有的没有被着色,是因为被前面的对象遮住了,一个解决办法是顺带更改前面对象的层级,让它不再被射线交互。
public class Sphere : MonoBehaviour
{
[SerializeField] private Material recordBad;
[SerializeField] private Material originBad;
[SerializeField] private LayerMask badMask;
private void OnDrawGizmos()
{
Ray ray = new Ray(transform.position, transform.forward);
Gizmos.color = Color.blue;
//Gizmos.DrawRay(ray);
RaycastHit hit;
Gizmos.color = Color.blue;
Gizmos.DrawLine(transform.position, transform.TransformPoint(Vector3.forward * 10));
//Gizmos.color = Color.black;
//Gizmos.DrawLine(transform.position, Vector3.forward);
RaycastHit[] hits;
hits = Physics.RaycastAll(ray,Mathf.Infinity,badMask);
for (int i = 0; i < hits.Length; i++)
{
Debug.Log(hits[i].collider.gameObject.name);
}
Debug.Log(hits.Length);
if (Physics.Raycast(ray, out hit,Mathf.Infinity,badMask,QueryTriggerInteraction.Ignore))
{
Gizmos.color = Color.green;
Gizmos.DrawLine(transform.position, hit.point);
hit.collider.gameObject.GetComponent<Renderer>().material = recordBad;
}
}
}
2.从相机发射射线与地面进行射线交互
Ray ray = viewCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, ground))
{
Vector3 point = hit.point;
controller.LookAt(point);
}
public void LookAt(Vector3 lookPoint)
{
Vector3 heightCorrectedPoint = new Vector3(lookPoint.x, transform.position.y, lookPoint.z);
transform.LookAt(heightCorrectedPoint);
}
还有一种方式是通过Plane来接收射线,返回的distance表示沿射线与平面相交的距离。如果射线平行于平面,函数返回false
并设置enter
为零。如果射线指向与平面相反的方向,则函数返回false
并设置enter
为沿射线的距离(负值)。
Ray ray = viewCamera.ScreenPointToRay(Input.mousePosition);
float distance;
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
if (groundPlane.Raycast(ray, out distance))
{
Vector3 point = ray.GetPoint(distance);
//Debug.DrawLine(ray.origin, point, Color.red);
controller.LookAt(point);
}
射线检测
3.运动的物体在场景中进行避障
以物体为圆心,规定半径和生成的点的数量,近似以点来近似球表面
public static class BoidHelper {
const int numViewDirections = 300;
public static readonly Vector3[] directions;
static BoidHelper () {
directions = new Vector3[BoidHelper.numViewDirections];
float goldenRatio = (1 + Mathf.Sqrt (5)) / 2;
float angleIncrement = Mathf.PI * 2 * goldenRatio;
for (int i = 0; i < numViewDirections; i++) {
float t = (float) i / numViewDirections;
//对应二维的半径R
float inclination = Mathf.Acos (1 - 2 * t);
float azimuth = angleIncrement * i;
//球坐标转直角坐标
float x = Mathf.Sin (inclination) * Mathf.Cos (azimuth);
float y = Mathf.Sin (inclination) * Mathf.Sin (azimuth);
float z = Mathf.Cos (inclination);
directions[i] = new Vector3 (x, y, z);
}
}
}
Vector3 ObstacleRays()
{
rayDirections = BoidHelper.directions;
if (rayDirections != null)
{
flag = true;
}
for (int i = 0; i < rayDirections.Length; i++)
{
Vector3 dir = cachedTransform.TransformDirection(rayDirections[i]);
Ray ray = new Ray(position, dir);
if (!Physics.SphereCast(ray, settings.boundsRadius, settings.collisionAvoidDst, settings.obstacleMask))
{
return dir;
}
}
return forward;
}
void OnDrawGizmosSelected()
{
if (flag)
{
DrawGizmos();
}
}
void DrawGizmos()
{
Debug.LogFormat("进入DraGizmos");
Debug.LogFormat("进入DraGizmos Flag");
Gizmos.color = Color.red;
foreach (var go in rayDirections)
Gizmos.DrawSphere(position + go, 0.02f);
Gizmos.color = Color.green;
foreach (var go in rayDirections)
{
Vector3 dir = cachedTransform.TransformDirection(go) * SpherePointsRadius;
Gizmos.DrawSphere(position + dir, radius);
}
}
总结
- 想要一个物体被射线检测到,就必须加上Collider组件,Is Trigger并不影响射线检测
- 生成一个射线需要五个参数,起始点,方向,碰撞信息结构体,范围距离,检测层
- 起始点一般使用transform.position来表示
- 方向一般使用transform.forward,表示当前物体正前方
- 碰撞信息使用RaycastHit结构体来存贮,需要事先声明
- 检测范围是float型,可不写,默认无限长
- 检测层可不写,默认全检测
- 如果你使用Debug.DrawLine()画了一条线而没有显示出来,不妨看看Gizmos有没有被关闭
- Raycast返回值是Bool,而RaycastAll返回值则是RaycastHit结构体数组
- 混淆点
- Ray是一个类,表示射线
- RaycastHit是一个结构体,记录射线的碰撞信息
- Raycast和RaycastAll是函数,使用射线来检测碰撞,通过Physics来调用
参考资料
梦小天幼:详解Unity中的射线与射线检测文章来源:https://www.toymoban.com/news/detail-725477.html
Coding Adventure: Boids文章来源地址https://www.toymoban.com/news/detail-725477.html
到了这里,关于Unity 的射线检测的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!