Contact.Update behavior

Topics: Developer Forum
Developer
Apr 27, 2011 at 4:22 PM

I had a problem in my game where the OnCollision handler for one object was returning false to disable the collision between itself and certain objects, but due to the unpredictable order of contacts in a manifold it didn't always work. Many of my objects have OnCollision callbacks, and some of them return false to disable collision depending on game conditions. Sometimes collisions were disabled as expected, and sometimes they weren't. Turns out it has to do with this bit of code in Contact.Update:

 

            if (wasTouching == false && touching)
            {
                //Report the collision to both participants:
                if (FixtureA.OnCollision != null)
                    Enabled = FixtureA.OnCollision(FixtureA, FixtureB, this);

                //Reverse the order of the reported fixtures. The first fixture is always the one that the
                //user subscribed to.
                if (FixtureB.OnCollision != null)
                    Enabled = FixtureB.OnCollision(FixtureB, FixtureA, this);

                //BeginContact can also return false and disable the contact
                if (contactManager.BeginContact != null)
                    Enabled = contactManager.BeginContact(this);

                //if the user disabled the contact (needed to exclude it in TOI solver), we also need to mark
                //it as not touching.
                if (Enabled == false)
                    Flags &= ~ContactFlags.Touching;
            }

There is a bit of a design flaw here with the behavior of OnCollision - right now, these callbacks are documented to disable the collision if they return false however the behavior is really "disable the collision if you happen to be the second fixture getting the callback". This is unpredictable behavior and thus undesirable and should be improved.

Another subtle problem with the current code that the Fixture A OnCollision callback cannot set up anything that it can expect to reset in an OnSeparation callback because Fixture B might disable the contact. For example, Fixture A might begin triggering a particle effect at the beginning of a contact and turn it off in an OnSeparation. With the current code there's no guarantee Fixture A's OnSeparation will be called even if Fixture A's OnCollision returns true. I think these need to be coupled in order to deal with fixture to fixture OnCollision triggered specific effects and similar issues.

So, this is what I came up with and it seems to work:

					bool enabledA, enabledB;

					// Report the collision to both participants. Track which ones returned true so we can
					// later call OnSeparation if the contact is disabled for a different reason.
					if (FixtureA.OnCollision != null)
						enabledA = FixtureA.OnCollision(FixtureA, FixtureB, this);
					else
						enabledA = true;

					// Reverse the order of the reported fixtures. The first fixture is always the one that the
					// user subscribed to.
					if (FixtureB.OnCollision != null)
						enabledB = FixtureB.OnCollision(FixtureB, FixtureA, this);
					else
						enabledB = true;

					Enabled = enabledA && enabledB;

					// BeginContact can also return false and disable the contact
					if (enabledA && enabledB && contactManager.BeginContact != null)
					{
						Enabled = contactManager.BeginContact(this);
					}

					// If the user disabled the contact (needed to exclude it in TOI solver) at any point by
					// any of the callbacks, we need to mark it as not touching and call any separation
					// callbacks for fixtures that didn't explicitly disable the collision.
					if(!Enabled)
					{
						Flags &= ~ContactFlags.Touching;

						if (enabledA && FixtureA.OnSeparation != null)
						{
							FixtureA.OnSeparation(FixtureA, FixtureB);
						}
						if (enabledB && FixtureB.OnSeparation != null)
						{
							FixtureB.OnSeparation(FixtureB, FixtureA);
						}
					}

 

Any thoughts or feedback appreciated,

-Eric Cosky