SpriteKit 对同一类型的多个对象的碰撞检测

Posted

技术标签:

【中文标题】SpriteKit 对同一类型的多个对象的碰撞检测【英文标题】:SpriteKit collision detection on multiple objects of the same type 【发布时间】:2014-10-26 19:25:22 【问题描述】:

我目前正在和朋友一起使用 Objective-C 和 SpriteKit 开发一款游戏。我和我的朋友都是 SpriteKit 的新手,所以我们正在进行的这个项目更多的是一种学习体验。但是,当然,我们遇到了一些麻烦。

游戏相当简单:有一个球在屏幕周围飞来飞去,然后从 iPhone 屏幕的“墙壁”或边缘弹回。用户控制屏幕底部的拨片使球向后偏转,以防止球到达屏幕底部。每次球击中球拍,屏幕顶部的计分器就会加一。

这是我们正在努力实现的目标。当游戏得分等于 5 时,我们向场景中添加一个新球。新球开始自行移动,但每当我们尝试检测新球是否撞到屏幕边缘时,都不会检测到碰撞。

这是我们返回一个球对象的方法:

+(id)ball

    // the ball is a random image from google
    Ball *ball = [Ball spriteNodeWithImageNamed:@"ball"];

    // set the position of the ball
    ball.position = CGPointMake(0, 80);

    // set ball name property
    ball.name = @"ball";

    // give the ball a physics body
    ball.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ball.size];

    ball.physicsBody.affectedByGravity = NO;

    return ball;

调用这个方法来移动场景中的球:

-(void)move:(int)deltaX withDeltaY:(int)deltaY

    SKAction *testMoveRight = [SKAction moveByX:deltaX y:deltaY duration:0.03];

    // this will repeat the action over and over
    SKAction *move = [SKAction repeatActionForever:testMoveRight];
    [self runAction:move];

这些是我们的碰撞检测类别:

// this category is for the original ball in the game
static const uint32_t ballCategory = 0x1 << 0;
// this category is for the second ball that gets added to the game
static const uint32_t ball2Category = 0x1 << 1;  
static const uint32_t paddleCategory = 0x1 << 2;
static const uint32_t topBarrierCategory = 0x1 << 3;
static const uint32_t leftBarrierCategory = 0x1 << 4;
static const uint32_t rightBarrierCategory = 0x1 << 5;
static const uint32_t gameOverBarrierCategory = 0x1 << 6;

这是我们的 didBeginContact 方法:

-(void)didBeginContact:(SKPhysicsContact *)contact

    SKPhysicsBody *firstBody, *secondBody;

    if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) 
        firstBody = contact.bodyA;
        secondBody = contact.bodyB;
    
    else 
        firstBody = contact.bodyB;
        secondBody = contact.bodyA;
    


    // first ball contact
    if ((firstBody.categoryBitMask & ballCategory) != 0 && (secondBody.categoryBitMask & rightBarrierCategory) != 0) 
        [ball move:-15 withDeltaY:0];
    

    else if ((firstBody.categoryBitMask & ballCategory) != 0 && (secondBody.categoryBitMask & topBarrierCategory) != 0) 
        [ball move:0 withDeltaY:-20];
    

    else if ((firstBody.categoryBitMask & ballCategory) != 0 && (secondBody.categoryBitMask & leftBarrierCategory) != 0) 
        [ball move:15 withDeltaY:0];
    

    else if ((firstBody.categoryBitMask & ballCategory) != 0 && (secondBody.categoryBitMask & paddleCategory) != 0) 
        [ball move:0 withDeltaY:20];

        // increment score
        self.score++; 
        // update the score label
        self.deathLabel.text = [NSString stringWithFormat:@"%i", self.score];

        // add a new ball to the scene if the score is 5
        if (score == 5) 
            [scene addChild:ball2];
            [ball2 move:5 withDeltaY:10];
        
    

    // second ball contact detection
    if ((firstBody.categoryBitMask & ball2Category) != 0 && (secondBody.categoryBitMask & rightBarrierCategory) != 0) 
        [ball2 move:-15 withDeltaY:0];
    

为什么我们添加到场景中的第二个球没有检测到它何时与右侧障碍物(屏幕的右边缘)发生碰撞?当它到达屏幕的右边缘时,它不会像我们想要的那样反弹并向左移动。有什么建议么?

【问题讨论】:

【参考方案1】:

仅仅为球添加一个physicsBody 是不够的,您还必须设置类别、接触和碰撞位掩码。首先,我将球的physicsBody 更改为

ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.frame.size.width/2];

圆形物理体首先更有效,其次更适合您的情况。除此之外,您不需要为每个障碍设置单独的类别。将它们合二为一:

self.size = self.view.bounds.size;

CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 0, 0);
CGPathAddLineToPoint(path, NULL, 0, self.frame.size.height);
CGPathAddLineToPoint(path, NULL, self.frame.size.width, self.frame.size.height);
CGPathAddLineToPoint(path , NULL, self.frame.size.width, 0);
CGPathAddLineToPoint(path, NULL, 0, 0);
SKShapeNode *bounds = [SKShapeNode shapeNodeWithPath:path];
bounds.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromPath:path];
CGPathRelease(path);
bounds.physicsBody.categoryBitMask = boundsCategory;

如您所见,每个 SpriteNode 都有一个 categoryBitmask,它有一个碰撞位掩码(这意味着它与这些类别反弹/碰撞)和一个接触位掩码(我这里没有设置,但是当这种接触发生时, didBeginContact 方法被调用)。在您的代码中,不会调用 didBeginContact 方法,但您也不需要它来进行弹跳。澄清一下,弹跳会自行发生,这意味着您不需要任何接触方法或位掩码,只需对球施加力/脉冲,当它接触到边界时它就会弹跳。

对于球,您不需要为每个球设置另一个类别 - 这是冗余。对于创建的每个球,只需将 categoryBitmask 设置为相同的值:

ball.physicsBody.categoryBitMask = ballCategory;
ball.physicsBody.collisionBitMask = boundsCategory;

通过添加这些行,您是在告诉编译器每个球都属于“ballCategory”并且它应该与任何属于“boundsCategory”的对象发生碰撞。希望这会有所帮助,祝你好运!

【讨论】:

感谢您的澄清。您是否知道我是否可以将球的物理体设置为在从障碍物反弹时不会失去“速度”,并允许它朝相反的方向移动?我已经按照您上面的建议设置了球的 categoryBitMask 和 collisionBitMask,但最终在从几个障碍物反弹后,球失去了速度。有什么建议么?我仍然对如何使用 didBeginContact 方法感到有些困惑。 didBeginContact 用于当您希望在两个对象相互接触时调用方法(对象不会相互反弹,didBeginContact 只会被调用但对象将继续移动/做他们的事)。要让球连续弹跳,请将其恢复力设置为 1.0f:ball.physicsBody.restitution = 1.0f Restitution 是一个 'bounciness' 的值,其中 1.0f 意味着它在弹跳时永远不会失去速度,而 0.0f 意味着它一旦碰撞就停止(0 反弹)。如果这回答了您的问题,如果您将我的答案标记为正确,我将不胜感激。谢谢! 至于反方向移动,您可能需要准确说明您的意思。 感谢您的回复。这似乎使它按照我们想要的方式工作。我将球的恢复力调整为 1.0f,当我对球施加一个脉冲时,它似乎在最初的几次反弹中起作用,但最终球在 y 方向失去了它的速度并开始减速到最终停止。然而,球继续以相同的速度沿 x 方向移动。关于如何在 y 方向上保持这个速度的任何建议?我之前所说的“向相反方向移动”的意思是按照你的建议保持球的弹性。 抱歉回复晚了; Y 方向的速度损失可能是由于重力作用在精灵上。你设置ball.physicsBody.affectedByGravity = NO;了吗?

以上是关于SpriteKit 对同一类型的多个对象的碰撞检测的主要内容,如果未能解决你的问题,请参考以下文章

SpriteKit 中的碰撞检测

SpriteKit 碰撞检测不起作用

基于 SpriteKit 中的碰撞检测更新分数的问题

未检测到 spritekit 物理碰撞

未检测到 SpriteKit 碰撞

未检测到 SpriteKit 碰撞