2D triggers proving grossly inaccurate

I’m finding triggers can be exited by objects that are completely covering them. I’ve a 2D lens flare effect with 8 trigger zones. While an asteroid is covering them, a single solid 2D polygon collider, it disables the light based on how many zones are covered. But the flare can suddenly blip on for a single frame and OnTriggerExit2D() is called, despite the triggers still being covered.

Covered light


Still covered but OnTriggerExit2D() is fired and OnTriggerStay2D is not.

Can you show some code? It seems to me that it would be based on the idea that you’re using several zones.

Attached to each child GO with the trigger attached. The GO has its ID set in the editor. Structure is

Background -

  • Sun -
    – Flare -
    — Trigger1 -
    — Trigger… -
    — Trigger8 -
    — flare elements
using UnityEngine;

public class behaviour_flareTrigger : MonoBehaviour {
    // attach to flare triggers to control flare
    public spc_2DFlare flareScript;
    public int id;

    void OnTriggerStay2D(Collider2D col){
       if(id == 1){
            print("On Stay " + Time.time);
        }
        flareScript.Triggered(id);
    }
}

Flare controller script.

using System.Collections;
using UnityEngine;

public class spc_2DFlare : MonoBehaviour {
    // scales flare sprites based on source position relative to camera
    // Gets flare sprite children in object with this script attached

    int[] triggers;
    int occlusion;                                // count of how much occlusion there is
    Color tempCol;
    Vector2 tempScale;
    float tempFloat;

    void Start () {
        triggers = new int[8];
    }
 
    void Update () {
        occlusion = triggers[0]+triggers[1]+triggers[2]+triggers[3]+triggers[4]+triggers[5]+triggers[6]+triggers[7];
        if(occlusion == 8){
            DisableFlare();
        }else{
            ScaleFlare(Occlusion);
        }
        print("Clear triggers " + Time.time);
        triggers[0] = 0;
        triggers[1] = 0;
        triggers[2] = 0;
        triggers[3] = 0;
        triggers[4] = 0;
        triggers[5] = 0;
        triggers[6] = 0;
        triggers[7] = 0;
    }

    public void Triggered(int n){
        triggers[n] = 1;
    }

    void FixedUpdate(){
    }
}

Every physics update, the OnTriggerStay2D() is evaluated setting occlusions, and then triggers are reset, which I’ve tested logging flow order by printing the time. I’ve tried changing the reset of the triggers from Update to FixedUpdate but it makes no difference. Randomly the flare lights on with occlusion == 0. According to the manual, control flow should always evaluate triggers before Updates, so AFAICS this control flow should work. And it does 99% of the time.

Currently looking to replace the trigger tests with ray tests so the above code may have errors from where I’m hacking in/out my changes. :wink:

If I were you I would not be using OnTriggerStay, or Update for that matter. The only time you need to do any sort of processing is when a trigger is entered or exited, correct?

If all you care about is the number of triggers covered, and not which triggers, then you can simplify this a great deal.

Would something like this work for you?

using UnityEngine;
public class behaviour_flareTrigger : MonoBehaviour {
    // attach to flare triggers to control flare
    public spc_2DFlare flareScript;

    private void OnTriggerEnter2D(Collider2D collision) {
        flareScript.TriggerEntered();
    }

    private void OnTriggerExit2D(Collider2D collision) {
        flareScript.TriggerExited();
    }
}
using System.Collections;
using UnityEngine;
public class spc_2DFlare : MonoBehaviour {
    // scales flare sprites based on source position relative to camera
    // Gets flare sprite children in object with this script attached
    int occlusion; // count of how much occlusion there is
    Color tempCol;
    Vector2 tempScale;
    float tempFloat;
    int triggerCount = 8;

    private void UpdateFlare() {
        if(occlusion == triggerCount) {
            DisableFlare();
        } else {
            ScaleFlare(occlusion);
        }
    }

    public void TriggerEntered() {
        occlusion++;
        UpdateFlare();
    }

    public void TriggerExited() {
        occlusion--;
        UpdateFlare();
    }
}

That was my first approach but it had the same sort of failures. Exit would happen even when the triggers were sat under a collider, plus I had times where a trigger was exited but it didn’t log properly so the flare never returned to full brightness (although I can’t be absolutely sure that wasn’t my code). That’s where I thought TriggerStay would be more reliable as it’s tested every frame and should never fail unless the physics engine isn’t robust enough. And I really can’t see the problem. A cluster of circular triggers and a large polygon collider that works 99.9% of the time. Why would it randomly fail some tests?

My alternative raytraced solution works correctly.

There must be other factors at play that are causing it. Assuming your collision layers are setup correctly, and you’re not picking up many different objects entering and exiting the same trigger, there’s no reason inherent to the physics system that would cause that kind of unexpected behavior. It should be straightforward enough to debug if you output the objects entering/exiting.

I don’t fully understand the effect you’re going for and why all this physics testing is necessary, but I’m glad you got it working.

Indeed. I listed the objects coming and going in the Trigger methods. The only object coming was the asteroid, and it was randomly exiting despite being present over the triggers. Completely, not even partially. :face_with_spiral_eyes:

The effect is a lens flare that’s adjusted based on proportion of coverage. The default flare just fades in/out over time. Mine allows an object to stop halfway over the light and drop its intensity to halfway. Perhaps overkill but it seemed a simple and effective solution when I thought of it. :wink:

Perhaps you can detect an approaching object using one encompassing trigger radius, then make use of the new 5.6 unity physics function Physics2D.Distance or collider2D.Distance to find the distance between the flare and the incoming collider. Then based on that distance, affect the intensity of the flare. (The reason I suggest the physics one is because it gets the closest point on the collider bounds, not just center-to-center distance.)

However, if your current solution works for you, then there’s no need to sweat it. :smile:

Thanks. Wasn’t aware of those new functions. I’ll look into it further down the line.