OntriggerExit2D before OnTriggerEnter2D for same object (bug?)

so in general what happens i have an Archer that have colider. In its ontriggerenter2d it adds entered objects (enemys) to a list. And in ontriggerexit2d removes them. I also have arrow that when colides with enemy, enemys script triggers “Destroy(this.gameobject)” in ontriggerenter if its arrow.

And so sometimes, as the title implies, OntriggerExit2D method inside archer class for enemy objects happens before OnTriggerEnter2D method for same object. As for my more detailed debugging it seems to happen when enemy spawns right away on top of an arrow. (but honestly it seems like this happens more often than only this exact situations). And so unity First tries to remove from list first, then it adds it to list, and then it destroys the object leaving the list with new “null” reference.

Could Unity devs make so all “ontriggerEnter” events happens before all “OntriggerExit” events for same frame please to make it logical and intuitive? Or at least have some options in settings to control this and change behavior as desired.

Also as i cant control it, i decided to make some workarounds. And knowing the famous picture of monobehevior classes lifecycle -

where we can see that “update” method happens after all “fixedupdate” methods (including ontriggerenter/exit physics methods) and knowing that “Destroy()” method postpone actual destruction of an object to “before the end of frame” triggering all the events relative to this object before hand, i came up with such logical solution -

public class TriggerHandler : MonoBehaviour
{
    private Queue<Collider2D> enterQueue = new Queue<Collider2D>();
    private Queue<Collider2D> exitQueue = new Queue<Collider2D>();

    private void OnTriggerEnter2D(Collider2D other)
    {
        enterQueue.Enqueue(other);
    }

    private void OnTriggerExit2D(Collider2D other)
    {
        exitQueue.Enqueue(other);
    }

    private void Update()
    {
        // Process OnTriggerEnter2D events first
        while (enterQueue.Count > 0)
        {
            Collider2D other = enterQueue.Dequeue();
            // Handle trigger enter logic
        }

        // Process OnTriggerExit2D events after
        while (exitQueue.Count > 0)
        {
            Collider2D other = exitQueue.Dequeue();
            // Handle trigger exit logic
        }
    }
}

to ensure all enter logics executed before exit logics. But now with this, it seems that the actual objects ALWAYS without exceptions are already 100% destroyed and not just postponed to destruction leading to nullref exceptions in the console each time we try to work with “Collider2D other = exitQueue.Dequeue();”. Can Unity also fix this to work as intended?

P.S. If anybody encountered with these problems i would love to hear u’r solutions and workarounds. I obviously then after just decided to go with basic null ref checks and just fix it all along the code flow.
But it felt rly dirty to know that i can never know real execution and that all intuitive logis is messed up. If only u ever somehow fixed it so it actually works in proper order, I would love to hear how u did that. :heart:

Any callback is for a collider pair, not a single collider, If a single pair of colliders has no contacts then you’ll get an exit. You won’t get an exit then an enter because that means there’s a contact so you won’t even get an exit or an enter at all.

Enter when a new collider pair has started. Exit when a collider pair has no contacts.

It simply cannot happen any other way.

The diagram is irrelevant. The callbacks happen at the end of the simulate step i.e. the Physics2D.Simulate before it finishes. You cannot interrupt this by doing anything, it happens as part of the simulate step.

well “it can not happen” but it reliably happens 100% of the time.

O also know how it should work, I’m not asking about that. I’m asking why its don’t work like intended and how we can deal with that. If u just don’t believe me, let me show you the exact code that produces such errors. I most sure its exactly have to do something with how the “Destroy” method works and the order of other events that it should call. Im using the latest 2022.3.33f1 LTS version of unity.

in the archer class script -

private void OnTriggerEnter2D(Collider2D other)
{
    if (other.CompareTag(_targetTag))
    {
        AddTarget(other.gameObject);
    }
}

private void OnTriggerExit2D(Collider2D other)
{
    if (other.CompareTag(_targetTag))
    {
        RemoveTarget(other.gameObject);
    }
}

private void AddTarget(GameObject target)
{
    if (!_targets.Contains(target))
    {
        _targets.Add(target);
        if (_archerFSM.CurrentState.GetType() != typeof(ArcherShootingState))
        {
            _archerFSM.TransitionToState<ArcherShootingState>();
        }
    }
}

private void RemoveTarget(GameObject target)
{
    if (_targets.Contains(target))
    {
        int targetIndex = _targets.IndexOf(target);
        _targets.Remove(target);
        if (_targets.Count == 0)
        {
            _archerFSM.TransitionToState<ArcherIdleState>();
        }
        else
        if (targetIndex == 0)
        {
            OnTargetChange?.Invoke();
        }
    }
}

in the enemy target script (the objects that added to the list of targets) -

using UnityEngine;

public class ShroomEnemy : MonoBehaviour, ITargetable
{
    [SerializeField] private float _delayBeforeLeaving = 5f;

    private float _timer = 0f;

    public void Initialize(Vector3 position)
    {
        transform.position = position;
    }

    public void ExecuteHitEction(int damage)
    {
        Debug.Log($"I took {damage} damage");
        Destroy(gameObject);
    }

    private void Update()
    {
        _timer += Time.deltaTime;
        if (_timer >= _delayBeforeLeaving)
        {
            Destroy(gameObject);
        }
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        IHaveDamage damager;

        if (other.gameObject.TryGetComponent<IHaveDamage>(out damager))
        {
            ExecuteHitEction(damager.GetDamageValue());
        }
    }
}

all the times when I debugged it with exact "debug.log"s, the problem always started from “OnTriggerEnter2D” of the ShroomEnemy class which is called by the arrow which is killing it and calling Destroy(gameObject).
right after that debug log was sending me a message that “ontriggerexit” from the archer class is called where it tries to remove it from the list, and only then debug logs show that the same shroom enemy object tries to enter archer collider via ontriggerenter method. So my guess that “Destroy” method messing up something somehow. (MB it making immediate call for ontriggerexit and not actually postponing them. if not, then i don’t know, that was my best guess as to why it’s happening like that). I also remember that one version of Unity had a bug when the “awake” method was called twice(so I don’t think that something like that is imposible).

You’re misinterpreting my point in that there’s nothing wrong with 2D physics and that it absolutely won’t produce an Exit followed by an Enter if there are contacts between a single pair of colliders and it’s not about believing you or not, or suggesting you’re not encountering an issue. I’m sure you’re seeing something but I am telling you how it works underneath so that what remains is an issue with your script or a misunderstanding of how something works. Physics callbacks are one of the most battle-tested and unit-tested parts of physics and there’s not been a changed to it in years.

If there were to be an issue there’d need to be some simple reproducible evidence but simply looking at a script isn’t going to help much; we’d need to see a project that includes objects and settings similar to a bug report.

Do you know that if you destroy an object and you have the Physics2D setting on of Unity - Scripting API: Physics2D.callbacksOnDisable enabled (default) then you’re asking for a callback immediately because it’s being disabled/destroyed? Note that this has been in Unity for a very long time and might be worth checking that setting and disabling it if it’s on and you don’t want this default behaviour.

Awake callbacks (etc) are nothing to do with physics and don’t affect physics callbacks. As I’ve said above, the physics callbacks happen ONLY during the simulation step. A simulation step doesn’t happen during “Awake”.

If none of the above helps then I would suggest putting together a minimal project with the most simple setup you can and provide a link to it. If this is a fundamental thing then that should be easy. Strip out all non-relevant stuff and provide a simple description of how to reproduce, what you expect to happen and what you see happening. If you provide a link here, I’d be happy to debug it for you.

It might also be a different object with the same name or something. If you can’t figure it out in any other way, add a Debug.Log for every enter and exit, and log both the collider’s name and instanceID.

Okay, so I first tried to create a new clean and empty project to test my initial suggestion - that it happens when an enemy spawns right on top of the arrow. But this went fine and logs were as intended - first enter then exit. So I still don’t know when and what exactly braking it and still investigating it. Il give u this test project, it’s not minimal, but it’s small anyway. git hub link to project

It’s already populated with debug logs and to just test it for reproducing the error just hit play. The error does not happen with the first or second enemy. It usually happens on enemy numbers 40-60.

U can see on this screenshot how before the error happens it all starts with the selected debug log on Shroom_44. It took damage and first called both Archer and Archer(Clone) OnTriggerExit’s and only then it called OnTriggerEnter’s leaving our list populated with a destroyed object resulting in null ref.

The most relevant scripts is “ArcherTriggerHandler” where we add/remove these Shrroms to lists in on trigger enter/exits, and “ShroomEnemy”. On the scene there is a spawner object to spawn new Shrooms enemies, it specifically creates new shrooms with new names, so we could not have the same names on different objects. (it anyway doesn’t matter cause we add/remove from the list by the object itself if the list contains a reference to it) - answering a message from Baste.

Yeah, the project has a lot going on and that’s not easy to figure out not being familiar with all the working parts: poolers, spawners, timers, FSMs etc.

I ran the project for 10 minutes and nothing went wrong here using Unity 2022.3.38f1 I’ll try again later when I get some time.

Hard to follow the logic here but I notice that you’re not using layers to ensure you only get the appropriate callbacks for things that should contact so you get callbacks for everything and you use components to see if they’re relevant. Maybe that’s related.

Any comment on this? It’s on as expected so you’ll get the behaviour described.

“I ran the project for 10 minutes and nothing went wrong here using Unity 2022.3.38f1 I’ll try again later when I get some time.” that’s interesting… I should try another version.

Regarding poolers, spawners, timers, FSMs etc. - they basically irrelevant to our problem from (enemys that spawned - don’t use pool or factory, its only used for arrows) the only thing relevant is spawner that spawns our Enemys (Shroom’s) and timer that destroy it after some time if it wasn’t killed with arrow. Altho it seems like problem never occurred when its destroyed from timers (never showed in logs, only when it was destroyed via arrow kill, which means from ontriggerenter (enemy+arrow) call).

About disabling physics2D callbacks - i didn’t do it cause as i understood its gonna absolutely completely never call “ontriggerExit2D” callbacks on destroys". right? and we obviously don’t need it. It not like its gonna just postpone call to later, right?

As I’ve said, with that setting off, it’ll only perform callbacks when the simulation runs. With it on, if you call disable/destroy, you’ll get a callback on that pair of colliders immediately.

In other words, this setting is an extra step it does if you want a callback immediately when you disable/destroy. In comparison, this option isn’t available in 3D physics as callbacks there only ever happen when the simulation runs and you don’t get callbacks if you disable/desrroy a collider.

Note that we’ve now got a bug report but the same situation applies. I cannot see an Exit being called before Enter.

Also note from the bug report:

MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
ArcherShootingState.ChangeTarget () (at Assets/Scripts/Models/Units/Archer/States/ArcherShootingState.cs:82)
ArcherTriggerHandler.RemoveTarget (UnityEngine.GameObject target) (at Assets/Scripts/Models/Units/Archer/ArcherTriggerHandler.cs:104)
ArcherTriggerHandler.OnTriggerExit2D (UnityEngine.Collider2D other) (at Assets/Scripts/Models/Units/Archer/ArcherTriggerHandler.cs:75)
UnityEngine.Object:Destroy(Object)
ShroomEnemy:ExecuteHitEction(Int32) (at Assets/Scripts/Models/Units/Enemys/ShroomEnemy.cs:17)
ShroomEnemy:OnTriggerEnter2D(Collider2D) (at Assets/Scripts/Models/Units/Enemys/ShroomEnemy.cs:35)

This is not showing Exit before Enter. It’s showing Enter, you destroying the object and because you have the option on as I’ve mentioned a few times above, it immediately calls Exit.

sry for not answering for so long. (motherboard died, waited till new comes). so I now have fresh windows, and new Unity version (Unity 2022.3.41f1) I still had same bug in it (now even with enemy number 12, in picture)


as u can see from the picture, first there is OntriggerExit2D calls in the log by Shroom_12 (which Is called when we destroy an object) on both archers (origin and clone) and only then we have OnTriggerEnter2D calls by the same Shroom_12 also on both archers. So i still do have exits happens before enters even with another version of Unity, so i wonder what else could be different between mine and ur’s setup so u didn’t have such interaction in 10 minutes of testing. :thinking: Could it be faster computer (like may it be so framerate matters)?

Regarding the “Physics2D.callbacksOnDisable” setting, again, it will remove these calls completely from destroys, and I do need them. My problem that object calls enter methods after it already called exits beforehand for same pairs as u mentioned. I don’t know if its rly impossible on physics side as u mentioned or not, but may that’s not physics problems but how and when ontriger methods call’s itself. i dunno, but regardless of why - the logs show that for saim pairs on trigger exit definitely was called before enter. (maybe pair was already created on physics levels, but methods calls itself was timed wrongly somehow idk). Also this problem still seems very random. Sometimes it also appears for me only after 200 shrooms already spawned. It is not very consistent. Still cant rly see what exactly can cause this. As we also can see from logs in picture, when enter called by shroom_12 (which called exit first before this message already so its Destroy method already called) its not yet actually destroyed, cause in next message we can see how its name get correctly pulled from gameobject and added to a list, so the object still exists, which still points me to the thinking that it SHOULD happen when exit and enter called in same frame (cause actual destruction of objects postponed to the end of frames), so enemy shroom object had to spawn inside arrow colider which will kill him already. (but for some reason its not always the problem, just some times…)

For now my solution was rly turn off “Physics2D.callbacksOnDisable” and just implement my own code that will remove objects from list in onDestroy methods. Tested it with 1000+ spawns already and seems like OnDestroy is always called after ontriggerenters even when its same frames with no exceptions as it should be expected :-). And yet even tho I can workaround it, the problem still feels like a bug or my poor understanding of Unity mechanics. So if its a bug I would be glad to inform u about it and keep giving any insight that I can, or if its just my poor understanding would rly apritiate some enlightening.

I also found In my mail box answer to my bug report issue where they say they were able to reproduce the same bug and exception even tho u couldn’t for 10 minutes. So that’s already a good sign I guess, that its not only my end. Although they were mostly focusing on the null ref exception itself and answering me on why that happened (where they’r explanation, btw, was just completely wrong) rather than on the fact that prior to the null ref exception, we were getting logs of onTriggerExit calls before onTriggerEnter calls on same pairs and completely ignored that, which was the actual topic of a bug report. And they marked it as closed. I answered them to their message, pointing out to some details, but I don’t know if they reopen issues that were marked as closed or not and would they even look at my answers or not.

Any way. Back to the point, the null ref exception itself is not the problem. I could change code in such a way that it couldn’t appear. It appears in the first place because the way I coded relied on that Exit couldn’t be called before the object actually called enter trigger, and that was where all my coding logic broke resulting in a null ref exception. The OnTriggerExit calls being called before Enters don’t make any errors on their own or doesn’t throw any exceptions. We only could see in logs that they happened exactly like that, and that’s it.

well this is a message of a null ref exception it self. Why would it tell what happened prior to this error and even say something about that OnTriggerExit called before OnTriggerEnter on same pairs? its just basic null ref exception message pointing to the line where it appeared. And as I told in actual console prior to this message, as I already pointed out many times and with 2 different screenshots already u can see my custom debug logs that tells which object enters and exits on which coliders, and where this Bug appears. As I already told the null ref exception itself is not the focus here.

And as for “you destroying the object and because you have the option on as I’ve mentioned a few times above, it immediately calls Exit.” - yes, but the problem that it calls exit before the enter even happened, and only after it it actually calls enter to a colider which he first called exit via destroy call. (sounds weird) :slight_smile: