3

I'm using SpriteKit's collision detection. It has a callback that looks like this:

- (void)didBeginContact:(SKPhysicsContact *)contact

The contact object has two physics bodies:

SKPhysicsBody *bodyA;
SKPhysicsBody *bodyB;

My game will have lots of objects, and of course I can test the categoryBitMask to find out what collided with what. But given that I intend to have many kinds (not more than 32 of course) and might dynamically introduce new types, what's the most elegant way to do dynamic double dispatch to code the logic for collisions, explosions, points scored, etc that will result from all these collisions? Of course I can build a giant hairy if-statement, but I was hoping for something cleaner.

Maybe a lookup table storing selectors for the appropriate handlers? And then I index the lookup table by some combination of the categoryBitMasks? I'd love to hear some suggestions.

jscs
  • 63,694
  • 13
  • 151
  • 195
nont
  • 9,322
  • 7
  • 62
  • 82
  • You can give the associated nodes a name, then use those names as keys in a dictionary of selectors. – CodaFi Nov 09 '13 at 21:00
  • I don't see a name property on the SKPhysicsBody class. Do you mean to add a category with an associative reference to store the name? – nont Nov 09 '13 at 21:24
  • Physics bodies don't have names, their nodes do. I'd probably try that first, but if you've decided not to attach them to `SKNode`s, then associated objects like you said would work. – CodaFi Nov 09 '13 at 21:25
  • ah right, of course. thanks, that sounds like it should work. – nont Nov 09 '13 at 21:34

3 Answers3

2

Following is how the contact dispatch works in Kobold Kit.

The gist of it: you send each contacting node a message didBeginContact:withOtherBody: so each node on its own knows with which other body it made or lost contact. If you need the other body's node, you can get that from the SKPhysicsBody node property.

-(void) didBeginContact:(SKPhysicsContact *)contact
{
    SKPhysicsBody* bodyA = contact.bodyA;
    SKPhysicsBody* bodyB = contact.bodyB;
    SKNode* nodeA = bodyA.node;
    SKNode* nodeB = bodyB.node;
    for (id<KKPhysicsContactEventDelegate> observer in _physicsContactObservers)
    {
        SKNode* observerNode = observer.node;
        if (observerNode == nodeA)
        {
            [observer didBeginContact:contact otherBody:bodyB];
        }
        else if (observerNode == nodeB)
        {
            [observer didBeginContact:contact otherBody:bodyA];
        }
    }
}

-(void) didEndContact:(SKPhysicsContact *)contact
{
    SKPhysicsBody* bodyA = contact.bodyA;
    SKPhysicsBody* bodyB = contact.bodyB;
    SKNode* nodeA = bodyA.node;
    SKNode* nodeB = bodyB.node;
    for (id<KKPhysicsContactEventDelegate> observer in _physicsContactObservers)
    {
        SKNode* observerNode = observer.node;
        if (observerNode == nodeA)
        {
            [observer didEndContact:contact otherBody:bodyB];
        }
        else if (observerNode == nodeB)
        {
            [observer didEndContact:contact otherBody:bodyA];
        }
    }
}
CodeSmile
  • 64,284
  • 20
  • 132
  • 217
  • While interesting, this doesn't really answer my question. I want to call a method based on the types (or failing that, the values) of the two objects that collided. That way I don't need to write a long unmaintainable sequence of if-then-else statements. Using CodaFi's suggestion, I would concatenate the names, and then use that as a key to look up a selector in a dictionary, then I could invoke the selector with the nodes as parameters. – nont Nov 10 '13 at 00:06
  • You mean you want to run a selector like 'bulletDidContactWithEnemyHead'? This is what the above code does except for the name and assuming you're working with subclasses (ie bullet and enemy are subclasses of a node). Then the bullet would run "didContactWith:" and can then use [body.node class] to determine isMemberOfClass relationship, or the category bit masks or quite simply the node tags. The advantage of that is that each object handles its own collision with other bodies, avoiding the tedious "am I body A or body B?" decisions (and perhaps this is your main point about all this?). – CodeSmile Nov 10 '13 at 00:31
  • Because with say the bullet handling collision with other bodies, you can already limit the number of checks: the bullet may hit an enemy, a shootable wall or a different player - and that's that. PS: whether you write 100 if/else statements (or better: switch/case) or 100 custom selectors doesn't really gain you anything, neither in productivity nor expressiveness (well, maybe a little) nor code size. Plus dynamic dispatch will certainly end up being slower. – CodeSmile Nov 10 '13 at 00:32
2

I've created a working example of double dispatch for an SkPhysicsBodyContact using Pong as my game of choice. The working code is available on my github.

https://github.com/kouky/iOS-SpriteKit-Pong

You actually need to use the Visitor pattern to perform the double dispatch in your contact delegate as in objective-c we can't overload class methods arguments.

- (void)didBeginContact:(SKPhysicsContact *)contact
{

    SKPhysicsBody *firstBody, *secondBody;
    firstBody = contact.bodyA;
    secondBody = contact.bodyB;

    VisitablePhysicsBody *firstVisitableBody = [[VisitablePhysicsBody alloc] initWithBody:firstBody];
    VisitablePhysicsBody *secondVisitableBody = [[VisitablePhysicsBody alloc] initWithBody:secondBody];

    [firstVisitableBody acceptVisitor:[ContactVisitor contactVisitorWithBody:secondBody forContact:contact]];
    [secondVisitableBody acceptVisitor:[ContactVisitor contactVisitorWithBody:firstBody forContact:contact]];

}

The VisitablePhysicsBody and ContactVisitor are middlemen required to perform the double dispatch, they're pretty simple and the source code is in the project repo. They ultimately allow you to have classes which are solely concenred with handling contacts for certain types of nodes.

For example in my Pong example there is a class called BallNodeContactVisitor which only receives messages when contacts arise which involve a BallNode. There are methods within the class which follow a naming convention and allow us to determine the outcome of the BallNode contact with other node types such as PaddleNode.

@implementation BallNodeContactVisitor

// Handles contacts with PlayfieldScene edges
- (void)visitPlayfieldScene:(SKPhysicsBody *)playfieldBody
{

    BallNode *ball = (BallNode *) self.body.node;
    PlayfieldScene *playfield = (PlayfieldScene *) playfieldBody.node;
    // Perform something
}

// Handles contacts with PaddleNodes
- (void)visitPaddleNode:(SKPhysicsBody *)paddleBody
{
    BallNode *ball = (BallNode *) self.body.node;
    PaddleNode *paddle= (PaddleNode *) paddleBody.node;
    // Perform something else
}

@end
0

Why not inspect SKNode subclasses?

Then you can easily inspect for contacted body.node classes. Then create a configuration dictionary that gives you what method to call.

Some node subclasses...

Player : SKSpriteNode
Bullet : SKSpriteNode
Monster : SKSpriteNode

...then create methods like...

-(void)player:(Player*) player didBeginContactWithMonster:(Monster*) monster;
-(void)player:(Player*) player didBeginContactWithBullet:(Bullet*) bullet;

...then create a configuration like...

NSDictionary *contactDispatch = @{
@"player:didBeginContactWithMonster:" : @[ [Player class], [Mosnter class] ],
@"player:didBeginContactWithBullet:" : @[ [Player class], [Bullet class] ]
};

So you can check for containments of the contacted bodies, then instantiate a selector with NSSelectorFromString, then call it with performSelector:withObject:withObject:.

It may be optimized, normalized in may ways, but feels me as a way clean solution. Have not proofed yet, just came across.

Just noticed you just wrote almost the same at the end of the question itself. Will post it anyway. :D Wow, the comment suggestions just does the same. Aw.

Geri Borbás
  • 15,810
  • 18
  • 109
  • 172