Search Unity

Physics2D.OverlapArea returns incorrect value with yield return null

Discussion in 'Physics' started by _eternal, Jun 10, 2016.

  1. _eternal

    _eternal

    Joined:
    Nov 25, 2014
    Posts:
    304
    I accidentally fixed a bug I was having, and I'm trying to understand why. Here's piece of a simple coroutine to check if the player is overlapping with a projectile:

    Code (CSharp):
    1.  
    2. while (Physics2D.OverlapArea(bulletBounds.min, bulletBounds.max, 1 << LayerMask.NameToLayer("Projectile")) && state != MyStates.Dead)
    3.         {
    4.             bulletBounds = myBullet.GetComponent<Collider2D>().bounds;
    5.             gameController.DrawGizmoBounds(bulletBounds);
    6.             yield return new WaitForFixedUpdate();
    7.         }
    I originally had "yield return new WaitForFixedUpdate()" as "yield return null," and OverlapArea always returned true. It didn't matter where my character was - it stayed true until the bullet itself was destroyed, ending the coroutine along with it.

    I get that FixedUpdate works out of sync with Update, but in order for a new frame to render to the screen, Update has to be called, right? How is it possible for OverlapArea to have returned true even when I could visibly see that the colliders weren't overlapping?
     
  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,496
    Not sure if I follow what you're asking but here goes.

    Colliders (or more to the point, the Rigidbody2D they are attached to) only actually move per fixed-update under their own velocity unless you are moving the Rigidbody2D yourself explicitly (which you shouldn't do).

    Visually, during update, it can appear that colliders are separated and are moving if you are using Rigidbody2D interpolation. Interpolation doesn't move the body or colliders, it just updates the Transform component from the the previous position to the current position during frames in-between fixed updates. The body/colliders are still at their current position. This is why Rigidbody2D.position and Transform.position can be different when using interpolation/extrapolation.
     
    _eternal likes this.
  3. _eternal

    _eternal

    Joined:
    Nov 25, 2014
    Posts:
    304
    Ah, now that's interesting.

    This bug appeared when my platformer character was dashing through a projectile. The dash is performed by flipping isKinematic on and directly moving the Transform. Does the Rigidbody2D itself not move unless it's affected by physics? In this case, when isKinematic becomes false?

    I was also under the impression that FixedUpdate is generally called more frequently than Update, unless heavy processing is slowing Update down to <60fps. I believe my FixedTimestep is set to something below the default (possibly 0.01). So it doesn't sound like the Transform should be "ahead" of the Rigidbody2D/Collider by any more than a frame - or if anything, it should be behind/equal.

    Although... if there's no default framerate cap in Unity, perhaps my Update/Game Logic step was being called >60 times per second this whole time? And my Physics step was being called 100 times per second when set to 0.01 timestep?

    Sorry for all the questions - I've read about how to use FixedUpdate vs Update, of course, but I've had trouble internalizing the differences, especially as I generally only test the game on my development PC without artificially capping FPS.
     
  4. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,496
    FixedUpdate will be called at the fixed-update interval (default is 50Hz) but you can obviously set this to whatever you want. Update is called once per frame render. FixedUpdate can be called multiple times in succession without an Update if rendering is taking too long and the fixed-update needs to 'catch-up'.

    If rendering is taking less time than the fixed-update interval (higher FPS) then you'll get the same/more Update callbacks than FixedUpdate callbacks. If rendering is taking more time than the fixed-update interval (lower FPS) then you'll get more FixedUpdate callbacks than Update callbacks.

    Rigidbody2D move when they have velocity. Velocity can be set directly or via forces you set / gravity (unless it's static or kinematic). If you set the Transform directly (which you shouldn't) then that will instantly reposition the Rigidbody2D to be at the Transform position. Any contacts at that position will be recalculated during the next fixed update.

    You should move a Rigidbody2D by using Rigidbody2D.MovePosition.
     
  5. _eternal

    _eternal

    Joined:
    Nov 25, 2014
    Posts:
    304
    Thanks for the explanation - this has been really helpful for my overall understanding. After a bit more digging, however, this specific issue still doesn't make sense. Here's my new loop:

    Code (CSharp):
    1.  
    2. while (Physics2D.OverlapArea(bulletBounds.min, bulletBounds.max, 1 << LayerMask.NameToLayer("Projectile")) && state != MyStates.Dead)
    3.         {
    4.             gameController.DrawGizmoBounds(bulletBounds);
    5.             gameController.DrawGizmoPoint(bulletBounds.max);
    6.  
    7.             bulletBounds = myBullet.GetComponent<Collider2D>().bounds;
    8.             Collider2D collidingWith = Physics2D.OverlapArea(bulletBounds.min, bulletBounds.max, 1 << LayerMask.NameToLayer("Projectile"));
    9.  
    10.             print(collidingWith.transform.position + "     " + rigidBody.position + "    " + transform.position);
    11.             print(collidingWith.IsTouching(heroCollider));
    12.             print(collidingWith.name);
    13.  
    14.             yield return new WaitForFixedUpdate();
    15.         }
    This while loop continues infinitely once it's started, and I can't figure out why. Here's a screenshot taken during the loop:



    The point in the top-right is bulletBounds.max, while the big magenta rectangle is just bulletBounds. According to the print statements:

    - rigidBody.position and transform.position are the same, and they change while I move around; collidingWith.transform.position remains the same no matter what

    - collidingWith.IsTouching is always false

    - collidingWith.name is always the laser's name (not some other object)

    These are all the factors I can think of. Everything seems to indicate that the loop should break, yet it doesn't. Any ideas?

    EDIT: ended up fixing this by implementing my own simple version of OverlapArea that compares one Bounds's max and min against another's. Maybe the original error had something to do with layer masks, but I honestly don't know.
     
    Last edited: Jun 17, 2016