3

I'm creating a game with SpriteKit, that has collision between 2 bodies. After setting up the bodies, I've implemented the didBegin(_contact:) moethod as shown below:

func didBegin(_ contact: SKPhysicsContact) {
    if contact.bodyA.categoryBitMask == 0 && contact.bodyB.categoryBitMask == 1 {
        gameOver()
    }
}

and it worked perfectly.

Later, while inspecrting the documentation for this method, I found the following:

The two physics bodies described in the contact parameter are not passed in a guaranteed order.

So to be on the safe side, I've extended the SKPhysicsContact class with a function the swaps the categoryBitMask between both bodies, as following:

extension SKPhysicsContact {

    func bodiesAreFromCategories(_ a: UInt32, and b: UInt32) -> Bool {
        if self.bodyA.categoryBitMask == a && self.bodyB.categoryBitMask == b { return true }
        if self.bodyA.categoryBitMask == b && self.bodyB.categoryBitMask == a { return true }
        return false
    }
}

The problem is that when the function gets called, the app crashes, and I get the following error:

2017-07-18 13:44:18.548 iSnake Retro[17606:735367] -[PKPhysicsContact bodiesAreFromCategories:and:]: unrecognized selector sent to instance 0x60000028b950

2017-07-18 13:44:18.563 iSnake Retro[17606:735367] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[PKPhysicsContact bodiesAreFromCategories:and:]: unrecognized selector sent to instance 0x60000028b950'

Community
  • 1
  • 1
Fayyouz
  • 682
  • 7
  • 18
  • this is a cool extension, but usually you just add the category masks together to avoid this problem altogether. – Fluidity Jul 18 '17 at 11:53
  • @Fluidity Do you mean by replacing both values by `0 | 1`? If yes, this if quite impossible, because the collision between 2 bodies of the same category produces a different action from the one from 2 different categories – Fayyouz Jul 18 '17 at 11:58
  • 1
    no, see my answer. You add the category masks together to get a unique sum. You have to set your masks in powers of two for this to work. 2+2 always equals 4, and 2 + 4 always equals six, even if it is 4 + 2 – Fluidity Jul 18 '17 at 12:07
  • 1
    you should not be using 0 for a body, as this will give you non-unique contacts, example 0+4 is 4, but so is 2+2 – Fluidity Jul 18 '17 at 12:41
  • 2
    Additional info about all this : https://forums.developer.apple.com/thread/24545 – Whirlwind Jul 18 '17 at 12:51
  • 0 honestly shouldn't even work at all (it shouldn't register any contact) – Fluidity Jul 18 '17 at 19:26

2 Answers2

2

SKPhysicsContact is a wrapper class to PKPhysicsContact, you are extending SKPhysicsContact but in reality you need to extend PKPhysicsContact (Which you can't do)

To preserve order in your contact methods, just do:

let bodyA = contact.bodyA.categoryBitMask <= self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB

let bodyB = contact.bodyA.categoryBitMask > self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB

This way when you need to check for a specific node, you know what node to hit, so

func didBegin(_ contact: SKPhysicsContact) {
    if contact.bodyA.categoryBitMask == 0 && contact.bodyB.categoryBitMask == 1 {
        gameOver()
    }
}

Becomes

func didBegin(_ contact: SKPhysicsContact) {

    let bodyA = contact.bodyA.categoryBitMask <= self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB

    let bodyB = contact.bodyA.categoryBitMask > self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
    if bodyA.categoryBitMask == 0 && bodyB.categoryBitMask == 1 {
        gameOver()
    }
}

You can then add to your code since you now know the individual bodies.

func didBegin(_ contact: SKPhysicsContact) {

    let bodyA = contact.bodyA.categoryBitMask <= self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB

    let bodyB = contact.bodyA.categoryBitMask > self.bodyB.categoryBitMask ? contact.bodyA : contact.bodyB
    if bodyA.categoryBitMask == 0 && bodyB.categoryBitMask == 1 {
        gameOver()
        //since I know bodyB is 1,  let's add an emitter effect on bodyB.node
    }
}

BTW, for people who see this answer, categoryBitMask 0 should not be firing any contacts, you need some kind of value in it to work. This is a bug that goes beyond the scope of the authors question, so I left it at 0 and 1 to since that is what his/her code is doing and (s)he is claiming it works.

Knight0fDragon
  • 16,609
  • 2
  • 23
  • 44
  • this (and the documentaton) make a lot more sense now with the extra info – Fluidity Jul 18 '17 at 20:56
  • @Fluidity, just add a comment when you don't understand something and I will elaborate on it – Knight0fDragon Jul 18 '17 at 20:57
  • I do, we even went to chat a few times about it. – Fluidity Jul 18 '17 at 20:58
  • I think the key thing that I needed was "we will just sort based on lowest bitmask to highest, so we will make a tuple (x, y) and x will always be the lowest category.) that was more obvious now in this example – Fluidity Jul 18 '17 at 20:59
  • well add a comment on the answer. instead of just downvoting LOL, give me a chance to improve my answer – Knight0fDragon Jul 18 '17 at 20:59
  • I told you that I thought your answer didn't address it in my answers comments – Fluidity Jul 18 '17 at 21:00
  • "also my addition idea is perfect, because he doesn't care about preserving body order, only if there is a unique collision to result in a game over :)" your first answer was a couple sentences that talked about preserving body order, which made no sense to me and didn't relate to what OP was asking. now that you added teh rest of answer, it makes sense – Fluidity Jul 18 '17 at 21:01
  • well my code in documentation was for advance users. LOL comment on THIS answer so that I know it was you who downvoted so I could let you know that it was fixed. I thought it was the OP who downvoted. – Knight0fDragon Jul 18 '17 at 21:01
  • Don't be snide, your code may be obvious to some but the biggest part of being a genius is being able to explain things instead of just showing and expecting others to know instantly what you are doing :) – Fluidity Jul 18 '17 at 21:03
  • Are we talking about this answer or documentation now – Knight0fDragon Jul 18 '17 at 21:06
  • Both, I think a simple "assign bodyA to the lowest bitmaks, and bodyB to the highest" would go a long way. Looking at it now it's like "duh" but before I was so used to looking at physics done the other way, it just was kind of like a dyslexia thing.. Due to my own biases I was "looking for something else" and just the code on screen didn't make sense. I would have had to go to xcode and try it out to understand. And also, in the documentation, that is a pretty big one liner, so a comment IMO would help.. – Fluidity Jul 18 '17 at 21:14
  • well documentation is an addendum to the other code already in place, to repeat what is happening would be redundant. If you think you can word anything better, go at it, that is what edit is there for. – Knight0fDragon Jul 18 '17 at 21:36
2

This apparently is a bug, as answered here: https://stackoverflow.com/a/33423409/6593818

The problem is, the type of contact is PKPhysicsContact (as you've noticed), even when you explicitly tell it to be an SKPhysicsContact, and the extension is on SKPhysicsContact. You'd have to be able to make an extension to PKPhysicsContact for this to work. From this logic, we can say that no instance methods will work in SKPhysicsContact extensions at the moment. I'd say it's a bug with SpriteKit, and you should file a radar. Class methods still work since you call them on the class itself.

In the meantime, you should be able to move that method into your scene or another object and call it there successfully.

For the record, this is not a Swift-specific problem. If you make the same method in an Objective-C category on SKPhysicsContact you'll get the same crash.

You can submit a bug report to apple:

https://developer.apple.com/bug-reporting/

And report it to the community:

https://openradar.appspot.com/search?query=spritekit

However, what you really want to do with your code is to add the category masks together. And then check for the sum (2 + 4 and 4 + 2 always equals 6, regardless of bodyA and bodyB order).

This is how you get unique contacts, if you set up your masks correctly in powers of two (2, 4, 8, 16, etc)

Community
  • 1
  • 1
Fluidity
  • 3,985
  • 1
  • 13
  • 34
  • that is not a bug – Knight0fDragon Jul 18 '17 at 12:25
  • also adding them together is a terrible idea, you still do not know which body is which, so there is no way to guarantee what order the bodies are in – Knight0fDragon Jul 18 '17 at 12:27
  • @Knight0fDragon this is a bug. Because of Apple's poor design decision thus breaking code that should work. It's like trying to eat an apple then realizing that is plastic, when every other piece of fruit is edible. The plastic apple needs to be "fixed" – Fluidity Jul 18 '17 at 12:38
  • @Knight0fDragon also my addition idea is perfect, because he doesn't care about preserving body order, only if there is a unique collision to result in a game over :) – Fluidity Jul 18 '17 at 12:41
  • 1
    no, all of SKPhysics classes are wrapper classes, that is their intended use and it is by design. You are not suppose to extend it. Bugs are when things happen that are not by design. What you are proposing is only a work around that works in this one case, which means it can't be broadened to other projects. Finally: he mentions `The two bodies described in the contact parameter are not passed in a guaranteed order.` in his concern that he is trying to address. Your way is no different than his original way, with the exception of just checking bodyA for 0 or 1 instead of both bodies. – Knight0fDragon Jul 18 '17 at 12:48
  • @Knight0fDragon I use category in addition in all of my programs, and then use a func to sort the bodies. This is very applicable to other programs, and is how have done bitmasks for almost two decades now. May not be the best way, but it is a way. – Fluidity Jul 18 '17 at 12:51
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/149484/discussion-between-fluidity-and-knight0fdragon). – Fluidity Jul 18 '17 at 12:54