【问题标题】:Double dispatch for collision handling with SpriteKit使用 SpriteKit 进行碰撞处理的双重调度
【发布时间】:2013-11-09 19:59:55
【问题描述】:

我正在使用 SpriteKit 的碰撞检测。它有一个如下所示的回调:

- (void)didBeginContact:(SKPhysicsContact *)contact

接触对象有两个物理体:

SKPhysicsBody *bodyA;
SKPhysicsBody *bodyB;

我的游戏会有很多对象,当然我可以测试categoryBitMask 以找出碰撞的对象。但是考虑到我打算有很多种类(当然不超过 32 种)并且可能会动态引入新类型,那么进行动态双重调度以编码碰撞、爆炸、得分等逻辑的最优雅的方法是什么?从所有这些碰撞中?当然,我可以构建一个巨大的毛茸茸的 if 语句,但我希望有更干净的东西。

也许是一个查找表,用于存储适当处理程序的选择器?然后我通过categoryBitMasks的某种组合来索引查找表?我很想听听一些建议。

【问题讨论】:

  • 您可以给关联的节点起一个名字,然后将这些名字用作选择器字典中的键。
  • 我在 SKPhysicsBody 类上看不到 name 属性。您的意思是添加一个具有关联引用的类别来存储名称吗?
  • 物理实体没有名字,它们的节点有。我可能会先尝试,但如果您决定不将它们附加到 SKNodes,那么您所说的关联对象将起作用。
  • 嗯,当然。谢谢,听起来应该可以。

标签: objective-c sprite-kit dispatch double-dispatch


【解决方案1】:

我使用 Pong 作为我选择的游戏,为 SkPhysicsBodyContact 创建了一个双重调度的工作示例。工作代码在我的 github 上。

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

您实际上需要使用访问者模式在您的联系人委托中执行双重调度,因为在 Objective-c 中我们不能重载类方法参数。

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

}

VisitablePhysicsBodyContactVisitor 是执行双重调度所需的中间人,它们非常简单,源代码在项目 repo 中。它们最终允许您拥有仅关注处理某些类型节点的联系人的类。

例如,在我的 Pong 示例中,有一个名为 BallNodeContactVisitor 的类,它仅在出现涉及 BallNode 的联系时接收消息。类中有一些方法遵循命名约定,允许我们确定 BallNode 与其他节点类型(例如 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

【讨论】:

    【解决方案2】:

    以下是Kobold Kit 中联系人调度的工作方式。

    它的要点:您向每个联系节点发送一条消息didBeginContact:withOtherBody:,以便每个节点自己知道它与哪个其他主体建立或失去联系。如果您需要其他主体的节点,可以从 SKPhysicsBody node 属性中获取。

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

    【讨论】:

    • 虽然很有趣,但这并不能真正回答我的问题。我想根据碰撞的两个对象的类型(或失败的值)调用一个方法。这样我就不需要编写一长串不可维护的 if-then-else 语句。使用 CodaFi 的建议,我将连接名称,然后使用它作为键在字典中查找选择器,然后我可以使用节点作为参数调用选择器。
    • 你的意思是你想运行一个像'bulletDidContactWithEnemyHead'这样的选择器?这就是上面代码所做的,除了名称和假设您正在使用子类(即子弹和敌人是节点的子类)。然后子弹将运行“didContactWith:”,然后可以使用 [body.node class] 来确定 isMemberOfClass 关系,或者类别位掩码,或者非常简单的节点标签。这样做的好处是每个物体都可以处理自己与其他物体的碰撞,避免了“我是物体 A 还是物体 B?”的乏味问题。决定(也许这是你对这一切的主要观点?)。
    • 因为子弹处理与其他物体的碰撞,您已经可以限制检查次数:子弹可能会击中敌人、可射击的墙壁或不同的玩家——仅此而已。 PS:无论您编写 100 个 if/else 语句(或更好:switch/case)还是 100 个自定义选择器,都不会真正为您带来任何好处,无论是生产力还是表现力(好吧,也许是一点点)或代码大小。加上动态调度肯定会更慢。
    【解决方案3】:

    为什么不检查 SKNode 子类

    然后,您可以轻松检查已联系的body.node 课程。 然后创建一个配置字典,为您提供调用什么方法。

    一些节点子类...

    Player : SKSpriteNode
    Bullet : SKSpriteNode
    Monster : SKSpriteNode
    

    ...然后创建类似...的方法

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

    ...然后创建一个类似...的配置

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

    因此,您可以检查所接触主体的包含情况,然后使用 NSSelectorFromString 实例化选择器,然后使用 performSelector:withObject:withObject: 调用它。

    它可能会以多种方式进行优化、规范化,但我觉得它是一种干净的解决方案。还没打样,刚遇到。

    刚刚注意到您在问题本身的末尾写了几乎相同的内容。无论如何都会发布它。 :D 哇,评论建议也一样。哦。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-26
      • 1970-01-01
      相关资源
      最近更新 更多