0

The problem: As soon as the player node comes in contact with a coin node, the game ends when the game should only end when the player collides with the boundary.

What the output should be: The player should be able to come in contact with the coin node and travel through it, adding a value to the scoreLabel. The current code:

 struct ColliderType {

static let playerCategory: UInt32 = 0x1 << 0
static let boundary: UInt32 = 0x1 << 1
static let coinCategory: UInt32 = 0x1 << 2
static let firstBody: UInt32 = 0x1 << 3
static let secondBody: UInt32 = 0x1 << 4

}

   var gameOver = false
   var coinInt = 0

    coin.physicsBody?.categoryBitMask = ColliderType.coinCategory
    coin.physicsBody?.contactTestBitMask = ColliderType.playerCategory
    coin.physicsBody?.collisionBitMask = 0


    player.physicsBody?.categoryBitMask = ColliderType.playerCategory
    player.physicsBody?.contactTestBitMask = ColliderType.boundary | ColliderType.coinCategory
    player.physicsBody?.collisionBitMask = ColliderType.boundary

func didBeginContact(contact:SKPhysicsContact) {
    var firstBody: SKPhysicsBody
    var secondBody: SKPhysicsBody

    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        firstBody = contact.bodyA
        secondBody = contact.bodyB


    } else{

        firstBody = contact.bodyB
        secondBody = contact.bodyA

    }

    if firstBody.categoryBitMask == ColliderType.playerCategory && secondBody.categoryBitMask == ColliderType.coinCategory {
       self.coin.removeFromParent()
       coinInt += 1
       coinLabel.text = "\(coinInt)"
    }

    gameOver = true
    self.speed = 0
    timer.invalidate()

}

override func touchesBegan(touches: Set<UITouch> , withEvent event: UIEvent?) {
    if gameOver == false {

    self.player.physicsBody?.velocity = CGVectorMake(1, 3)
    self.player.physicsBody?.applyImpulse(CGVectorMake(0, 12))

    }

}

UPDATE:

    boundary.contactTestBitMask = ColliderType.playerCategory
    boundary.categoryBitMask = ColliderType.boundary
    boundary.collisionBitMask = ColliderType.playerCategory

   coin.physicsBody?.categoryBitMask = ColliderType.coinCategory
   coin.physicsBody?.contactTestBitMask = ColliderType.playerCategory
   coin.physicsBody?.collisionBitMask = 0


    player.physicsBody?.categoryBitMask = ColliderType.playerCategory
    player.physicsBody?.contactTestBitMask = ColliderType.coinCategory
    player.physicsBody?.collisionBitMask = ColliderType.boundary




func didBeginContact(contact:SKPhysicsContact) {
    let firstBody: SKPhysicsBody
    let secondBody: SKPhysicsBody


    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        firstBody = contact.bodyA
        secondBody = contact.bodyB


    } else{

        firstBody = contact.bodyB
        secondBody = contact.bodyA

    }


    if firstBody.categoryBitMask == ColliderType.coinCategory || secondBody.categoryBitMask == ColliderType.coinCategory {

    self.coin.removeFromParent()
    self.coinInt += 1
    self.coinLabel.text = "\(self.coinInt)"

    }else if firstBody.categoryBitMask == ColliderType.boundary || secondBody.categoryBitMask == ColliderType.boundary {

    gameOver = true
    self.speed = 0
    timer.invalidate()

}
}
P.Dane
  • 91
  • 8
  • Regarding your edits: Your player not checking for boundaries, but rather the other way around could make some sense. But then you probably don't want the _player_ to check for any contact at all to simplify things. – T. Benjamin Larsen Jun 12 '16 at 07:47

3 Answers3

2

Your gameOver = true statement is outside all the ifs in didBeginContact. In other words: The moment a contact happens you set gameOver = true.

if firstBody.categoryBitMask == ColliderType.coinCategory || secondBody.categoryBitMask == ColliderType.coinCategory {
   self.coin.removeFromParent()
   coinInt += 1
   coinLabel.text = "\(coinInt)"
} else if firstBody.categoryBitMask == ColiderType.boundary || secondBody.categoryBitMask == ColiderType.boundary {
    gameOver = true
    self.speed = 0
    timer.invalidate()
}

Is probably closer to what you want.

T. Benjamin Larsen
  • 6,373
  • 4
  • 22
  • 32
  • Hi there, sorry for the delayed reply. That code looks like exactly what I need, but when I test it, the first if statement does not function but the else if function works just like it should. I've been tinkering with my code, testing different methods and everything looks like it should work flawlessly but that first if statement is currently non-functional. – P.Dane Jun 12 '16 at 06:53
  • Actually it looks like you should use if `firstBody.categoryBitMask == ColliderType.coinCategory || secondBody.categoryBitMask == ColliderType.coinCategory` since the coin only check if it is in contact with the player. – T. Benjamin Larsen Jun 12 '16 at 07:17
  • I replaced the first if statement with the one that you had provided and I seem to notice any changes regarding the first if statement not functioning the way it should. One sec and I will update my question with my current code. I am still learning swift so I really appreciate your help thus far. – P.Dane Jun 12 '16 at 07:34
2

(Adding a new answer, as it is based on new code and the discussion/changes in the O.P.-question would make the logic-flow a bit hard to follow)

First I would make the following change to the physicBodies:

player.physicsBody?.contactTestBitMask = 0 // or ColliderType.None which would be my stylistic choice

Then the rest would read something like this.

func didBeginContact(contact:SKPhysicsContact) {
    let firstBody = contact.bodyA
    let secondBody = contact.bodyB

    if firstBody.categoryBitMask == ColliderType.coinCategory || secondBody.categoryBitMask == ColliderType.coinCategory {
        self.coin.removeFromParent()
        self.coinInt += 1
        self.coinLabel.text = "\(self.coinInt)"
    } else if firstBody.categoryBitMask == ColliderType.boundary || secondBody.categoryBitMask == ColliderType.boundary {
        gameOver = true
        self.speed = 0
        timer.invalidate()
    }
}

A note: It is not clear from the provided code how the self.coin is referenced. I guess the self.coin.removeFromParent() might not make too much sense as you are writing about several coins in the initial post. It should probably be something like this instead:

if contact.bodyA.categoryBitMask == ColliderType.coinCategory {
    contact.bodyA.node!.removeFromParent()
} else if contact.bodyB.categoryBitMask == ColliderType.coinCategory {
    contact.bodyB.node!.removeFromParent()
}

but I really consider reworking the whole contact-handling into a different beast altogether if you plan to expand on this down the line.

T. Benjamin Larsen
  • 6,373
  • 4
  • 22
  • 32
  • 1
    The code that you have provided has fixed the issue and now the coin collision functions flawlessly! Thank you for your help! I am going to make a note to come back to this post when I achieve 15 reputation so that I can up vote this answer. – P.Dane Jun 13 '16 at 03:52
1

A cleaner way to code didBeginContact, which reduces the mucking about with firstBody/secondbody, is:

    func didBeginContact(contact: SKPhysicsContact) {
        let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask

        switch contactMask {

        case ColliderType.playerCategory | ColliderType.coinCategory:
            // player and coin have contacted. We need to get the coin, which is either firstBody or secondBody,  to remove it, so assign the corect one to coinNode
            let coinNode = contact.bodyA.categoryBitMask == ColliderType.coinCategory ? contact.bodyA.node! : contact.bodyB.node!
             coinNode.removefromParent
             coinInt += 1
             coinLabel.text = "\(coinInt)"

        case ColliderType.playerCategory | ColliderType.boundaryCategory:
            // player and boundary have contacted
                gameOver = true
            self.speed = 0
            timer.invalidate()

        default :
            //Some other contact has occurred
            print("Some other contact")
        }
    }

You can add as many case ColliderType.object1 | ColliderType.object2: as you like.

Steve Ives
  • 7,894
  • 3
  • 24
  • 55
  • Only issue with this is objects with 2 categories get excluded – Knight0fDragon Jun 10 '16 at 17:42
  • Not sure that's true, as we're testing if certain bits within the category but masks are set, not the whole category (at least, that was the intention). – Steve Ives Jun 10 '16 at 17:47
  • player = 0001 , coin = 0010 superpower = 0100 if node becomes 0101 because it has superpower, then the case of player | coin fails because you are comparing 0111 to 0011, but you want it to pass, and it would be ridiculous to have to branch out to every case to achieve this – Knight0fDragon Jun 10 '16 at 18:25
  • Yep - you're right, but I imagine that's a fairly unusual situation. For that, I think that if you perform a bitwise AND between the contactMask and the 2 categories you want to check for contact, a non-0 result means a hit. – Steve Ives Jun 10 '16 at 22:40
  • Pretty sure I've a comment in my code somewhere about how I should really test every bit... I'm taking this as a personal challenge. – Steve Ives Jun 10 '16 at 22:45
  • Thank you for your answer Steve and showing me a different approach to this. I have tested and played around with the code that you've provided. When I test the code, there doesn't seem to be any contact being made between the player and coin. The second case involving the player and boundary is functioning perfectly, but I seem to be having a little trouble with the first case. The player doesn't travel through the coin, but it doesn't bounce off of the coin either, as it seems to be pushed backwards when making contact with the coin. – P.Dane Jun 12 '16 at 07:24
  • @P.Dane Looking at your update, you have the coin's collisionBitMask set to 0, so it won be affected by a collision with anything. this is why the cin is unaffected when it hits the player. The player, however, is set to only collide with the boundary. You've also got your firstBody || secondBody logic both referencing the ColliderType.coinCategory in the first instance and ColliderType.boundary in the second - so the first test will fire only if one of the bodies is a coin and the second only if one of the bodies is the boundary (I think). – Steve Ives Jun 14 '16 at 16:06
  • @P.Dane. Also, self.coin.removeFromParent() in your first collision test should be contact.bodyA.node.removeFromParent(). – Steve Ives Jun 14 '16 at 16:08
  • @P.Dane Add the `func checkPhysics()` from this answer http://stackoverflow.com/questions/36570859/ios-spritekit-collisions-and-contacts-not-working-as-expected and then call `checkPhysics()` once you have set up everything as you think it should be and see if the printed results match what you expect. – Steve Ives Jun 14 '16 at 16:09
  • Hi Steve, Thank you for your response and the helpful information! I have now managed to get the collision between player and coin to function properly! The next step for me now is to expand this collision by adding different types of coins for the player to collide with, 3 or 4 more nodes in total. – P.Dane Jun 15 '16 at 07:28
  • @P.Dane you're welcome. Please up-vote any answers & comments that have helped you. This is not only for my benefit :-) but also to anyone who has a similar problem. I'd also be grateful if you could try my checkPhysics() function, to see if it works for you. – Steve Ives Jun 15 '16 at 08:52
  • I will make note to do so when I reach 15 reputation! – P.Dane Jun 16 '16 at 06:23