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
- 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.
- when I shoot, I automatically lose
- 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.
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:
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";
andmyNode.name = @"BadRock";
. Now in your contacts method: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:
Then you need to store each new asteroid you created in a mutable array:
In the contacts method you enumerate the array like this:
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.