Here are two similar strategies with tiny differences. I suggest the second approach.
The physics will happen the next FixedUpdate. So wait until that FixedUpdate is over. We can do this by waiting for FixedUpdate twice. The first pass will take us to when the physics start taking place. The second pass will take us to the FixedUpdate after the physics have completed.
void OnCollisionEnter2D(Collision2D other)
{
StartCoroutine(DestroyAfterPhysics());
}
private IEnumerator DestroyAfterPhysics()
{
yield return new WaitForFixedUpdate();
yield return new WaitForFixedUpdate();
Destroy(gameObject);
}
That code makes the assumption that all current physics will be dealt with during the immediate FixedUpdate. We can avoid this assumption and clean the code a bit by waiting for the maximum delta time. This version of the code will assure success, but possibly at the loss of an insignificant amount of time:
void OnCollisionEnter2D(Collision2D other)
{
Destroy(gameObject, Time.maximumDeltaTime));
}
This is not a flawless approach, and I would be interested to see other ideas. For example, if you check to see if the bullet exists the frame after the collision, it could return a false positive.