- Home /
OnTriggerExit doesn't fire when changing RigidBody (bug? feature?)
I'm seeing the following funny thing:
when you change the RigidBody of a GameObject, and move it on the same frame, you do not get OnTriggerExit (on that object or on an object it intersects).
I have looked everywhere online and can't find anyone trying to do this or talking about this issue. In the Unity docs, I can't find a spec on what should happen in this scenario.
This is a deep thing we found in a big project and figuring out a way around this is very important for us.
My questions are:
Is this a bug?
If so can we get a fix ;)
If not, is there a workaround?
We have found a workaround, which involves OnTriggerStay (even though we don't get an OnTriggerExit, we do see that OnTriggerStay stops getting called). This seems computationally heavy - any insight/advice here?
We have spent dozens of hours on this issue and are keen to do whatever it takes to get to the bottom of this ;).
Here's the setup:
Scene has a Cube and a Sphere, as well as two rigidbodies (debugRigidBody1, debugRigidBody2)
Sphere is Kinematic Rigidbody Trigger Collider and never moves (RigidBody is on the sphere, IsKinematic, no gravity) (Sphere collider on Sphere, isTrigger checked)
Sphere NEVER MOVES OR CHANGES.
Cube has a non-trigger Collider on it. Starts out intersecting the Sphere in the scene. The Cube is not parented to anything and thus, at the beginning is thus what I believe one would call a Static Collider.
This script is attached to the Cube:
public class DebugCube : MonoBehaviour {
public GameObject debugRigidBody1;
public GameObject debugRigidBody2;
void Update () {
Debug.Log("---- ---- Update, frame is " + Time.frameCount + " ---- ---- ");
if (Time.frameCount == 1) Debug.LogError("timeout");
if (Time.frameCount == 2) {
transform.parent = debugRigidBody1.transform;
}
if (Time.frameCount == 6) {
transform.position += new Vector3(0, 0, .4f);
}
if (Time.frameCount == 8) {
transform.position -= new Vector3(0, 0, .4f);
}
if (Time.frameCount == 12) {
transform.parent = debugRigidBody2.transform;
transform.position += new Vector3(0, 0, .4f);
}
}
}
This script is attached to the Sphere:
public class DebugSphere : MonoBehaviour {
void OnTriggerEnter(Collider other) {
Debug.Log("Sphere OnTriggerEnter " + other.gameObject.GetInstanceID());
}
void OnTriggerStay(Collider other) {
Debug.Log("Sphere OnTriggerStay " + other.gameObject.GetInstanceID());
}
void OnTriggerExit(Collider other) {
Debug.Log("Sphere OnTriggerExit " + other.gameObject.GetInstanceID());
}
}
Running this in Unity (tried it in 5.1.1 and 5.1.2) I see that:
On Frame 2, I get an OnTriggerEnter
On Frame 6, I get an OnTriggerExit
On Frame 8, I get an OnTriggerEnter
I expect an OnTriggerExit on Frame 12 but I don't get one. (well, "expect" is a strong word. I want one ;). But not sure if I should "expect" one (bug or feature?)).
I notice that I get OnTriggerStays for frames 8, 9, 10, 11, but none for frames 12 and after.
For the purposes of further investigation, I made a modification of this code where, when (Time.frameCount == 12), I set transform.parent but comment out the line that changes transform.position. Here I see the curious behavior that on Frame 12 I get an OnTriggerEnter (ostensibly because, the new RigidBody is "entering" the Sphere's collider).
Please help me understand what's going on here! Please please please ;)
Hey - so, an update, in part thanks to the folks at Unity ;).
The issue seems to be clarified by looking at the wonderful Unity execution order diagram. You see, OnTriggers are called after FixedUpdate.
So, it seems that if you move something in, and out of a trigger in an Update, when later FixedUpdate is called it completely misses the relevant triggers.
We are now working on a solution where we, after moving, explicitly wait for a FixedUpdate before proceeding. There are many ways to go about this. One of them is using the "yield WaitForFixedUpdate" construct to have some code that fires after the FixedUpdate. This is a bit messy as it spawns a new thread which, in our particular application, causes some issues. So we are trying some other stuff out, all of which is way beyond the scope of this thread;).
To answer my original question, posed in the title -- this is not a bug but apparently a feature. Triggers are only processed after FixedUpdate, and if you do something in an Update that should cause a Trigger, then do something else -- well you may not get your triggers. If you need a trigger you should basically wait for it.
There is an alternate $$anonymous$$dset, which is to do all motion in the FixedUpdate which (as many of us know) is a general Unity principle that we should all try to follow. This still may lead to weird behavior, in that if multiple kinds of trigger-collisions happen, with reparenting and what not from within FixedUpdate, you may not get all your triggers. That's at least what we found when we did the trivial change of Update to FixedUpdate in the core code region.
The real lesson we learned is "wait for the trigger" aka "wait for the FixedUpdate". That guiding principle seems to promise a workable, robust solution. If this fails well expect me to post here again ;).
Answer by FortisVenaliter · Aug 21, 2015 at 08:35 PM
The problem is likely that you are moving the rigidbody yourself. Rigidbodies are defined by the physics system, and expect to only be moved by forces and torque. Changing the position manually "pops" it, which is an unexpected behaviour for the physics engine. It handles it pretty well, but it doesn't surprise me that it fails on some collision calls as a result.
Also, why would you use a rigidbody on a trigger collider? If it's a trigger, it can't properly interact with the physics system, so it seems like it would be redundant and superfluous. Try removing the RigidBody for the sphere and see if the collision works a bit better. There are pretty specific rules to how rigidbody colliders and non-rigidbody colliders interact. Read this page for more info.
But, the thing is, TriggerEnter does work for manual movement. It's working in the code above, which moves by hand. And I think it purposely works for non-physics moves in general. I sometimes use it that way, with no problems.
I think the entire error (which the OP should have put in the title!!!) is about changing the parenting on the same frame in which you leave(?) a trigger. I think this may be a real not-working thing. It may confuse the trigger about whether you were in it last frame.
Hi Owen - I tried to put it in the title - sorry if it was unclear. I fully agree that changing the parenting (aka changing the rigidbody) is what's causing the issue. That's the difference between frame 12 and 6 in my source code example.
$$anonymous$$y question is: is this a bug? is this a feature? what is supposed to happen when you change a parent, with OnTriggerExits?
Also - FortisVenaliter - I responded to your Answer below, as a separate reply (not comment) so you may not have gotten it, curious to hear your thoughts.
Answer by schkolne · Aug 23, 2015 at 05:00 AM
Cool some good thoughts thank you!
As for insight #1: It's true Unity frowns upon setting Rigidbody positions directly. I went in and changed the code on frame 12 to affect the Rigidbody position instead of the gameobject, like so:
if (Time.frameCount == 12) {
transform.parent = debugRigidBody2.transform;
debugRigidBody2.GetComponent<Rigidbody>().position += new Vector3(0, 0, .4f);
}
The behavior is the same and I don't get the OnTriggerExit that I'm looking for.
As for insight #2: The reasons for using a Rigidbody on the sphere are due to other parts of this system. That said, we could consider designing our system to not use Rigidbodies if that would help.... one reason we're using a Rigidbody is that, on this page, you can see at the bottom that the Kinematic Rigidbody Trigger Collider sends trigger messages in all cases and is the most general.
That said, i did a quick test and removed the Rigidbody from the Sphere. The behavior remains the same (no OnTriggerExit on or around frame 12).