SpriteKit 中多个不同的碰撞问题(iOS 应用程序用 Objective-C 和 Xcode 编写)

Issue with multiple different collions in SpriteKit (iOS app written in Objective-C with Xcode)

我目前正在 Objective-C 中构建一个 iOS 应用程序。该应用程序的想法是,你有某种火箭飞船在某种小行星带中航行。它以纵向模式播放。现在,有两种不同类型的小行星。普通的一撞就输,金色的射一枪拿金币。

对于碰撞,我使用的是 Ray Wenderlich SpriteKit 教程中的代码。类别设置如下:

static const uint32_t playerCategory     =  0x1 << 0;
static const uint32_t asteroidCategory        =  0x1 << 1;

运行 的代码是这样的:

- (void)player:(SKSpriteNode *)player didCollideWithAsteroid:(SKSpriteNode *)asteroid {
[self runAction:[SKAction playSoundFileNamed:@"Explosion.mp3" waitForCompletion:NO]];


NSLog(@"Hit");
[self.player removeFromParent];
[asteroid removeFromParent];

SKAction *actionMoveDone = [SKAction removeFromParent];
SKAction * loseAction = [SKAction runBlock:^{
    SKTransition *reveal = [SKTransition crossFadeWithDuration:0.5];
    SKScene * gameOverScene = [[GameOverScene alloc] initWithSize:self.size won:NO];
    [self.view presentScene:gameOverScene transition: reveal];
}];

[self.asteroid runAction:[SKAction sequence:@[loseAction, actionMoveDone]]];
}

碰撞检测代码是这样的:

- (void)didBeginContact:(SKPhysicsContact *)contact
{
// 1
SKPhysicsBody *firstBody, *secondBody;

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

// 2
if ((firstBody.categoryBitMask & playerCategory) != 0 &&
    (secondBody.categoryBitMask & asteroidCategory) != 0)
{
    [self player:(SKSpriteNode *)firstBody.node didCollideWithAsteroid:(SKSpriteNode *)secondBody.node];
}
}

像这样,一切顺利,玩家撞上一颗小行星,检测到碰撞,播放音效,"Game Over"场景替换当前场景。

当我尝试添加另一种小行星时,问题就开始了。对于类别,我尝试了很多东西,例如

static const uint32_t playerCategory     =  0x1 << 0;
static const uint32_t asteroidCategory        =  0x1 << 1;

static const uint32_t bulletCategory     =  0x1 << 2;
static const uint32_t goldAsteroidCategory        =  0x1 << 3;

static const uint32_t playerCategory     =  0x1 << 0;
static const uint32_t asteroidCategory        =  0x1 << 1;

static const uint32_t bulletCategory     =  0x1 << 0;
static const uint32_t goldAsteroidCategory        =  0x1 << 1;

甚至

static const uint32_t playerCategory     =  0x1 << 0;
static const uint32_t asteroidCategory        =  0x1 << 1;

static const uint32_t bulletCategory     =  1x1 << 0;
static const uint32_t goldAsteroidCategory        =  1x1 << 1;

以及我能想到的几乎所有这些东西的组合。

当我的子弹击中小行星时运行的代码如下:

- (void)bullet:(SKSpriteNode *)bullet didCollideWithGoldAsteroid:(SKSpriteNode *)goldAsteroid
{
  [self runAction:[SKAction playSoundFileNamed:@"ding.m4a" waitForCompletion:NO]];
NSLog(@"Hit");
[bullet removeFromParent];
[goldAsteroid removeFromParent];
[self plusOneCoin];
}

碰撞检测代码是相同的,但进行了一些小的编辑,用相关信息替换了碰撞信息。

出于某种原因,没有任何效果。根据我使用的类别设置代码,

  1. 当我 运行 进入一颗普通小行星时,我输了(如预期的那样),如果我 运行 进入一颗金色小行星,则没有任何反应(如预期的那样),但是当我射中一颗金色小行星时, 除了 运行 代码之外什么也没有发生。
  2. 开枪就自动输
  3. 当我射击时,没关系,但如果我击中 任何 颗小行星,我就输了。

我不太清楚发生了什么事,如有任何帮助,我们将不胜感激。如果我需要 post 更多的代码或细节来帮助你们,我会很乐意这样做。

您的问题来自您在 didBeginContact 方法中处理联系人的方式。有几种方法可以处理联系人。有些简单,有些更复杂。考虑这种更简单的方法:

- (void)didBeginContact:(SKPhysicsContact *)contact {
    uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);

    if (collision == (playerCategory | asteroidCategory)) {
        // do something
    }
}

你可以稍微复杂一点。例如,您有 2 种类型的小行星,但不想为每种小行星使用唯一的类别,因为您拥有的类别数量有限。您可以通过为您的小行星添加名称 属性 来完成此操作,例如 myNode.name = @"GoodRock";myNode.name = @"BadRock";。现在在您的联系人方法中:

- (void)didBeginContact:(SKPhysicsContact *)contact {
    uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);

    if(collision == (playerCategory | asteroidCategory)) {

        if(([contact.bodyA.node.name isEqualToString:@"GoodRock"]) || ([contact.bodyB.node.name isEqualToString:@"GoodRock"])) {
            // do something
        }

        if(([contact.bodyA.node.name isEqualToString:@"BadRock"]) || ([contact.bodyB.node.name isEqualToString:@"BadRock"])) {
            // do something else
        }
    }
}

这允许您只对大量不同类型的小行星使用一种接触类别。

另一种方法是为每颗小行星指定一个唯一的名称。如果您需要准确知道哪颗小行星被击中,则可能需要执行此操作。也许每隔一段时间你就会为三相点创建一个 "surprise" 小行星,或者为每次小行星撞击制作一个分解动画。

在这种情况下,您需要像这样为每颗小行星分配一个唯一的名称:

// create an int variable and +1 every time you create a new asteroid
asteroidCounter++;
[myNode setName:[NSString stringWithFormat:@"asteroid-%i", asteroidCounter]];

然后您需要将您创建的每个新小行星存储在一个可变数组中:

[asteroidArray addObject:myNode];

在联系人方法中,您像这样枚举数组:

- (void)didBeginContact:(SKPhysicsContact *)contact {
    uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);

    if(collision == (playerCategory | asteroidCategory)) {

        for(SKSpriteNode *object in myArray) {

            if(([contact.bodyA.node.name isEqualToString:@"asteroid-3"]) || ([contact.bodyB.node.name isEqualToString:@"asteroid-3"])) {
                // do something to asteroid #3
            }
        }
    }
}

如果你使用最后一个选项,你需要记住从数组中删除任何不再使用的节点(销毁、屏幕外等)。如果您不这样做,它不会使您的代码崩溃,但这是一种很好的做法。特别是如果您不断向阵列中添加新的小行星。