A few problems here.
- You define categories so that they are not easily tested.
- You test categories so that you donโt get the unique answers you want.
- You messed up your code trying to track up to four bodies in one contact. Any contact will always have exactly two bodies.
Allow them one at a time ...
1. Definition of categories
You want to define collision categories so that each body type in your game uses its own mask bit. (You have a good idea using Swift binary literals, but you define categories that overlap.) Here is an example of non-overlapping categories:
struct PhysicsCategory: OptionSet { let rawValue: UInt32 init(rawValue: UInt32) { self.rawValue = rawValue } static let enemy = PhysicsCategory(rawValue: 0b001) static let bullet = PhysicsCategory(rawValue: 0b010) static let spiral = PhysicsCategory(rawValue: 0b100) }
I use the Swift OptionSet type for this because it makes it easy to create and test combinations of unique values. This makes the syntax for defining my type and its members a bit cumbersome compared to enum , but it also means that I don't need to do a lot of boxing and unpack the raw values โโlater, especially if I also make convenient accessors like this:
extension SKPhysicsBody { var category: PhysicsCategory { get { return PhysicsCategory(rawValue: self.categoryBitMask) } set(newValue) { self.categoryBitMask = newValue.rawValue } } }
In addition, I use binary literature and extra spaces and zeros in my code to make it easy to make sure that each category gets its own bit - the enemy gets only the least significant bit, the bullet next, etc.
2 and 3. Categories of testing and tracking
I like to use a two-tier approach to contact handlers. First of all, I check which collision is a bullet / enemy collision or a bullet / spiral collision or a spiral / enemy collision? Then, if necessary, I will check which body is in the collision. This is not computationally expensive, and in every part of my code it is very clear what is happening.
func didBegin(_ contact: SKPhysicsContact) {
Additional loan
Why use if and the OptionSet method of type contains() ? Why not make something like this switch , which makes the syntax for testing values โโmuch shorter?
switch contactCategory { case [.enemy, .bullet]: // ... case [.enemy, .spiral]: // ... // ... default: // ... }
The problem with using switch is that it checks your OptionSet for equality - that is, case # 1 is triggered if contactCategory == [.enemy, .bullet] , and will not fail if it is [.enemy, .bullet, .somethingElse] .
With the contact categories that we defined in this example, this is not a problem. But one of the nice features of the category bit / contact bit system is that you can encode multiple categories for one item. For instance:
struct PhysicsCategory: OptionSet {
In such a situation, you may have a contact whose category is [.ship, .bullet, .enemy] - and if your contact processing logic is tested specifically for [.ship, .bullet] , you will skip it. If you use contains instead, you can test your flags without worrying about having other flags.