Unity使用射线检测做传感器让汽车避障

本文根据油管作者EYEmaginary原视频创作,视频地址是Car AI Tutorial #1 (Unity 5 ) - Make the Path - YouTube

本文主要做的是对视频中的内容进行分析和讲解,且本文和上篇文章息息相关,如果直接看这一篇可能会有某些变量让你陌生,但是你可以从中学到思路。同时强烈建议如果各位有时间请去看原视频。以下内容如有错误请留言评论,欢迎理性讨论。

承接上文,下面来为汽车制作传感器来让汽车实现避障的功能。

实际上汽车的避障也不一定要使用这种方法,很多人一想到避障就会想到使用导航组件,我刚开始也是这么想的,但是使用传感器做避障也为各位提供了一种新的思路,不只是避障,利用该方法也许可以实现更多的功能。

原理讲解

如图所示,传感器就是在车的前方的五个位置发射射线,如果射线击中了某物,那么就判定前方有障碍物,从而做出一些逻辑语句让车辆进行避障。

传感器的位置确定

首先创建的是A传感器的位置,然后根据偏移计算左右传感器的位置。这里有个方法,首先将汽车模型的位置放在原点处,然后创建一个cube,将该cube放在中间传感器的位置,这样cube的位置就是A传感器相对于汽车位置的偏移量。如下图所示

可以看到这里的cube的位置是(-0.025,1.15,2.7),那么偏移量也就是这个数。同样的方法可以计算出传感器D,E距离A的长度,我这里是1.125,借此继续编辑CarEngine脚本(上一篇文章中的脚本)。

传感器的创建

有了偏移位置,接下来来创建传感器,脚本内容如下

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CarEngine : MonoBehaviour
{
    //.....
    
    //传感器部分
    public float sensorLength;  //传感器能够检测的长度

    //传感器A相对于车辆的偏移
    private Vector3 frontSensorPosition = new Vecotr3(-0.025f,1.15f,2.7f); 
 
    //传感器D,E相较于Z轴的偏移角度
    private float frontSensorAngle = 30f;

    private void FixedUpdate()
    {
        //....
        Sensors();
    }
    
    private void Sensors()
    {
        Vector3 sensorStartPos = transform.position;
        
        //同步汽车和传感器的位置,也就是说当汽车移动时传感器的位置也要改变
        sensorStartPos +=transform.forward * frontSensorPosition.z;
        sensorStartPos +=transform.up * frontSensorPosition.y;
        sensorStartPos +=transform.right * frontSensorPosition.x;
        RaycastHit hit;
        
        //传感器A
        if(Physics.Raycast(sensorStartPos, transform.forward, out hit, sensorLength))
        {
            Debug.DrawLine(sensorStartPos, hit.point, Color.white);
        }
        
        //传感器C
        //传感器C相对于A的偏移量为X轴的1.125
        sensorStartPos += transform.right * 1.125f;
        if(Physics.Raycast(sensorStartPos, transform.forward, out hit, sensorLength))
        {
            Debug.DrawLine(sensorStartPos, hit.point, Color.white);
        }

        //传感器E
        //E的射线发射方向改变了
        if(Physics.Raycast(sensorStartPos,         
                           Quaternion.AngleAxis(frontSensorAngle,transform.up) *     
                           transform.forward, out hit, sensorLength))
        {
            Debug.DrawLine(sensorStartPos, hit.point,Color.white);
        }

        //传感器B
        sensorStartPos -= transform.right * 2.25f;
        if(Physics.Raycast(sensorStartPos, transform.forward, out hit, sensorLength))
        {
            Debug.DrawLine(sensorStartPos, hit.point, Color.white);
        }

        //传感器D
       if(Physics.Raycast(sensorStartPos,         
                           Quaternion.AngleAxis(frontSensorAngle,-transform.up) *     
                           transform.forward, out hit, sensorLength))
        {
            Debug.DrawLine(sensorStartPos, hit.point, Color.white);
        }
    }

}

这里创建了五个传感器,以A,B,D为例,为了实时同步车和传感器的位置,使用了 这三行代码          sensorStartPos += transform.forward * frontSensorPosition.z;
        sensorStartPos += transform.up * frontSensorPosition.y;
        sensorStartPos += transform.right * frontSensorPosition.x;

其主要作用就是为了让汽车在动的时候传感器也在动,如果你不嫌麻烦,可以在车的模型下创建几个空物体,这些空物体的位置对应着传感器的位置,然后为他们附上脚本。

其次就是根据偏移量更新B和C的位置,同样的道理,也是用的和上述三种语句相同的语法。

最后就是如何更改射线发射的方向,也就是传感器D和E的射线发射方向,这里使用的方法是Quaternion.AngleAxis(frontSensorAngle,transform.up) * transform.forward

 Quaternion.AngleAxis(angle, axis)函数的作用是返回一个沿axis轴旋转angle角度的四元数,所以Quaternion.AngleAxis(frontSensorAngle,transform.up)返回的就是绕y轴旋转30°的四元数,将该四元数在乘以transform.forward这就定义好了传感器E的射线方向。

再使用DrawLine方法在传感器位置和射线击中点位置画出一条白色的线,点击Play,画面效果如下:

如果你没有出现一样的效果或者少了几根线,或者传感器一直射向原点,那么请调整sensorLength的长度,只有当射线射中了某个具有collider属性的物体才会有线出现。

传感器的使用

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CarEngine : MonoBehaviour
{
    //.....
    
    //是否有障碍物
    private bool haveObstacles;


    private void FixedUpdate()
    {
        //....
        ApplySteer();
        Sensors();
    }


    private void ApplySteer()
    {
        if (haveobstacles) return;
        //这里利用路径点坐标和车的坐标计算出相对向量,也就是从车目前的位置指向路径点位置的向量
        Vector3 relativeVector =    transform.InverseTransformPoint(nodes[currentIndex].position);
        //print(relativeVector);
        //relativeVector /= relativeVector.magnitude;
        //计算相对向量的X分量和该向量之比,表示了目标节点相对于车辆的水平偏移程度
        float newSteer = (relativeVector.x / relativeVector.magnitude) * maxSteerAngle;
        targetSteerAngle = newSteer;
        LF.steerAngle = targetSteerAngle;
        RF.steerAngle = targetSteerAngle;
    }
    
    private void Sensors()
    {
        haveObstacles = false;
        Vector3 sensorStartPos = transform.position;
        
        //同步汽车和传感器的位置,也就是说当汽车移动时传感器的位置也要改变
        sensorStartPos +=transform.forward * frontSensorPosition.z;
        sensorStartPos +=transform.up * frontSensorPosition.y;
        sensorStartPos +=transform.right * frontSensorPosition.x;
        RaycastHit hit;

        //转向系数
        float avoidMultiplier = 0;
        
        
        //传感器A
        if(Physics.Raycast(sensorStartPos, transform.forward, out hit, sensorLength))
        {
            Debug.DrawLine(sensorStartPos, hit.point, Color.white);   
        }
        
        //传感器C
        //传感器C相对于A的偏移量为X轴的1.125
        sensorStartPos += transform.right * 1.125f;
        if(Physics.Raycast(sensorStartPos, transform.forward, out hit, sensorLength))
        {
            if(!hit.collider.CompareTag("Terrain"))
            {
                haveObstacles = true; 
                avoidMultiplier -= 1f;
                Debug.DrawLine(sensorStartPos, hit.point, Color.white);                           
            }
        }

        //传感器E
        //E的射线发射方向改变了
        else if(Physics.Raycast(sensorStartPos,         
                           Quaternion.AngleAxis(frontSensorAngle,transform.up) *     
                           transform.forward, out hit, sensorLength))
        {
            if(!hit.collider.CompareTag("Terrain"))
            {
                haveObstacles = true; 
                avoidMultiplier -=0.5f;
                Debug.DrawLine(sensorStartPos, hit.point, Color.white);             
            }
        }

        //传感器B
        sensorStartPos -= transform.right * 2.25f;
        if(Physics.Raycast(sensorStartPos, transform.forward, out hit, sensorLength))
        {
            if(!hit.collider.CompareTag("Terrain"))
            {
                haveObstacles = true; 
                avoidMultiplier += 1f;
                Debug.DrawLine(sensorStartPos, hit.point, Color.white);            
            }
        }

        //传感器D
       else if(Physics.Raycast(sensorStartPos,         
                           Quaternion.AngleAxis(frontSensorAngle,-transform.up) *     
                           transform.forward, out hit, sensorLength))
        {
            if(!hit.collider.CompareTag("Terrain"))
            {
                haveObstacles = true; 
                avoidMultiplier += 0.5f;
                Debug.DrawLine(sensorStartPos, hit.point, Color.white);            
            }        
        }
        if(haveObstacles)
        {
            LF.steerAngle = maxSteerAngle * avoidMultiplier;
            RF.steerAngle = maxSteerAngle * avoidMultiplier;
        }
    }

}

当传感器检测到了障碍物,也就是(!hit.collider.CompareTag("Terrain")),这个条件的意思是如果射线检测到除了地形以外的东西,就判定为真,所以要将赛车能够行驶的区域添加一个标签为Terrain,然后定义一个全局变量haveObstacles和局部变量avoidMultiplier,haveObstacles为真时代表前方有障碍物,avoidMultiplier代表车轮的转向程度。同时要在ApplySteer函数中添加if(haveObstacles) return;语句,这代表当前方有障碍物时就不能简单的依照路径点转向了。在最后的一个if语句中所使用的变量是上一篇文章所定义的,不理解的可以查看前面的。还是用这副图举例

此时传感器C检测到了障碍物,那么此时将haveObstacles置为真,此时不在执行ApplySteer内的函数,并且将avoidMultiplier减去1,最在最后的if语句中将前轮的转向角度设置为向左最大来让车辆转向,相对的,如果是E检测到障碍物,那么就不需要那么急转,只需要将avoidMultiplier减去0.5即可。

同时要在Sensor函数开始时将haveObstacles置为false。传感器B,D和传感器C,E之间要用if..else if语句成对判断。

传感器功能完善

依照上个标题中会出现问题,如下图所示

如果一个障碍物同时被B和C检测到了,那么系数avoidMultiplier将会被置0,此时车会直接撞上障碍物。这个时候就要依靠传感器A了。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CarEngine : MonoBehaviour
{
    //.....
    
    //是否有障碍物
    private bool haveObstacles;

    public float turnSpeed =5.0f;    

    private void FixedUpdate()
    {
        //....
        ApplySteer();
        Sensors();
        LerpToSteerAngle();
    }


    private void ApplySteer()
    {
        if (haveobstacles) return;
        //这里利用路径点坐标和车的坐标计算出相对向量,也就是从车目前的位置指向路径点位置的向量
        Vector3 relativeVector =    transform.InverseTransformPoint(nodes[currentIndex].position);
        //print(relativeVector);
        //relativeVector /= relativeVector.magnitude;
        //计算相对向量的X分量和该向量之比,表示了目标节点相对于车辆的水平偏移程度
        float newSteer = (relativeVector.x / relativeVector.magnitude) * maxSteerAngle;
        targetSteerAngle = newSteer;
        LF.steerAngle = targetSteerAngle;
        RF.steerAngle = targetSteerAngle;
    }
    
    private void Sensors()
    {
        haveObstacles = false;
        Vector3 sensorStartPos = transform.position;
        
        //同步汽车和传感器的位置,也就是说当汽车移动时传感器的位置也要改变
        sensorStartPos +=transform.forward * frontSensorPosition.z;
        sensorStartPos +=transform.up * frontSensorPosition.y;
        sensorStartPos +=transform.right * frontSensorPosition.x;
        RaycastHit hit;

        //转向系数
        float avoidMultiplier = 0;
       
        
        //传感器C
        //传感器C相对于A的偏移量为X轴的1.125
        sensorStartPos += transform.right * 1.125f;
        if(Physics.Raycast(sensorStartPos, transform.forward, out hit, sensorLength))
        {
            if(!hit.collider.CompareTag("Terrain"))
            {
                haveObstacles = true; 
                avoidMultiplier -= 1f;
                Debug.DrawLine(sensorStartPos, hit.point, Color.white);                           
            }
        }

        //传感器E
        //E的射线发射方向改变了
        else if(Physics.Raycast(sensorStartPos,         
                           Quaternion.AngleAxis(frontSensorAngle,transform.up) *     
                           transform.forward, out hit, sensorLength))
        {
            if(!hit.collider.CompareTag("Terrain"))
            {
                haveObstacles = true; 
                avoidMultiplier -=0.5f;
                Debug.DrawLine(sensorStartPos, hit.point, Color.white);             
            }
        }

        //传感器B
        sensorStartPos -= transform.right * 2.25f;
        if(Physics.Raycast(sensorStartPos, transform.forward, out hit, sensorLength))
        {
            if(!hit.collider.CompareTag("Terrain"))
            {
                haveObstacles = true; 
                avoidMultiplier += 1f;
                Debug.DrawLine(sensorStartPos, hit.point, Color.white);            
            }
        }

        //传感器D
       else if(Physics.Raycast(sensorStartPos,         
                           Quaternion.AngleAxis(frontSensorAngle,-transform.up) *     
                           transform.forward, out hit, sensorLength))
        {
            if(!hit.collider.CompareTag("Terrain"))
            {
                haveObstacles = true; 
                avoidMultiplier += 0.5f;
                Debug.DrawLine(sensorStartPos, hit.point, Color.white);            
            }        
        }

        //传感器A
        if(avoidMultiplier == 0)
        {
            if(Physics.Raycast(sensorStartPos, transform.forward, out hit, sensorLength))
            {
                if(!hit.collider.CompareTag("Terrain"))
                {
                    haveObstacles = true; 
                    Debug.DrawLine(sensorStartPos, hit.point, Color.white);  
                    if(hit.normal.x < 0)
                    {
                        avoidMultiplier = -1.0f;
                    }          
                    else
                    {
                        avoidMultiplier = 1.0f;
                    }
                }    
            }    
        }
        if(haveObstacles)
        {
            targetSteerAngle = maxSteerAngle * avoidMultiplier
        }
    }

    //平滑转弯
    private void LerpToSteerAngle()
    {
        LF.steerAngle = Mathf.Lerp(LF.steerAngle, targetSteerAngle, Time.deltaTime * turnSpeed);
        RF.steerAngle = Mathf.Lerp(RF.steerAngle, targetSteerAngle, Time.deltaTime * turnSpeed);
    }

}

最后这里使用了A传感器,假设在BCDE传感器都检测过了之后,avoidMultiplier依然为0,那么可能前方没有障碍,也有可能出现一个障碍物同时被B和C检测到了,这个时候A发射的射线会检测到它,此时利用被击中面的法线方向判断汽车转向方向,以下图为例:

假设BC同时击中了障碍物,且A也击中了障碍物,这个时候去获取A射出的射线与障碍物表面交点的所处平面的法线,也就是蓝色的线,可以看到该法线指向车辆的右侧,也就是在X轴上为正,那么此时将avoidMultiplier设置为1,从而指导车辆往右转。

最后使用了插值函数Lerp将汽车的转向进行平滑。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值