Unity uses ray detection as a sensor to help cars avoid obstacles

This article is based on the original video of YouTube author EYEmaginary. The video address is Car AI Tutorial #1 (Unity 5) - Make the Path - YouTube

The main purpose of this article is to analyze and explain the content in the video, and this article is closely related to the previous article. If you read this article directly, there may be some variables that are unfamiliar to you, but you can learn ideas from it. At the same time, it is strongly recommended that if you have time, please watch the original video. If there are any errors in the following content, please leave a comment and rational discussion is welcome.

Continuing from the above, let’s make a sensor for the car to enable the car to avoid obstacles.

In fact, this method is not necessarily used for obstacle avoidance in cars. Many people think of using navigation components when they think of obstacle avoidance. I thought so at the beginning, but using sensors for obstacle avoidance also provides you with a new method. The idea is not just to avoid obstacles, but more functions may be achieved using this method.

Principle explanation

As shown in the figure, the sensor emits rays at five positions in front of the car. If the ray hits something, it is determined that there is an obstacle in front, and some logical statements are made to allow the vehicle to avoid obstacles.

Sensor location determined

The first thing created is the position of the A sensor, and then the positions of the left and right sensors are calculated based on the offset. Here is a method, first place the position of the car model at the origin, then create a cube, and place the cube at the position of the middle sensor, so that the position of the cube is the offset of the A sensor relative to the position of the car. As shown below

You can see that the position of the cube here is (-0.025, 1.15, 2.7), so the offset is this number. The same method can be used to calculate the length of sensors D and E from A. Here it is 1.125. Use this to continue editing the CarEngine script (the script in the previous article).

Creation of sensors

With the offset position, let's create the sensor next. The script content is as follows

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.Angle(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.Angle(frontSensorAngle,-transform.up) *     
                           transform.forward, out hit, sensorLength))
        {
            Debug.DrawLine(sensorStartPos, hit.point, Color.white);
        }
    }

}

Five sensors are created here, taking A, B, and D as examples. In order to synchronize the positions of the car and the sensor in real time, these three lines of code are used sensorStartPos += transform.forward * frontSensorPosition.z;         sensorStartPos += transform.right * frontSensorPosition.x;
        sensorStartPos += transform.up * frontSensorPosition.y;

Its main function is to make the sensor move when the car is moving. If you don't mind the trouble, you can create several empty objects under the car model. The positions of these empty objects correspond to the positions of the sensors, and then attach them to script.

The second step is to update the positions of B and C based on the offset. For the same reason, the same syntax as the above three statements is used.

The last step is how to change the direction of ray emission, that is, the ray emission direction of sensors D and E. The method used here is Quaternion.AngleAxis(frontSensorAngle,transform.up) * transform.forward

 The function of the Quaternion.AngleAxis(angle, axis) function is to return a quaternion that rotates the angle angle along the axis axis, so Quaternion.AngleAxis(frontSensorAngle, transform.up) returns a quaternion that is rotated 30° around the y-axis. Multiply the quaternion by transform.forward to define the ray direction of sensor E.

Then use the DrawLine method to draw a white line between the sensor position and the ray hit point, click Play, and the screen effect is as follows:

If you don't get the same effect or there are a few lines missing, or the sensor always shoots to the origin, then please adjust the length of sensorLength. Only when the ray hits an object with the collider attribute will a line appear.

Sensor usage

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.Angle(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.Angle(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;
        }
    }

}

When the sensor detects an obstacle, that is (!hit.collider.CompareTag("Terrain")), this condition means that if the ray detects something other than terrain, it will be judged to be true, so the car must be able to drive Add a label to the area as Terrain, and then define a global variable haveObstacles and a local variable avoidMultiplier. When haveObstacles is true, it means there are obstacles ahead, and avoidMultiplier represents the degree of wheel steering. At the same time, add the if(haveObstacles) return; statement to the ApplySteer function, which means that when there are obstacles ahead, you cannot simply turn according to the path point. The variables used in the last if statement are defined in the previous article. If you don’t understand, you can check the previous one. Let’s use this picture as an example

At this time, sensor C detects an obstacle, then set haveObstacles to true. At this time, the function in ApplySteer is no longer executed, and avoidMultiplier is subtracted by 1. Finally, in the last if statement, the steering angle of the front wheel is set to Left is the maximum to make the vehicle turn. On the contrary, if E detects an obstacle, then there is no need to turn so sharply. You only need to subtract 0.5 from avoidMultiplier.

At the same time, set haveObstacles to false at the beginning of the Sensor function. Sensors B and D and sensors C and E must be judged in pairs using if..else if statements.

Sensor functions are complete

According to the previous title, there will be a problem, as shown in the figure below

If an obstacle is detected by both B and C, the coefficient avoidMultiplier will be set to 0, and the car will directly hit the obstacle. At this time we have to rely on sensor 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.Angle(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.Angle(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);
    }

}

Finally, the A sensor is used here. Assuming that after the BCDE sensor has been detected, the avoidMultiplier is still 0, then there may be no obstacle in front, or there may be an obstacle detected by B and C at the same time. At this time, the ray emitted by A will detect At this time, the normal direction of the hit surface is used to determine the steering direction of the car. The following figure is an example:

Suppose BC hits the obstacle at the same time, and A also hits the obstacle. At this time, get the normal of the plane where the ray emitted by A intersects with the obstacle surface, which is the blue line. You can see the The normal points to the right side of the vehicle, which is positive on the X-axis. Then set avoidMultiplier to 1 to guide the vehicle to turn right.

Finally, the interpolation function Lerp is used to smooth the steering of the car.

Guess you like

Origin blog.csdn.net/qq_68117303/article/details/133030984