Objects still affected when OnCollision returns false

Topics: User Forum
Apr 14, 2009 at 1:26 AM
Edited Apr 14, 2009 at 1:32 AM
I guess that's how I should put it..

Basically, I have a Rock object and a Player object.  I don't want the Player to be able to move a Rock, but I do want the Rock to be moved if another Rock collides with it.  I also want the Player to still collide with the Rock, so if I can't simply use the CollisionCategory properties.

The way I am doing this now is by intercepting the Player's collision with the Rock in an OnCollision method in Rock's code:
private bool OnCollision(Geom geom1, Geom geom2, ContactList contactList)
{
if (geom1.Tag is Player || geom2.Tag is Player)
return false;

return true;
}
I thought this would achieve the effect I want, but the Rock still reacts to the Player when they collide.


I've also tried resetting the Rock's velocities/forces on a collision:
private bool OnCollision(Geom geom1, Geom geom2, ContactList contactList)
{
if (geom1.Tag is Player || geom2.Tag is Player)
{
_pBody.LinearVelocity = Vector2.Zero;
_pBody.AngularVelocity = 0.0f;
_pBody.ClearForce();
_pBody.ClearImpulse();
_pBody.ClearTorque();
return false;
}

return true;
}
But this doesn't work either. Basically, I want the Rock to pretty much be a Static object whenever the Player collides with it, but react normally if anyone else collides with it (Player shouldn't be able to move the Rock, but other things should). Any suggestions? I'm only intercepting the Player-Rock collision in Rock's code.. should I also be doing it in Player's code? It seems like I shouldn't have to do it, but maybe this is the problem.. Player is still triggering a collision reaction on the Rock, and it's occurring AFTER the Rock has?


Update:
I tried intercepting the collision in Player's code as well, but it still doesn't work.  Also, since I want the Rock to still be affected by other Rocks, I can't simply give it some fixed joints to hold it in place.  I also can't make it Static for the same reason.
Apr 14, 2009 at 6:10 AM
I'm a bit confused as to the exact purpose of the OnCollision method, but i thought that it was just for letting you do stuff with the collision data not preventing a collision. On that note, what you could do is replace the returns with setting the rock.IsStatic (so that the rock becomes static when a player hits it) . I suspect that this method would be buggy but it might work for what you need.
Apr 14, 2009 at 7:02 AM
Sam, returning false from OnCollision will definitely stop the collision reations from happenning. However, OnCollision will be called twice for each collision - each time the geom that has registered the collision callback will be passed as geom1. I guess that the collision will be handled if either of the geoms return True from the collision callback (or if either of the geoms don't have a callback).

In your case, I don't think returning false from the other object's collision callback will help. It's effectively the same as using collision categories and would result in the player walking through the rock. Instead, I'd try making the rocks really heavy so that the player's force has no effect. Increasing their friction coefficient might also help.
Apr 15, 2009 at 5:39 AM
You probably did, but make sure to call the new OnCollision Method as you load your content:

playerGeom.OnCollision += MyOnCollisionMethod;

You could also set different OnCollision Methods for different Geoms that way, and get more fine-tuned control. 

Also, my understanding is that the OnCollision method is for calling custom reactions, not to stop collisions.  The collisions will still occur in the PhysicsSimulator whether you have logic in the OnCollisions or not.  Could you just adjust the mass of the rock so that the player isn't strong enough to move it, and still allow rocks to move each other?  Maybe I'm way off.

Apr 15, 2009 at 6:05 AM
Do you want it so that if rock is on the ground, the player can walk right through it like it is not there? Or do you want it so that the rock stays still and the person cannot walk through the rock? If it is the first one, you could change the collision categories of the rocks when they collide with a person. Then rocks can be set back to collide with a player once they are activated again, assuming they are thrown at the player or something. In my game, I have ships that launch spears at one another on water. When a ship is hit by a spear, I change its collision category so that it no longer collides with the water. This has the effect of the ship looking like it is sinking beneath the water. It looks like you want the rock to "collide" with a player but not be affected by the collision as far as the physics are concerned, but I do not entirely understand what you wish to achieve.
Apr 15, 2009 at 7:21 AM
I think what he wants is the rock to appear so heavy to the player that he can't move it at all, but if it gets hit with other rocks then it will move as normal. On that note, you have 2 options: 1. make the mass extremely high (much higher than any force the player could ever conceivably exert), or use some sort of anchor joint that has a force breakpoint higher than the player can exert, but lower than the rock can.
Apr 17, 2009 at 10:08 PM
Thanks for the input.  I ended up making the Rock's mass very high.  I didn't want to have to do it that way, but it seems to be the only simple solution.  The only problem is the player can still rotate and effectively "roll" the rock if he jumps into it at the right angle.  I might try just setting the rock static when it touches the ground, and leaving it that way until another object comes in contact with it, but that is much more complicated.  If only there was some way to just prevent Player from applying forces to it...   I fully understand what collision categories are for (as stated in my first post) and I can't use them here because the player should still collide with the rock.
Apr 18, 2009 at 1:50 AM
Edited Apr 18, 2009 at 1:50 AM
You can also try setting the RotationalDragCoefficient of the rock's body, so it is difficult to rotate, or you could try adjusting the MOI (MomentOfInertia) value.  One of those, or a combination, should be able to stop a player for tipping the rock.  You will have to fiddle with them to get it just right.
Apr 20, 2009 at 7:33 AM
I'm thinking the problem might be that the engine doesn't propogate a false return value for the collision callback.  This would mean that only the last delegate's return value would be used (might not always be false).  I didn't test or anything, but I was reading this earlier: http://en.csharp-online.net/Multi-cast_Delegate and thought it might be relevant if someone else wanted to look into it.
Apr 20, 2009 at 11:42 AM
Edited Apr 20, 2009 at 11:56 AM
It would be nice to test if OnCollision returning false actually does what it's supposed to do.  I was under the same impression as roonda that if the two Geoms involved in a collision both returned false for that collision, then they would pass through each other just like using CollisionCategory.  But this wasn't the case for my Rock/Player example - even if they both returned false, the collision still occurred and a reaction was made.  Could this be a bug?

From the documentation:
The OnCollision event is fired when the geometry hits another geometry. You will need to return a boolean inside the event method to indicate if you want the collision to happen or not.

This seems to say that returning false would mean the collision (or is it the collision reaction?) does not happen.  If both Geoms returned false, the collision would not happen for either, and they would either A) pass through each other, or B) not react to the collision (but not pass through each other either).

Maybe if it is a bug it is related to 00se7en's link: http://en.csharp-online.net/Multi-cast_Delegate




After stepping through some code, I would have to agree with 00se7en in that the false isn't being propagated back to the Arbiter for the collision properly.  In my case I have an OnCollision delegate in my generic game entity base class (which Rock derives from), and another OnCollision delegate defined within Rock's class.  So there are two OnCollision delegates for Rock.  What is happening is my first delegate is returning false (the one that checks if it collided with the Player), but the second delegate (the one from the base class) is returning true.  According to the link here again, only the last return is getting sent back to the Arbiter, which is "true" in my case, therefore the collision still happens, even though my first delegate returned false.

The link has a possible solution:
.Net delegates are all multi-cast delegates. When calling a multi-cast delegate that returns a value, only the last value will be returned.
In order to process every return value, iterate the delegate list and invoke each delegate individually. If one of the methods throws an exception,
the delegate call will immediately stop and no other methods in the list will be called.

I don't know much about delegates in C#, but I made these modifications to the end of the Collide(Geom geometry1, Geom geometry2, ContactList contactList) method in Arbiter.cs and it properly cancels collision if any of the delegates returns false.  I don't know if this is the most efficient way of doing this, but it seems to work:
private void Collide(Geom geometry1, Geom geometry2, ContactList contactList)
{
...

//allow user to cancel collision if desired
if (geometry1.OnCollision != null)
{
//If the contactlist is populated, this means that there is an collision.
if (contactList.Count > 0)
{
foreach (MulticastDelegate d in geometry1.OnCollision.GetInvocationList())
{
if (!(bool)d.DynamicInvoke(new object[] { geometry1, geometry2, contactList }))
{
//The user aborted the collision. Clear the contact list as we don't need it anymore.
contactList.Clear();
break;
}
}
}
}

//allow user to cancel collision if desired
if (geometry2.OnCollision != null)
{
if (contactList.Count > 0)
{
foreach (MulticastDelegate d in geometry2.OnCollision.GetInvocationList())
{
if (!(bool)d.DynamicInvoke(new object[] { geometry2, geometry1, contactList }))
{
contactList.Clear();
break;
}
}
}
}
}
Hope this might help someone...  To be clear, this bug/fix only applies when you add more than one OnCollision handler to a Geom.