# Simulating Road Traffic/Overtaking - Overtake works mostly, but starts clipping other vehicle

I am trying to improve on my Car class, which is used to make a small road scene with 3 lanes each direction with the ability to overtake by changing lanes.

I had a very basic version working with all box colliders and a predictive movement script in my code (so basically I performed the movement of the object, then immediately check a collision and move it to previous_position in same frame). It worked OK but i found i was a bit clunky and I had to have oversized collision boxes on the car which caused me other problems elsewhere in the game.

Now I am using Raycast. At first I used just one Raycast from the centre of the car. But of course once the centre of the car had past the vehicle in front it would think road was clear. I tried to fix this by having 3 rays (centre, left, right in local space). This improved it a lot but the wingmirrors of the car (and often times a slice of the body) would clip into each-other.

I've ended up adding around 10 Raycasts trying to make sure it always sees the car in front. I even tried making the edge-most ones slightly past the width of the car.

The only thing I can think to mention is that the cars are rotated 90 degree. So transform.forward is actually facing either +/- X-Axis in world space depending on if isTopLane is true/false.

Here is the Car class in full. For the life of me I cannot figure out why the cars overtake but stop overtaking too early (ie. they continue straight through the edge of the car in front):

public class Car : MonoBehaviour
{
bool isTopLane;
float speed;

float vehicle_width;
float vehicle_length;
Material material;

AudioSource audioSource;
float start_pitch;
float max_pitch;

float ray_length_forwards;
float ray_length_sideways;

public void InitCar(int spawn_z)
{
Color color = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f), 1);

if (spawn_z <= 0)
isTopLane = false;
else
isTopLane = true;

Vector3 rot = Vector3.zero;
rot.y = 90;
if (isTopLane)
{
x *= -1;
rot.y *= -1;
}

transform.position = new Vector3(x, 0, spawn_z);
transform.eulerAngles = rot;

material = GetComponentInChildren<MeshRenderer>().material;
material.color = color;

audioSource = GetComponent<AudioSource>();
start_pitch = audioSource.pitch + (speed * 0.01f);
max_pitch = start_pitch * 2.6f;

vehicle_width = GetComponent<Collider>().bounds.size.x;
vehicle_length = GetComponent<Collider>().bounds.size.z;
ray_length_forwards = GetComponent<Collider>().bounds.size.z * 1.4f;
ray_length_sideways = GetComponent<Collider>().bounds.size.x * 1.4f;

if ((isTopLane && transform.position.z < 1.1f) || (!isTopLane && transform.position.z > -1.1f))

}

private void FixedUpdate()
{
if (GameManager.instance.Game_Paused)
return;

audioSource.enabled = GameManager.instance.Options_SoundOn;

audioSource.pitch += (speed * 0.04f) * Time.fixedDeltaTime;
if (audioSource.pitch > max_pitch)
audioSource.pitch = start_pitch;

Drive();
}

void Drive()
{
Vector3 centre = transform.position;
Vector3 right1 = centre + Vector3.up * (vehicle_width * 0.1f);
Vector3 right2 = centre + Vector3.up * (vehicle_width * 0.2f);
Vector3 right3 = centre + Vector3.up * (vehicle_width * 0.3f);
Vector3 right4 = centre + Vector3.up * (vehicle_width * 0.4f);
Vector3 right5 = centre + Vector3.up * (vehicle_width * 0.5f);
Vector3 right6 = centre + Vector3.up * (vehicle_width * 0.65f);
Vector3 left1 = centre + Vector3.down * (vehicle_width * 0.1f);
Vector3 left2 = centre + Vector3.down * (vehicle_width * 0.2f);
Vector3 left3 = centre + Vector3.down * (vehicle_width * 0.3f);
Vector3 left4 = centre + Vector3.down * (vehicle_width * 0.4f);
Vector3 left5 = centre + Vector3.down * (vehicle_width * 0.5f);
Vector3 left6 = centre + Vector3.down * (vehicle_width * 0.65f);
Vector3[] ray_origins = { centre, right1, right2, right3, right4, right5, right6, left1, left2, left3, left4, left5, left6 };
for (int i = 0; i < ray_origins.Length; i++)
{
if (Physics.Raycast(new Ray(ray_origins[i], transform.forward), out RaycastHit hit, ray_length_forwards))
{
if (hit.transform.gameObject.GetComponent<Car>())
{
Overtake();
return;
}

}
}

DriveForwards();

}

void DriveForwards()
{
Vector3 motion = transform.forward * speed * Time.fixedDeltaTime;
transform.position += motion;
}

void Overtake()
{
if (isTopLane && transform.position.z <= 1)
return;

if (!isTopLane && transform.position.z >= -1)
return;

if (Physics.Raycast(new Ray(transform.position, -transform.right), out RaycastHit hit, ray_length_sideways))
{
if (hit.transform.gameObject.GetComponent<Car>())
return;
}

Vector3 motion = -transform.right * speed * Time.fixedDeltaTime;
transform.position += motion;
}

}


The behaviour I see in game is acting as if the vehicle_width is too thin. I have checked as best I can and my code to get the width seems correct vehicle_width = GetComponent<Collider>().bounds.size.x; I am quite sure the problem must lie in my Drive() function. Somehow the Ray Origins are positioned wrong. This is my guess, but I have tried many different ways. For me this seems impossible that the car can drive forwards if another car is in-front.

Drastically widening the box trigger has actually given me some help. But it needs to be a lot wider than the car why is this?

Thank you for your time and help.

• Have you considered putting an extra trigger collider in front of (and behind) each car, as "reserved space" that other cars should try not to merge into? Then you can use fewer rays, casting against a bigger obstacle. Jun 4 at 15:41
• Since the car already has a Box Collider (Trigger) and I use it for player-car collision check. Would I have to make child objects on the car to add the other Trigger boxes? I could then have component called 'OvertakeZone' attached and my Raycasts could check for those instead? I suppose one large box all around the car would do it , ill give it a try. Thanks Jun 4 at 16:10
• Still unsure as to why the existing code doesn't work. Overtakes are quite rare in my game, but basically every time one does occur it ends abruptly and the car slides through approx a quarter of the other cars width. I even already have slightly oversize collision trigger boxes on the cars. To me reading the code, it seems impossible for it to continue forwards when a car is in front of it Jun 4 at 16:47
• will vehicle_width = GetComponent<Collider>().bounds.size.x; get the width of my car in local space, or is it getting the length or something silly instead (since its rotated 90d)? Jun 4 at 16:52
• That looks like something that should be part of your question, not tucked away in a comment. It should also be accompanied by images showing how the hierarchy and transforms of your cars are set up. Jun 4 at 16:54

Well thanks to the advice in the comments. I have sort of fixed it (although I am still baffled why the original code in the question doesn't work).

I fixed it by adding child object called OvertakeZone with a script component attached with the same name. In the picture you can see it is another collider much larger than the car.

Now my code looks like this. The cars give eachother enough space now but I must say its still not as big a space as the large collider suggests.

    void Drive()
{
if ((isTopLane && transform.position.z < 1.1f) || (!isTopLane && transform.position.z > -1.1f))

Vector3 centre = transform.position;
Vector3 right = centre + Vector3.up * (vehicle_width * 0.6f);
Vector3 left = centre + Vector3.down * (vehicle_width * 0.6f);
Vector3[] ray_origins = { centre, right, left};
for (int i = 0; i < ray_origins.Length; i++)
{
if (Physics.Raycast(new Ray(ray_origins[i], transform.forward), out RaycastHit hit, ray_length_forwards))
{
if (hit.transform.gameObject.GetComponent<OvertakeZone>())
{
Overtake();
return;
}

}
}

DriveForwards();

}

void DriveForwards()
{
Vector3 motion = transform.forward * speed * Time.fixedDeltaTime;
transform.position += motion;
}

void Overtake()
{
if (isTopLane && transform.position.z <= 1)
return;

if (!isTopLane && transform.position.z >= -1)
return;

Vector3 centre = transform.position;
Vector3 left = centre + Vector3.left * (vehicle_length * 0.6f);
Vector3 right = centre + Vector3.right * (vehicle_length * 0.6f);
Vector3[] ray_origins = { centre, right, left };
for (int i = 0; i < ray_origins.Length; i++)
{
if (Physics.Raycast(new Ray(ray_origins[i], -transform.right), out RaycastHit hit, ray_length_forwards))
{
if (hit.transform.gameObject.GetComponent<OvertakeZone>())
{
return;
}

}
}

Vector3 motion = -transform.right * speed * Time.fixedDeltaTime;
transform.position += motion;
}