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 :wink:
  • 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 :wink:

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.

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).