Search code examples
iosobjective-ccocos2d-iphonechipmunksimpleaudioengine

Filtering collisions in Chipmunk for trapped bodies


Background

  • I'm making an iOS app for kids where you can use your finger to drag balls around on a screen.
  • I'm using Chipmunk 7.0.0 for the physics simulation.
  • I've adapted the Chipmunk demo code to implement the dragging functionality.
  • I'm using 2 ms fixed time step. (It's not like my app has anything better to do...)

Issue

I've recently added code to play a sound whenever the balls collide with each other or with a wall, which I'm doing inside a postSolve callback:

static void postSolve(cpArbiter *arb, cpSpace *space, cpDataPointer userData)
{
    GameLayer *layer = (__bridge GameLayer *)userData;
    [layer collisionHandler:arb];
}

-(void) collisionHandler:(cpArbiter*)arb
{
    if(cpArbiterIsFirstContact(arb)) {
        [[SimpleAudioEngine sharedEngine] playEffect:kCollisionEffectFilename];
    }
}

Here's the problem... When I drag a ball into a wall, it generates a very large number of collisions, even when filtering on cpArbiterIsFirstContact, and it sounds terrible. It appears that the ball is bouncing off the wall, being driven back into wall by the constraint, rinse, and repeat. I'd like to play the collision sound only once or twice in this scenario.

Things I've tried that don't seem to work...

  • Filtering using cpArbiterTotalKE, cpArbiterTotalImpulse, or relative velocity: Impulse, kinetic energy, and relative velocity are all in the range of typical collisions.
  • Using a separate callback: The ball really is bouncing off the wall multiple times.
  • Reducing the step size: The physics engine actually takes longer to converge.
  • Rate limiting the sound effects: Better, but the ball still makes noise even after it looks like it's stationary.

Question

Is there a way to filter collisions for trapped bodies?


Solution

  • A simple and effective solution is to avoid playing the sound in quick succession.

    When the ball contacts with the wall, check if the ball's "last contact" timer is lower than the current time. If so, play the sound and set the ball's "last contact" time to the current time plus x, where x is the timeout duration the sound shouldn't play again (ie 0.5 seconds).

    In cocos2d-iphone you can add up an update method's deltaTime to keep track of time. There are also a number of ways to get system time, for example [NSDate date].timeIntervalSince1970 gives you the current number of seconds since 1970. I know, seems ridiculous, but if you get that number again sometime later, and subtract the previous number of seconds you get the difference in seconds, that's all that counts.