How to wake up Rigibody2D within collider?

I have a moving object with Rigidbody2D and a CircleCollider2D. The object moves into a BoxCollider2D trigger zone. When the movement stops, the rigidbody falls asleep and the OnTriggerStay2D message is no longer called.

I would like to wake up this rigidbody from a MonoBehaviour on the BoxCollider2D when a specific event happens. I want to let the rigidbody fall asleep in other situations, so disabling sleeping entirely is not an option.

I’ve noticed that the exact behaviour I want is actually implemented by the BoxCollider2D component itself: When any value in its inspector changes, all rigidbodies within it are awoken and physics messages are called. This is very similar to what I would like to do. If something within my MonoBehaviour script changes, I want to awake all rigidbodies within the trigger zone.

Is there a convenient way to achieve this or do I have to somehow keep track of all the rigidbodies and call WakeUp on them manually?

There’s nothing special implemented there however it might look like that.

The way sleeping-state works is that wake/sleep is coordinated by things in contact known as a contact island. When a single body in a contact island wakes, they all wake. All bodies in a contact island have to be waiting to sleep before they will all sleep together. Modifying certain properties of colliders cause the collider to be recreated which ends-up waking islands it’s connected to but can be expensive depending on what is being recreated i.e. composites, tilemaps or complex polygon colliders.

You can see this by modifying stuff that is in contact and noting that everything in contact wakes too. Contact islands are not connected via a static rigidbody. More detail can be found here in the Box2D source.

Anyway, back to your problem. It might be best to add a Rigidbody2D set to static body-type to your “trigger zone” BoxCollider2D. Because you cannot wake static bodies, a way to wake the contact island it is part of is to simply toggle the Rigidbody2D.simulated property i.e.

 var body = GetComponent<Rigidbody2D>();
body.simulated = false;
body.simulated = true;

This will not cause the body, colliders or joints to be recreated which can be slow but instead simply takes the body, collider and joints out of the simulation. This will remove any contacts too. When you set simulated to true it simply adds the existing in-memory objects back into the simulation which really only requires them adding back into the internal linked-lists so it’s probably the quickest way to do it.

Note that when you don’t add a Rigidbody2D then it is also static but you also don’t have access to the static Box2D body known as the ground-body. Even if you did, you wouldn’t want to use this as it’d wake everything touching static colliders!
If you have a reference to anything you already know is in the trigger that is a dynamic body then you can simply call “WakeUp” on it but obviously you might not know that at the time.

Note: The only problem I can see with this is that you’ll effectively enter/exit the trigger state so you’ll not just continue getting “stay” callbacks. A quick hack might be to add a kinematic body as a child of the trigger zone with a small circle overlapping the trigger that doesn’t collide with anything else. You can then turn the simulation on/off of that kinematic body and it’ll wake everything without changing the callback state of the trigger.

1 Like

Thank you! That was a great explanation. :slight_smile:

Not a problem. Look at the last paragraph I just added as I think it’ll be a problem for you in that you’ll not just get continued “stay” callbacks but will get “enter/exit” if you toggle the simulated state of the trigger itself. Doing this on a special child kinematic body will achieve the same thing without affecting the trigger body.

In all honesty, it’d be useful if you could “force” a wake-up on a static body to wake the contact island but it’s not something that has ever been requested.

Note that you can also just use “Collider2D.GetContacts()” (the overlap that populates a list of Collider2D not ContactPoint2D) and wake up all of those by using “Collider2D.attachedRigidbody.WakeUp()” like so:

using System.Collections.Generic;
using UnityEngine;

public class WakeMeUp : MonoBehaviour
{
    private List<Collider2D> m_ColliderContacts = new List<Collider2D>();
    private Collider2D m_Collider;

    private void Start()
    {
        m_Collider = GetComponent<Collider2D>();
    }

    public void WakeContacts()
    {
        m_Collider.GetContacts(m_ColliderContacts);

        foreach(var collider in m_ColliderContacts)
        {
            var body = collider.attachedRigidbody;
            if (body)
                body.WakeUp();
        }
    }
}
1 Like