我目前正在用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];
}
}
像这样,一切都很好,玩家撞上一颗小行星,检测到碰撞,播放音效,"游戏结束"场景取代当前场景。
当我尝试添加另一种小行星时,问题就开始了。对于类别,我尝试了很多东西,例如
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];
}
碰撞检测代码是相同的,但进行了一些小的编辑,将碰撞信息替换为相关信息。
出于某种原因,没有任何效果。根据我使用的类别设置代码,要么
- 当我遇到一颗普通的小行星
- 时,我输了(正如预期的那样),如果我遇到一颗金色的小行星,什么也没发生(正如预期的那样),但是当我拍摄一颗金色小行星时,除了运行代码之外,什么都没有发生。
- 当我射击时,我自动输了
- 当我射击时,没关系,但是如果我击中任何小行星,我就输了。
我不确定发生了什么,所以任何帮助将不胜感激。如果我需要发布更多代码或细节以便你们能够提供帮助,我很乐意这样做。
您的问题来自您在 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
}
}
}
这允许您对大量不同的小行星类型仅使用一个接触类别。
另一种选择是为每个小行星分配一个唯一的名称。如果您需要确切地知道哪颗小行星被击中,您可能需要这样做。也许每隔一段时间,你就会为三相点创建一个"惊喜"的小行星,或者为每颗小行星撞击创建一个分裂动画。
在这种情况下,您需要为每个小行星分配一个唯一的名称,如下所示:
// 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
}
}
}
}
如果使用最后一个选项,则需要记住从数组中删除不再使用的任何节点(已销毁,屏幕外等)。如果你不这样做,它不会让你的代码崩溃,但这是一个很好的做法。特别是如果你不断向阵列添加新的小行星。