Issue with multiple different collions in SpriteKi

2019-08-14 05:57发布

I am currently building an iOS app in Objective-C. The idea of the app is that you have some sort of rocket ship navigating through an asteroid belt of sorts. It is played in portrait mode. Now, there are two different kinds of asteroids. Normal ones that make you lose when you crash into them, and golden ones that you shoot at to get coins.

For collisions, I am using the code from a Ray Wenderlich SpriteKit tutorial. The categories are set up like this:

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

the code to run is this:

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

and the collision detection code is this:

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

Like this, all is well, the player crashes into an asteroid, a collision is detected, the sound effect plays, and the "Game Over" scene replaces the current one.

The issues begin when I attempt to add another kind of asteroid. For the categories, I tried many things, such as

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;

and

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;

and even

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;

and pretty much any combination of those things that I could think of.

The code to run when I bullet hits the asteroid is the following:

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

And the collision detection code is the same, but with some minor edits replacing the information for the collision with the pertinent information.

For some reason, nothing works. Depending on which category setup code I use, either

  1. when I run into a regular asteroid, I lose (as expected), if I run into a gold asteroid, nothing happens (as expected), but when I shoot a gold asteroid, nothing at all happens instead of running the code.
  2. when I shoot, I automatically lose
  3. when I shoot, it's ok, but if I hit any asteroid at all, I lose.

I'm not exactly sure what's up, so any help would be appreciated. If I need to post more code or details for you guys to be able to help, I will gladly do so.

1条回答
贼婆χ
2楼-- · 2019-08-14 06:37

Your issue comes from the way you handle your contacts in the didBeginContact method. There are a couple of ways to handle contacts. Some simple and some more complex. Consider this simpler way:

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

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

You can go a little more complex. For example you have 2 types of asteroids but do not want to use a unique category for each as you are limited in the number of categories you have. You can accomplish this by adding a name property to your asteroids like this myNode.name = @"GoodRock"; and myNode.name = @"BadRock";. Now in your contacts method:

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

This allows you to only use one contact category for a large number of different asteroid types.

Yet another alternative is to assign a unique name to each asteroid. You might want to do this if you need to know precisely which asteroid was hit. Perhaps every so often you create a "surprise" asteroid for triple points or have a break up animation for each asteroid hit.

In such a case you need to do assign a unique name to each asteroid like this:

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

Then you need to store each new asteroid you created in a mutable array:

[asteroidArray addObject:myNode];

In the contacts method you enumerate the array like this:

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

If you use the last option, you need to remember to remove any node from the array which is no longer being used (destroyed, off-screen, etc). It will not crash your code if you don't but it's good practice. Especially if you keep adding new asteroids to the array.

查看更多
登录 后发表回答