- Home /
OnCollisionEnter event seems to be called late.
Hi people..
I was examining the physics in Unity and here is what I did for it:
- Created a sphere and a cube.
- Scaled the cube to make it act as a floor.
- Placed the sphere above the floor(cube).
- Attached a rigged body to the sphere.
- Configured the gravity as (0,-200,0) from the Physics Manager.
- Wrote a script which logs out when OnCollisionEnter event happens.
- Attached the script to the sphere.
- And finally pressed the Run button.
Sphere begins to fall down. When it hits the floor for the first time OnCollisionEnter event is not called. It is actually called on the next fixed frame. Please see the screenshots below.
Start
First hit
Next frame after the first hit
So why is this event called on the next frame? Am I missing something?
I had a quick look with the profiler and at least as far as whether or not the event is being called late you're definitely not being short changed; it's co$$anonymous$$g straight away right out of Physics.Simulate() and not queued into some extra loop, but it's odd that it's different to what is rendered.
Sorry I was editing my comment when you replied. As I modified, it's being called directly and it's not piled in waiting on the next update frame. I guess it must have something to do with the way the physics is simulated on a different loop than Update.
Actually, that would make a lot of sense. Perhaps it works like this:
The physics are simulated fully and the next frame is rendered. The physics simulation loop then steps through all the fixed time step iterations and processes events accordingly. If it were like this the buffer would most likely be presented to the screen before these physics are processed, and the editor might just stop there so you can have a preview of the the result of the next frame, and then when you hit the frame advance it processes all of the physics events.
I have no idea whether or not that is the case or not, but that's certainly how it would appear.
Answer by Datael · Jun 06, 2012 at 03:11 PM
After writing a guess about this I have come to realise that this is the exact reason for the collider-based bug I was experiencing today on a project I'm working on right now.
At least in the editor the following is true:
Update() is called, and then the rendering after Update() has been applied is carried out. The new positions of your game objects are then reflected in both the Scene and Game views. However, to do anything based on the new collider positions as a result of changes in Update(), you have to yield and WaitForFixedUpdate() before the colliders will be picked up by Physics methods in their new positions.
Here is a test that you can run yourself to verify this:
Set up a scene as you had it before, only make the following changes:
Set the Sphere's position to (0, 4, 0)
Set the Cube's position to (0, 0, 0)
Set your Camera's position to (0, 0, -10)
Disable Use Gravity on the Sphere's Rigidbody component
Then add the following scripts:
Add this script (SphereBehaviour.cs) to your Sphere:
using UnityEngine;
using System.Collections;
public class SphereBehaviour : MonoBehaviour {
int frameId = 0;
void Update() {
if (frameId == 0) {
Debug.Log("First frame Update() on Sphere: Do nothing");
} else if (frameId == 1) {
Debug.Log("Second frame Update() on Sphere: Translate downwards");
transform.Translate(0.0f, -4.0f, 0.0f);
}
frameId++;
}
void OnCollisionEnter(Collision collision) {
Debug.Log("OnCollisionEnter() responded");
}
}
Next add the following script (CubeBehaviour.cs) to your Cube object:
using UnityEngine;
using System.Collections;
public class CubeBehaviour : MonoBehaviour {
[SerializeField] Transform sphereTransform;
void LateUpdate() {
Debug.Log("LateUpdate() on Cube Before fixed update:");
OverlapSphere();
StartCoroutine(_WaitForFixedUpdate());
}
IEnumerator _WaitForFixedUpdate() {
yield return new WaitForFixedUpdate();
Debug.Log("After fixed update on Cube:");
OverlapSphere();
}
void OverlapSphere() {
Collider[] hitColliders = Physics.OverlapSphere(transform.position, 0.5f);
Debug.Log("Sphere position: " + sphereTransform.position + " / Hit collider count: " + hitColliders.Length);
}
}
Don't miss this step:
ow select the Cube object and then drag the Sphere object onto the Sphere Transform property in the Cube's inspector. If you're getting an error it's because you haven't done this step (and I forgot to add it in originally! Sorry!)
If you step through this scene frame-by-frame as you were doing before you will see the following occur:
In the first frame, we did nothing so that we can see how everything is laid out and we can get our first results from our first OverlapSphere(). Note that the Hit Collider Count in the shows it's hitting 1 collider because it's hitting the Cube game object's collider (itself). Note also that the results after WaitForFixedUpdate() are not showing yet despite the rendering already being completed. This is our first sign that the rendering occurs before the physics is updated.
Note that I cleared the console before advancing the frame to reduce clutter.
After frame two has been rendered we finally have our first result from the _WaitForFixedUpdate() coroutine in CubeBehaviour. Notice how the position of the Sphere is still being shown to be in its original position. This code was executed before the Scene and Game views were rendered.
It is here that the Sphere has been translated downwards into its new position that should cause it to hit the Cube object. The new transform position is reflected in the LateUpdate() of CubeBehaviour. Game Objects' positions have all been updated but we're still only showing one hit from our OverlapSphere() in both cases.
Note again that I cleared the console before advancing the frame to reduce clutter.
After we have advanced to the third frame we finally have results that we would expect to have:
You can see at the very top of the Console output what we have been waiting for: OnCollisionEnter(). Physics has now finally noticed that the Sphere is colliding with the Cube object. Remember that we concluded in the last frame that the code in the coroutine after the yield to WaitForFixedUpdate() is executed before the rendering of this frame but after the frame has been advanced. In both cases Hit Collider Count is now returning 2, the value we would expect.
From this we can conclude that, at least in the editor, the frame you can see rendered in both the Scene and the Game views is half-way between being fully updated: Update(), LateUpdate() and any translations on game objects that were made during those methods have been applied, however collider positions have not yet been updated. Therefore if you want to test for collisions that may result from these translations manually using OverlapSphere() and similar Physics-related functions (including Raycasts!) you have to yield and WaitForFixedUpdate() before you will get the correct results.
If you are interested and want to test using the raycast as well you can add the following line to the end of the OverlapSphere() method in CubeBehaviour.cs:
Debug.Log("Raycast Count: " + Physics.RaycastAll(Camera.mainCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0.0f))).Length);
Hope that clears it up :)
Answer by luckruns0ut · Jun 06, 2012 at 05:30 PM
The higher your fps, the higher the accuracy will be.
Answer by DanJC · Apr 28, 2013 at 12:15 PM
Just lost a day on this as well.
And when OCE finally fires and you check a collision point, that collision point comes from the data you could see rendered 1 frame ago. In other words, Collision.contacts[0].point isn't even on the object as you see it at all. Simply doing a test with Debug.DrawRay() from contacts[0].point will show this.
What's more screwy is when you collide from the wrong side:
The green line is contacts[0].point. It is called in the frame you see rendered, but is a point on where the blue sphere was last frame. What's more fun is the fact that last frame, the blue sphere had not collided at all. It was clearly not touching anything.
So colliding backwards is a little more derpy, but hey at least it called OCE on the right frame this time. Mmf. What a "feature".
Here's this too: