Search Unity

OnTriggerEnter called one frame after collision occurs

Discussion in 'Physics' started by dgoyette, Jul 26, 2019.

  1. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    I'm confused by some collision behavior with some objects of mine. I have one stationary object, a sphere, which has an OnTriggerEnter script on it. I also have one moving object, an icosahedron, which in this example is moving up towards the sphere. The sphere has a trigger collider, and the icosahedron has a non-trigger collider.

    What I'm finding is that OnTriggerEnter isn't called on the first frame that the objects overlap. Instead, it's called one frame later.

    Here are the two objects, paused on the first frame where they visually appear to overlap:

    upload_2019-7-26_15-32-39.png

    And here are just the colliders, showing them clearly overlapping:

    upload_2019-7-26_15-34-32.png


    However, OnTriggerEnter isn't called that frame. Instead, it's called on the following frame, where the objects aren't actually overlapping anymore:
    upload_2019-7-26_15-35-55.png

    I have the moving object's collision detection set to Continuous Dynamic for the most accuracy. I realize it's possible for a very fast moving object to bypass the collision detection if it's moving fast enough, but what puzzles me here is that OnTriggerEnter isn't called when the two objects initially overlap. It's only on the next frame that the collision occurs.

    From the docs on OnTriggerEnter, it says, "The GameObject with Collider.isTrigger set to true has OnTriggerEnter called when the other GameObject touches or passes through it."

    I also tried calling Physics.OverlapSphere in FixedUpdate, expecting more accurate results, but it's the same thing. OverlapSphere doesn't find any results on the first frame where the colliders overlap. Instead, it only finds results on the subsequent frame.

    In this case, the icosahedron is clearly touching the sphere, so I'm not sure why OnTriggerEnter isn't being called. Is there some Unity physics/collision convention I'm missing?
     
    Last edited: Jul 26, 2019
  2. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    Looking a bit more into this, I wondered how the behavior compared to OnCollisionEnter. I started a new project to make sure this wasn't something weird in my main project. What I found is that OnCollisionEnter, if anything, is called a bit earlier than I might have expected, but that it's definitely called some number of frames before OnTriggerEnter.

    I set up a simple scene with two spheres, one trigger and one not, at the bottom. Two other spheres with rigidbodies fall into to them. The left sphere is a non-trigger collider, while the right sphere is a trigger collider. The left sphere has an OnCollisionEnter script that changes its color to red on collision, and similarly the right sphere has an OnTriggerEnter script that changes it to red on collision. The dynamic spheres are dropped from the same height. I then stepped through the frames to observe the timing of the events:

    Collision.gif

    On the left, the OnCollisionEnter fires a frame before OnTriggerEnter. It fires exactly at the point that I would have expected the OnTriggerEnter to fire. But you can see on the right that the two sphere overlap for one frame before OnTriggerEnter is called and the right sphere changes to red.

    I'm not sure if this has always been the behavior, but I believe I'm left unable to test for the presence of colliders accurately. Again, maybe I'm missing something obvious, maybe this is just the way physics works in Unity, or maybe it's a bug.
     

    Attached Files:

    GRASBOCK and Raptosauru5 like this.
  3. Edy

    Edy

    Joined:
    Jun 3, 2010
    Posts:
    2,512
    I'd file a bug report with the repro, so at least the issue could be looked at.
     
    Raptosauru5 likes this.
  4. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    I'll do that. I went back to 2017.4 LTS, to see if it was happening there, and it seems to be the same behavior. I don't know if that implies it's "always" been this way, or if 2017.4 LTS has picked up physics changes that were considered bugs, causing its behavior to be aligned with 2019.1.

    Also, a correction to my previous post: It seems that using Physics.OverlapSphere does behave as expected, and that's how I've worked around this in my game. Where previous I was using OnTriggerEnter to detect overlap, I'm not using Physics.OverlapSphere, and it's giving me the expected results. So that's good, at least. Here's an example of the difference between OnTriggerEnter and Physics.OverlapSphere. On the left I'm using OnTriggerEnter to detect the falling sphere, and on the right I'm using Physics.OverlapSphere:

    TriggerVsOverlap.gif
     
    Last edited: Jul 27, 2019
    Raptosauru5 likes this.
  5. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    Submitted this as bug #1172583. Interesting it really doesn't seem to be an issue with how far into the trigger the falling sphere gets. If I turn gravity down to -0.1 instead of -9.81, the sphere falls very slowly, but it still only called OnTriggerEnter the second frame that the colliders overlap. So it seems like OnTriggerEnter's logic only fires when two colliders overlap for two consecutive frames. I tried an experiment where I let the falling sphere intersect the lower sphere. Then I dragged the upper sphere up so they weren't overlapping anymore. OnTriggerEnter was not called in that case. So, it's not that OnTriggerEnter is called a frame after the collision occurs; It's that OnTriggerEnter seems to need two consecutive frames of overlap before considering the collider triggered.
     
    GRASBOCK and Edy like this.
  6. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    GRASBOCK and Stexe like this.
  7. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,500
    I can't speak for 3D but note that Box2D has similar behaviour.

    This comes from when new contacts are generated. In the case of Box2D when you step the simulation, the first thing it does is create new contacts and updates/destroys existing ones as can be seen here. It then then solves both the contact and joint constraints which integrates positions/velocities (forces, gravity etc) and adjusts positions based upon the solver impulse solution. The step then finishes and Unity reports the current contacts and writes-back the body poses to the Unity Transform system.

    The above means that the contacts prior to solving are what you get for 2D or in other words, the contacts that were solved. This applies to discrete collision detection. For continuous there are several steps that can adjust the existing contact so the contact gets updated and is more relevant to the current position.

    This has been discussed on the Box2D forums and here a few times. The end result is a compromise where the simulation step is preceeded by ensuring all contacts are up to date before solving. You could do this after solving to ensure the contacts reflect the current post-solve poses but that can lead to other problems because you're not necessarily solving the correct state. You could also (additionally) update all contacts at the end of the simulation step but that'll potentially have a large performance impact.
     
    Last edited: Aug 1, 2019
    gamedevgarage and dgoyette like this.
  8. dgoyette

    dgoyette

    Joined:
    Jul 1, 2016
    Posts:
    4,196
    That's an interesting write up. I probably don't strongly understand it, but I think I get then point. To oversimplify, the physics step needs to move stuff around, and it needs to determine the state of the world (in order to fire events like OnTriggerEnter). At the beginning of the step, it's kind of like you're checking where everything ended up after all the movement happened by the end of the previous step. Maybe. :)

    Thanks for taking the time to explain the behavior.
     
  9. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,500
    Yes, in simple terms it's a case of calculating the current set of contacts based upon where everything is (these will be reported when the simulation step finishes) then solve those contacts (remove overlaps etc) and finally update the body positions. These positions are not necessarily where the contacts were made but the callbacks report the contacts nevertheless. These new positions may have different contacts but those will be done when the simulation runs again.

    Well I tried to keep it short and reduce the waffle. :)
     
    gamedevgarage and dgoyette like this.
  10. JesterGameCraft

    JesterGameCraft

    Joined:
    Feb 26, 2013
    Posts:
    452
    I know this is an old thread but just wanted to mention that Physics.OverlapSphere also seems delayed by a frame for me, when it comes to collision detection. The actual solution for me turned out to use Collider+Rigidbody set as Kinematic (is true).

    In my use case it's not just detection of collision/trigger that is a problem but mainly objects going through each other at high velocity. Let's say I have a sphere and a cube. The sphere is moving at very high speed to hit the stationary cube. Here are combinations and results of what I saw:

    Sphere
    - sphere has a rigidbody and is not kinematic (is moved by force)
    - sphere is moving very fast and hits the cube

    Cube
    - cube has box collider and no rigidbody -> result is that sphere is blocked by cube (GOOD)
    - cube has box collider and rigidbody set to kinematic (= true) -> result is that sphere is blocked by cube (GOOD)
    - cube has box collider and rigidbody not set to kinematic (=false) -> result is that sphere moves through the cube (BAD)
    - cube has box collider and rigidbody set to kinematic (=true) and is set as a trigger -> result is that trigger is not fired when sphere goes through the cube (BAD)

    Is this expected behaviour? Anyone else seen this?
     
    Last edited: Dec 5, 2021
    JMeer likes this.