1
\$\begingroup\$

In Unity, how can I detect a collision between two game objects, but have them pass through one another, i.e. not block each other?

Specifically, I need the collision normal provided by the Collision2D object that comes with the OnCollisionEnter2D method. But I want to be able to move through the object that detected the collision, not get blocked by it.

PS: Checking the Is Trigger checkbox would mean the argument passed into the trigger method no longer contains the collision normal!

\$\endgroup\$
8
  • \$\begingroup\$ Hi there, I remember a recent post about having a player fall through a platform by ignoring collision between the two. It's not your question but does it relate / could it help? gamedev.stackexchange.com/questions/189021/… Instead of falling through, you seem to want to move through, which feels roughly the same. \$\endgroup\$
    – D.Kallan
    Feb 15, 2021 at 7:53
  • 1
    \$\begingroup\$ I'd swap the box to be a trigger to get the movement working as desired, then use a shapecast to get the collision normal when a trigger overlap occurs. \$\endgroup\$
    – DMGregory
    Feb 15, 2021 at 12:48
  • \$\begingroup\$ Can you tell us anything about the shapes of the two objects involved, and what kind of colliders they use? \$\endgroup\$
    – DMGregory
    Feb 15, 2021 at 13:19
  • \$\begingroup\$ BoxCollider2D is the one that needs to detect the collision. The incoming one is a CircleCollider2D. Basically, a round player colliding with a square floor plate. \$\endgroup\$
    – Ben
    Feb 15, 2021 at 13:20
  • \$\begingroup\$ @DMGregory I just ran into a related problem, where I have a ball colliding with a rock, and I want to use Vector2.Reflect to bounce it off. However, the physics engine stops the ball on collision with the rock. So by the time my calculation runs, the ball's velocity needed for the calculation is already 0. \$\endgroup\$
    – Ben
    Feb 16, 2021 at 8:25

1 Answer 1

2
\$\begingroup\$

Is there no way to tell Unity to "give me collision information", but "do not interfere with my game objects' movement"?

As I explain in this answer, the contact information you get from collision events is generated as part of the collision resolution process, where the engine is figuring out how to change the positions and movements of the colliding objects to remove penetration.

That's costly extra work it doesn't need to do to simply detect overlaps, so if you haven't asked it to alter the objects' trajectories, then it doesn't do all this extra work, helping your game run faster.

And that's not a problem, because for the rare cases where we need contact information without altering motion, we can get that through other means: the rich set of raycasting and shape-casting methods the physics engine gives us.

So, we can keep our moving object (or its obstacle) as a trigger collider, to save the costly collision resolution work and avoid fighting with the engine to undo it. Then we can use a script to fire shape casts to reconstruct the impact normal when we detect a new trigger overlap.

Here's an example of how we could do that with a circular body. Here I assume the circle is centered on the object's origin, but we can adapt this to work with off-center colliders too.

[RequireComponent(typeof(CircleCollider2D), typeof(Rigidbody2D)]
public class TriggerEdgeDetector : MonoBehaviour {

     public LayerMask layersToDetect;

     public UnityEvent<Vector2> OnHitWithNormal;

     Vector2 _oldPosition;
     CircleCollider2D _collider;
     Rigidbody2D _body;

     void Awake() {
         _collider = GetComponent<CircleCollider2D>();
         _body = GetComponent<Rigidbody2D>();
     }

     void FixedUpdate() {
         _oldPosition = _body.position;
     }

     void OnTriggerEnter2D(Collider2D other) {
         Vector2 travel = _body.position - _oldPosition;

         var hit = Physics2D.CircleCast(
                        _oldPosition,
                        _collider.radius,
                        travel,
                        travel.magnitude,
                        layersToDetect);

         if (hit && (OnHitWithNormal != null))
             OnHitWithNormal.Invoke(hit.normal);
     }
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .