3
\$\begingroup\$

I'm trying to make a racing game that measures your distance from the center-line of the road. To do this I need to check on which part of the road the player is, so the calculation is correct. (If it is a turn or a straight road.)

I made a trigger wall between road-parts, but for the trigger to work correctly I need to know if player actually drove through the trigger or just touched it and backed-off. Because if you activate the trigger and don't end up on the other side, then you changed the road calculation method without changing the road you are on.

I've tried making a small cube at the player location, reducing the width of the trigger and then setting only cube and trigger to interact on the separate layer. It failed as with average speed the trigger didn't detect the cube. (Cube is a part of player cart, so you can't assign to it rigid body as it just falls-off)

I'm trying to think of geometrical solution, but I have no idea how to generalize it. In short I'd need to draw a line through a middle of trigger area along the separating axis and check if OnTriggerEnter player is on one side and OnTriggerExit is on the other side of the trigger.

I'd appreciate any help. Green, thin trigger

Player Cart for reference of size

\$\endgroup\$
3
  • \$\begingroup\$ Maybe make triggers along both sides of the road and raycast/measure the distance to both. \$\endgroup\$
    – Nikaas
    Commented Jul 21, 2023 at 6:26
  • \$\begingroup\$ Do I understand it correctly that what you actually want is to keep track of which road segment the car is on, and that you want to treat the car as a point for this purpose so it can be on only one road segment at a time? \$\endgroup\$
    – Philipp
    Commented Jul 22, 2023 at 17:32
  • \$\begingroup\$ @Philipp Yes. You understand correctly. \$\endgroup\$ Commented Jul 22, 2023 at 21:10

2 Answers 2

2
\$\begingroup\$

Here's one way you can do this, using dot products to check how far the vehicle is from the center of the trigger's transform along the trigger's z axis (blue arrow in the local transform gizmo).

To perform well, the trigger should be at least one vehicle length thick, or thicker if your vehicles are very fast, so they don't tunnel past it in a single frame. Align the trigger so its local z+ axis points "forward" along the track.

public class Checkpoint : MonoBehaviour {

    // Identifier for "this" part of the track - maybe you have a component for that?
    public int trackSegment;

    // Reference to checkpoint "behind" this one for when going backwards.
    public Checkpoint previous;

    // Track which side a vehicle was on when it entered the trigger.
    List<Vehicle> _enteredFromRear;
    List<Vehicle> _enderedFromFront;

    // Gets the distance in world units along the checkpoint's z+ axis (blue arrow)
    // that the vehicle is in front of the checkpoint (negative for behind).
    float GetForwardDistance(Vehicle vehicle) {
        var toVehicle = vehicle.transform.position - transform.position;
        return Vector3.Dot(toVehicle, transform.forward);
    }

    // When a vehicle enters the trigger, record which side it entered from.
    void OnTriggerEnter(Collider other) {
        // Ignore collisions not from vehicles (use physics layers to exclude these completely)
        if (!other.rigidbody.TryGetComponent(out Vehicle vehicle))
            return;

        // Ignore repeated entries from vehicles with multiple colliders.
        if (_enteredFromRear.Contains(vehicle) || _enteredFromFront.Contains(vehicle))
            return;

        if (GetForwardDistance(vehicle) <= 0) {
            _enteredFromRear.Add(vehicle);
        } else {
            _enteredFromFront.Add(vehicle);
        }
    }

    // When a vehicle exits the trigger, check whether it left from the
    // opposite side it entered, and update its track segment if so.
    void OnTriggerExit(Collider other) {
        // Again, filtering with physics layers saves redundant calls.
        if (!other.rigidbody.TryGetComponent(out Vehicle vehicle))
            return;

        if (GetForwardDistance(vehicle) > 0) {
            // If we left from the front, and entered from the back...
            if (CheckCrossing(vehicle, _enteredFromRear, _enteredFromFront)
                vehicle.OnEnterTrackSegment(trackSegment);
        } else {
            // If we left from the back, and entered from the front...
            if (CheckCrossing(vehicle, _enteredFromFront, _enteredFromRear)
                vehicle.OnEnterTrackSegment(previous.trackSegment);
        }        
    }

    // Checks whether vehicle came from the "cross" side (true)
    // or the "reverse" side (false), and removes the vehicle from that list.
    bool CheckCrossing(Vehicle vehicle, List<Vehicle> cross, List<Vehicle> reverse) {
        int index = cross.IndexOf(vehicle);

        if (index < 0) {
            reverse.Remove(vehicle);
            return false;
        }

        cross.RemoveAt(index);
        return true;
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ Great answer - Thanks . :) It works as intended, but after trying that I decided that I need to change the distance measuring system so that it works on all roads (by using approximation). \$\endgroup\$ Commented Jul 23, 2023 at 7:13
1
\$\begingroup\$

If you want to know if the car is within a race track segment, then don't use OnTriggerEnter. Create a trigger area that covers the whole track segment, and use OnTriggerStay.

This method will get called every frame while the car's collider intersects the collider of the area. So you can use it to set the current segment the car is in, which you then use (right there or in another script) to do your distance calculation.

However, there is a slight problem with this method: When the car is on the edge of two track segments, its collider will intersect both of them. That means it will trigger the OnTriggerStay methods of both colliders in the same frame. This might or might not be a problem for you, depending on what you are doing, how you are doing it and why you are doing it. If that's not acceptable for you, then I have a completely different approach for you:

Still have colliders that cover the whole track segments, but don't use trigger events, use Physics.OverlapSphere. Use this method from the car with its transform.position to detect the colliders of the race track its center is currently in. This method returns not one but an array of colliers. This array might contain more than one collider even for a sphere with radius 0 in case two track segments overlap. In that case it would be prudent to just pick the first one. Also, this method is unaware of which gameObject called it, so it is possible for an object to detect its own collider with it as well. To avoid that, put all the race tracks with their colliders on a separate physics layer, and use a layer mask in your OverlapSphere call so the method detects only colliders.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .