Collision Detection with an NSMutable array of Objects

101 views Asked by At

I'm attempting to make a game with objects that bounce between the left and right walls of the screen until they reach the bottom.

I create my objects inside these methods which stores the objects in an NSMutable array and is called when the game begins.

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

This will endlessly create new objects, however a problem begins with collision detection. Originally I had my collision detection set up like this:

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

But, after more than one object is on the map, when the original object collides with a wall it begins to glitch and stop moving. This results in a pile up which bugs the new objects being spawned. I've also tried to use the update CurrentTime method to see if the collision detection would improve, however as predicted, only one object will move at a time while the rest will stay still.

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

How can I fix this so when one object collides with the wall, it does not effect the movement of the other objects, only itself?

2

There are 2 answers

7
sangony On BEST ANSWER

There are a couple of suggestions I have.

As you are creating multiple instances of crab and adding them into the crabs array, I suggest you give each crab a unique name property. Your can do this by having a running int counter. For example:

@implementation GameScene {
    int nextObjectID;
}

Then when you create a crab instance:

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

I personally prefer coding my didBeginContact like this:

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

Your code self.crabTouchLeftWall = YES; is not the right way to go. You should create a crab class property and set that to YES. Reason being that each crab instance needs to have these properties. The way you currently have it, all crab instances all share the same BOOLs self.crabTouchLeftWall and self.crabTouchRightWall.

You are checking for the BOOL values contacts in the update method and then running [crabNode stop];. You could do this directly when the contact is registered in the didBeginContact method.

The reason I earlier suggested you use unique names for each crab instance is because when it comes time to remove them from your array, you need to be able to point to which specific crab instance needs removing. Having a unique name just makes things easier.

1
Whirlwind On

Be aware that moving nodes manually like that can produce odd results. When using physics engine in Spritekit you have to let it to do calculation by itself, meaning you have to move objects by using forces. This not applies for contact detection, and you can move nodes around if you only need contact detection. But if you need more than just contact detection eg. to simulate collisions and restrict nodes from penetrating each other, then I suggest you to use forces appropriately (applyImpulse:, applyForce: are suitable methods).

You could probably solve your issue by moving the code from your update: method to 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 and similar.

And to point once again , which is not related to your question, but it can be useful: You can use physics engine for contact detection even if you move your nodes, for example by SKAction methods. Read more here.