使用 NSMutable 对象数组进行碰撞检测

Collision Detection with an NSMutable array of Objects

我正在尝试制作一个游戏,其中的物体会在屏幕的左右壁之间弹跳,直到到达底部。

我在这些方法中创建对象,这些方法将对象存储在 NSMutable 数组中并在游戏开始时调用。

-(void)createContent
{
    self.backgroundColor = [SKColor colorWithRed:0.54 green:0.7853 blue:1.0 alpha:1.0];

    world = [SKNode node];
    [self addChild:world];

    crabs = [NSMutableArray new];
    [self performSelector:@selector(createCrabs) withObject:nil afterDelay:1];
 }

-(void)createCrabs {
     crab = [HHCrab crab];
    [crabs addObject:crab];
    [world addChild:crab];
    crab.position = CGPointMake(world.scene.frame.size.width/12 , world.scene.frame.size.height/3.2);
    [crab moveLeft];

    //Next Spawn
    [self runAction:[SKAction sequence:@[
            [SKAction waitForDuration:10],
            [SKAction performSelector:@selector(createCrabs) onTarget:self],
    ]]];
}  

这将无休止地创建新对象,但问题始于碰撞检测。最初我的碰撞检测是这样设置的:

-(void)didBeginContact:(SKPhysicsContact *)contact
{
    SKPhysicsBody *firstBody, *secondBody;
    if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
    {
        firstBody = contact.bodyA;
        secondBody = contact.bodyB;
    } else {
        firstBody = contact.bodyB;
        secondBody = contact.bodyA;
    }

    if (firstBody.categoryBitMask==crabCategory && secondBody.categoryBitMask == leftWallCategory) {
        NSLog(@"Crab Hit Left Wall");
        [crab stop];
        [crab moveRight];
    } else if (firstBody.categoryBitMask == crabCategory && secondBody.categoryBitMask == rightWallCategory) {
        NSLog(@"Crab Hit Right Wall");
        [crab stop];
        [crab moveLeft];
    }
}

但是,在地图上出现多个对象后,当原始对象与墙壁碰撞时,它开始出现故障并停止移动。这会导致堆积,从而扰乱生成的新对象。 我还尝试使用更新 CurrentTime 方法来查看碰撞检测是否会有所改善,但是正如预测的那样,一次只有一个对象会移动,而其余对象将保持静止。

-(void)didBeginContact:(SKPhysicsContact *)contact {
    SKPhysicsBody *firstBody, *secondBody;
    if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
    {
        firstBody = contact.bodyA;
        secondBody = contact.bodyB;
    } else {
        firstBody = contact.bodyB;
        secondBody = contact.bodyA;
    }

    if (firstBody.categoryBitMask==crabCategory && secondBody.categoryBitMask == leftWallCategory) {
        NSLog(@"Crab Hit Left Wall");
        self.crabTouchLeftWall = YES;
    } else if (firstBody.categoryBitMask == crabCategory && secondBody.categoryBitMask == rightWallCategory) {
        NSLog(@"Crab Hit Right Wall");
        self.crabTouchRightWall = YES;
    }
}

-(void)update:(CFTimeInterval)currentTime {
    /* Called before each frame is rendered */
    for (HHCrab *crabNode in crabs){
        if (self.crabTouchLeftWall){
           [crabNode stop];
            [crabNode moveRight];
            self.crabTouchLeftWall = NO;
        }
        if (self.crabTouchRightWall){
            [crabNode stop];
            [crabNode moveLeft];
            self.crabTouchRightWall = NO;
        }
    }
}

我该如何解决这个问题,当一个物体与墙壁碰撞时,它不会影响其他物体的运动,只会影响它自己的运动?

我有几个建议。

当您创建多个 crab 实例并将它们添加到 crabs 数组中时,我建议您给每个 crab 一个唯一的名称 属性。您可以通过 运行 int 计数器来做到这一点。例如:

@implementation GameScene {
    int nextObjectID;
}

然后当你创建一个螃蟹实例时:

nextObjectID++;
[crab setName:[NSString stringWithFormat:@"crab-%i",nextObjectID]];

我个人更喜欢这样编写我的 didBeginContact:

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

    if (collision == (crabCategory | leftWallCategory)) {
        // figure out which crab in the array made the contact...
        for(HHCrab *object in crabs) {
            if(([object.name isEqualToString:contact.bodyB.node.name]) || ([object.name isEqualToString:contact.bodyA.node.name])) {
                NSLog("I am %@",object.name);
            }
        }
    }
}

您的代码 self.crabTouchLeftWall = YES; 不是正确的方法。您应该创建一个 crab class 属性 并将其设置为 YES。原因是每个螃蟹实例都需要具有这些属性。按照您目前的方式,所有 crab 实例都共享相同的 BOOL self.crabTouchLeftWallself.crabTouchRightWall.

您正在检查更新方法中的 BOOL 值联系人,然后 运行 [crabNode stop];。当在 didBeginContact 方法中注册联系人时,您可以直接执行此操作。

我之前建议您为每个 crab 实例使用唯一名称的原因是,当需要从数组中删除它们时,您需要能够指出需要删除的特定 crab 实例。有一个独特的名字只会让事情变得更容易。

请注意,像这样手动移动节点会产生奇怪的结果。在 Spritekit 中使用物理引擎时,您必须让它自己进行计算,这意味着您必须使用力来移动对象。这不适用于接触检测,如果您只需要接触检测,您可以四处移动节点。但是,如果您需要的不仅仅是接触检测,例如。模拟碰撞,限制节点相互穿透,那么我建议你适当地使用力(applyImpulse:, applyForce:是合适的方法)。

您可能可以通过将代码从 update: 方法移动到 didSimulatePhysics method, but that is not a solution...Because even if you move everything manually after simulation is done, you can make a mistake and for example manually initiate collision by positioning your nodes to overlap each other. So, the advice is, if you are using phyisics use it all, or don't use it at all and move nodes manually or using actions.In the case you decide not to use physics engine, you can handle collisions by using methods like CGRectIntersectsRect 和类似方法来解决您的问题。

再次指出,这与您的问题无关,但它可能很有用:即使您移动节点,也可以使用物理引擎进行接触检测,例如通过 SKAction 方法。阅读更多