Discrepancy with ColliderDistance2D "minimum separation" distance

Maybe I’m just doing something wrong, but I’m unsure of what’s causing this issue.

I have a few, simple lines of code (lines 24 and 27) that are behaving differently on sloped surfaces depending on how far into the sloped surface my character is sitting.

[SerializeField] private LayerMask collisionMask;

private BoxCollider2D boxCollider;

private void Awake()
{
    boxCollider = GetComponent<BoxCollider2D>();
}

private void Update()
{
    DetectAndResolveCollision();
}

private void DetectAndResolveCollision()
{
    // Get the collision/overlap
    Collider2D collision = Physics2D.OverlapBox(transform.position, boxCollider.size, 0, collisionMask);

    // Get the collision distance info
    ColliderDistance2D colliderDistance = collision.Distance(boxCollider);

    // Find the shortest resolution vector
    Vector2 resolutionVector = Mathf.Abs(colliderDistance.distance) * colliderDistance.normal;

    // Resolve the collision by moving out of it by the shortest amount
    transform.Translate(resolutionVector);
}

I’ve already discovered a superficial reason for my problem that will hopefully be better explained by this picture:

What I’ve noticed is that if only one corner of the character’s collider is in the slope, the resolution occurs as expected (as shown in the top half of the picture). However, if more than one corner of the character’s collider is in the slope, the shortest resolution is defined as the distance from the center of some side/face of the box collider to its closest point on the outside of the slope. Therefore, if 3 corners are in the slope, the distances from the center of two sides/faces of the box collider to their closest points outside the slope will be compared, and the shorter distance will be used in the “resolution” by pushing this center point out of the slope. The problem, of course, is that the entire collider is not pushed out of the collision when this happens, so I would have to run the collision check a second time, which seems like an incorrect approach.

Why is this happening? I thought ColliderDistance2D - and, more specifically, Collider2D.Distance, which returns a ColliderDistance2D object - was always supposed to give you the shortest distance that moves the ENTIRE collider out of its collision. The Unity docs even say that Collider2D.Distance “calculates the minimum separation of this collider against another collider.” How can the “minimum separation” be a value that doesn’t fully separate the colliders?

I’ve already been using trigonometry to figure out a lot of different ways to resolve collisions, and I know I can do that here too, but I was hoping to overcome this discrepancy with a built-in solution that already exists in Unity. I would appreciate any help or explanations that can be offered.

For the primary case of them being separated it works without iteration however, when overlapped, you are correct in that another iteration might be required and this info should be added to the docs. We don’t do this for you as you’d always pay for it even when circles and polygon/vertex are involved but the docs don’t state that and should.

FYI: We don’t perform any calculations here and defer to the following Box2D method here: box2d/src/collision/b2_distance.cpp at 1025f9a10949b963d6311995910bdd04f72dae6c · erincatto/box2d · GitHub

The b2Distance method gives us a separation when overlapped, however that can be an edge/edge which as you’ve found won’t necessarily give you a full separation. For a shape involving a circle it should always work and polygon/polygon where edge/vertex is involved too however when polygon/edge are involved it won’t with another iteration. Also note, I’m talking here about single convex primitives. If you do this with more complex Unity colliders that maintain many shapes such as PolygonCollider2D, TilemapCollider2D, EdgeCollider2D then it also might required multiple iterations. The reason is that those can produce an overall concave region even though they are composed of individual convex primitives.

Either iterating up to a hard limit until (distance > Mathf.Epsilon) works but for primitives, simlpy doing it twice as you suggest works perfectly fine.

Sorry for the confusion. I’ll make a note about adding this info to the docs.

1 Like

Yes. I am also facing this issue since last week. The above solution too didn’t work UPSers

I don’t follow. Nothing has changed and this isn’t some bug. What do you mean the solution above didn’t work? What solution? You mean doing it more than once to solve an overlap? That does work so you must be misunderstanding.

I duplicated the above which shows iteration #1 doing what the original poster said it does and then iteration two which (because it’s a vertex/edge) moves it out of overlap.

https://gyazo.com/d72bc59eff0e9bdff5c0ecbd81cd241c

EDIT: Unless of course I’m replying to a BOT because the lack of info and vague reply suggest I might be. :wink:

Thanks for the help; this cleared up some of the confusion.

Another potential issue I noticed is that when I check ColliderDistance2D.isOverlapped it perpetually returns true (on the next frame), even after resolving the collision using ColliderDistance2D.distance * ColliderDistance2D.normal. Why is this happening? It’s not a huge deal, but I’m using that bool to see if I should run my collision logic each frame, and this is unnecessary after resolution has already occurred. It doesn’t make sense to me why the distance returned from ColliderDistance2D would be negative (representing an overlap) after translating by the resolution vector; visually it doesn’t appear that there’s an overlap either.

It’s simply checking if the distance is < 0 which indicates an overlap. See here. I really can’t say anything more about. If you suspect a bug then please submit a bug report with a simple reproduction project attached and we can take a look.