Double dispatch for collision handling with SpriteKit

删除回忆录丶 提交于 2019-12-03 20:32:52

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

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];
        }
    }
}

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!