Objective c 使用SpriteKit进行碰撞处理的双重调度

Objective c 使用SpriteKit进行碰撞处理的双重调度,objective-c,sprite-kit,dispatch,double-dispatch,Objective C,Sprite Kit,Dispatch,Double Dispatch,我用的是SpriteKit的碰撞检测。它有一个如下所示的回调: - (void)didBeginContact:(SKPhysicsContact *)contact 接触对象有两个物理实体: SKPhysicsBody *bodyA; SKPhysicsBody *bodyB; 我的游戏将有很多对象,当然我可以测试categoryBitMask以找出哪些对象与哪些对象冲突。但是考虑到我打算有很多种类(当然不超过32种),并且可能会动态地引入新的类型,那么最优雅的方法是什么来进行动态双重分派

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

- (void)didBeginContact:(SKPhysicsContact *)contact
接触对象有两个物理实体:

SKPhysicsBody *bodyA;
SKPhysicsBody *bodyB;
我的游戏将有很多对象,当然我可以测试
categoryBitMask
以找出哪些对象与哪些对象冲突。但是考虑到我打算有很多种类(当然不超过32种),并且可能会动态地引入新的类型,那么最优雅的方法是什么来进行动态双重分派,以便为所有这些碰撞产生的碰撞、爆炸、得分等逻辑编码?当然,我可以建立一个巨大的毛茸茸的如果声明,但我希望有更干净的东西


可能是一个为适当的处理程序存储选择器的查找表?然后我通过某种组合的
categoryBitMasks
对查找表进行索引?我很想听到一些建议。

以下是联系人调度在中国的工作方式

它的要点是:向每个联系节点发送一条消息
didBeginContact:withOtherBody:
,这样每个节点就可以自己知道与哪个其他主体进行了联系或失去了联系。如果需要另一个实体的节点,可以从SKPhysicsBody
节点
属性获取该节点

-(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];
        }
    }
}
-(void)didBeginContact:(skphysiccontact*)联系人
{
SKPhysicsBody*bodyA=contact.bodyA;
SKPhysicsBody*bodyB=contact.bodyB;
SKNode*nodeA=bodyA.node;
SKNode*nodeB=bodyB.node;
for(PhysicContactObserver中的id观察者)
{
SKNode*observerNode=observer.node;
if(observerNode==nodeA)
{
[观察员didBeginContact:联系其他机构:bodyB];
}
else if(observerNode==nodeB)
{
[观察员didBeginContact:联系其他机构:bodyA];
}
}
}
-(无效)DiEndContact:(SKPhysicContact*)联系人
{
SKPhysicsBody*bodyA=contact.bodyA;
SKPhysicsBody*bodyB=contact.bodyB;
SKNode*nodeA=bodyA.node;
SKNode*nodeB=bodyB.node;
for(PhysicContactObserver中的id观察者)
{
SKNode*observerNode=observer.node;
if(observerNode==nodeA)
{
[观察员didEndContact:联系其他机构:机构B];
}
else if(observerNode==nodeB)
{
[观察员didEndContact:联系其他机构:机构A];
}
}
}

我为SkPhysicsBodyContact创建了一个双重分派的工作示例,将乒乓球作为我的首选游戏。我的github上提供了工作代码

实际上,您需要使用访问者模式在联系人委托中执行双重分派,就像在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]];

}
VisitablePhysicsBody和ContactVisitor是执行双重分派所需的中间人,它们非常简单,源代码在项目repo中。它们最终允许您拥有专门处理特定类型节点的联系人的类

例如,在我的Pong示例中,有一个名为BallNodeContactVisitor的类,它仅在出现涉及BallNode的联系人时接收消息。类中有一些方法遵循命名约定,允许我们确定BallNode与其他节点类型(如PaddleNode)接触的结果


为什么不检查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:
调用它

它可能会以各种方式进行优化、规范化,但我觉得它是一种干净的解决方案。还没有证明,只是偶然发现


我注意到你在问题的末尾写的几乎一样。我还是会把它寄出去的哇,评论建议也一样。Aw.

您可以为相关节点指定一个名称,然后将这些名称用作选择器字典中的键。我在SKPhysicsBody类中没有看到name属性。是否要添加具有关联引用的类别来存储名称?物理实体没有名称,它们的节点有名称。我可能会先尝试一下,但是如果您决定不将它们附加到
SKNode
s,那么您所说的关联对象就可以了。啊,当然可以。谢谢,听起来应该有用。虽然很有趣,但这并不能真正回答我的问题。我想根据发生碰撞的两个对象的类型(或值)调用一个方法。这样,我就不需要编写一长串无法维护的if-then-else语句。使用CodaFi的建议,我将连接名称,然后将其作为键在字典中查找选择器,然后我可以使用节点作为参数调用选择器。您的意思是要运行类似“bulletDidContactWithEnemyHead”的选择器吗?这就是上面的代码所做的,除了名称之外,并且假设您正在使用子类(即bullet和敌军是节点的子类)。然后,项目符号将运行“didContactWith:”,然后可以使用[body.node class]来确定isMemberOfClass关系、类别位掩码或非常简单的节点标记。这样做的好处是,每个物体都处理自己与其他物体的碰撞,避免了繁琐的“我是身体A还是身体B?”决定(这也许是你关于这一切的主要观点?)。因为在处理与其他物体的碰撞时,你已经可以限制检查次数:子弹可能会击中敌人,一堵可射击的墙或者一个不同的玩家——就是这样。PS:不管你写100个if/else语句(或者更好的:switch/case)还是100个自定义选择器都不行
NSDictionary *contactDispatch = @{
@"player:didBeginContactWithMonster:" : @[ [Player class], [Mosnter class] ],
@"player:didBeginContactWithBullet:" : @[ [Player class], [Bullet class] ]
};