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];
}
碰撞检测代码是相同的,但进行了一些小的编辑,用相关信息替换了碰撞信息。
出于某种原因,没有任何效果。根据我使用的类别设置代码,
- 当我 运行 进入一颗普通小行星时,我输了(如预期的那样),如果我 运行 进入一颗金色小行星,则没有任何反应(如预期的那样),但是当我射中一颗金色小行星时, 除了 运行 代码之外什么也没有发生。
- 开枪就自动输
- 当我射击时,没关系,但如果我击中 任何 颗小行星,我就输了。
我不太清楚发生了什么事,如有任何帮助,我们将不胜感激。如果我需要 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
}
}
}
}
如果你使用最后一个选项,你需要记住从数组中删除任何不再使用的节点(销毁、屏幕外等)。如果您不这样做,它不会使您的代码崩溃,但这是一种很好的做法。特别是如果您不断向阵列中添加新的小行星。
我目前正在 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];
}
碰撞检测代码是相同的,但进行了一些小的编辑,用相关信息替换了碰撞信息。
出于某种原因,没有任何效果。根据我使用的类别设置代码,
- 当我 运行 进入一颗普通小行星时,我输了(如预期的那样),如果我 运行 进入一颗金色小行星,则没有任何反应(如预期的那样),但是当我射中一颗金色小行星时, 除了 运行 代码之外什么也没有发生。
- 开枪就自动输
- 当我射击时,没关系,但如果我击中 任何 颗小行星,我就输了。
我不太清楚发生了什么事,如有任何帮助,我们将不胜感激。如果我需要 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
}
}
}
}
如果你使用最后一个选项,你需要记住从数组中删除任何不再使用的节点(销毁、屏幕外等)。如果您不这样做,它不会使您的代码崩溃,但这是一种很好的做法。特别是如果您不断向阵列中添加新的小行星。