pablo pablo - 1 month ago 9
Swift Question

Stop objects from colliding using SpriteKit

I am testing out the features of SpriteKit and I ran into a problem. I was reading into bit masks, colliding, category, and contact. I get what they are, mostly at least, I don't get the point of category bitmasks, but I get colliding bitmasks which are the ones I need to solve my problem.

Ok so my problem is I have two different types of sprites: object and second. The names don't really make much sense but it is just for the sake of testing. I want second to have an impulse, and I want object to have a force. I was able to apply the respective vectors on the sprites, but I do not want them to collide with each other. I want them to pass right through and ignore the existence of each other.

I tried to solve that issue by assigning different collision bitmasks to each other:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let texture = SKTexture(imageNamed: "pokeball")
let object = SKSpriteNode(texture: texture)

object.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: object.size.width,height: object.size.height))
object.physicsBody?.affectedByGravity = false
object.yScale = 0.5
object.xScale = 0.5

for t in touches {
object.position = t.location(in: self)
}
self.addChild(object)
object.physicsBody?.collisionBitMask = UInt32(4)
object.physicsBody?.applyForce(CGVector(dx: 0, dy: 10))

}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let texture = SKTexture(imageNamed: "purple")
let second = SKSpriteNode(texture: texture)
let impulse : Double = 20
let x = (impulse * Double(cosf(45)))
let y = Double(impulse * Double(sinf(45)))
let vector = CGVector(dx: x, dy: y)
second.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: second.size.width,height: second.size.height))

second.yScale = 1.5
second.xScale = 1.5
second.physicsBody?.isDynamic = true

for t in touches {
second.position = t.location(in: self)
}
self.addChild(second)
second.physicsBody?.collisionBitMask = UInt32(1)
second.physicsBody?.applyImpulse(vector)
}


So object has a bitmask of 4:

object.physicsBody?.collisionBitMask = UInt32(4)


And second has a bitmask of 1:

second.physicsBody?.collisionBitMask = UInt32(1)


I ran the simulator and they are still colliding with each other, so I went online and tried to look for some answers: I found one that says I must use numbers like:


these are bitmasks, you can't use arbitrary numbers 1,2,3,4,5 - you must use 1,2,4,8,16 and so on –


Can someone explain why? However, that wasn't the issue because I was using 1 and 4

Next question I ran into said that I had to use binary numbers (0100) and (0010), I tried them, same issue: still colliding.

I will leave a picture of the collisions:
Collisions

Does anyone know why this is happening? My apologies in advance if this is a really dumb mistake or something that has already been asked, I just couldn't find it.

Answer

There is a lot of documentation on these topics, but here is a practical example.

The power of categoryBitMasks

Pretend you have a collection of three nodes pool, basketball and bowlingball. Now, obviously, we want the basketball and bowlingball to collide with the each other. So you set the collisionBitMasks like so:

basketball.physicsBody?.collisionBitMask    = UInt32(2)
bowlingball.physicsBody?.collisionBitMask   = UInt32(2)

Great. Now, we want the bowlingball to sink to the bottom of the pool, and the basketball to collide with the pool (might be more of a splash, but bear with me). How would we do this? We could try:

pool.physicsBody?.collisionBitMask = UInt32(2) // ?

But wait, that would make the basketball AND the bowlingball collide with the pool. We only want the basketball to collide with the pool , whereas we want the bowlingball to ignore the pool and sink straight to the bottom with no collisions. This is where categoryBitMasks come in handy:

let basketballBitMask     = UInt32(1)
let bowlingballBitMask    = UInt32(2)
let poolBitMask           = UInt32(4)  // Why 4? See next section

basketball.physicsBody?.categoryBitMask    = basketballBitMask
bowlingball.physicsBody?.categoryBitMask   = bowlingballBitMask
pool.physicsBody?.categoryBitMask          = poolBitMask

Because each object has a unique number assigned to it, you can now pick and choose which objects you'd like another object to collide with:

// basketball physics body collides with bowlingball(2) OR pool(4)
basketball.physicsBody?.collisionBitMask    = bowlingballBitMask | poolBitMask
// ( '|' = logical OR operator)

// bowlingball physics body only collides with basketball(1)
bowlingball.physicsBody?.collisionBitMask   = basketballBitMask

// pool physics body only collides with basketball(1)
pool.physicsBody?.collisionBitMask          = basketballBitmask

If you're not sure what the strange '|' symbol is doing, I highly recommend the swift documentation on advanced operators to help you understand what's happening here.

Why not just use collisionBitMasks for only two objects?

Okay so we've set some bit masks. But how are they used? If we only have two objects why can't we just compare collisionBitMasks?

Simply put, that's just not how it works. When a bowlingball comes into contact with the pool, the SpriteKit physics engine will AND ('&') together the bowlingball's categoryBitMask with the pool's collisionBitMask (or vice versa; the result is the same):

objectsShouldCollide = (bowlingball.physicsBody?.categoryBitMask & 
                        pool.physicsBody?.collisionBitMask)
// objectsShouldCollide = (ob010 & 0b100) = 0b000 

Because the bowlingball's categoryBitMask and the pool's collisionBitMask have zero bits in common, objectsShouldCollide is equal to zero, and SpriteKit will stop the objects from colliding.

But, in your case, you're not setting your objects' categoryBitMasks. So they have a default value of 2^32, or 0xFFFFFFFF (hexadecimal representation) or in binary, 0b11111111111111111111111111111111. So when an "object" hits a "second" object, SpriteKit does this:

objectsShouldCollide = (0b11111111111111111111111111111111 & // Default categoryBitMask for "object" 
                        0b00000000000000000000000000000001)  // collisionBitMask for "second" object
//                   =  0b00000000000000000000000000000001

So when you haven't defined the object's categoryBitMask, no matter what you set as the second object's collisionBitMask, objectsShouldCollide will never be zero, and so they will always collide.

Note: you could set an object's collisionBitMask to 0; but then that object would never be able to collide with anything.

Using powers of 2 (0,1,2,4,8, etc.) for categoryBitMasks

Now let's say we wanted to include multiple bowlingballs that collided with each other. Easy:

bowlingball.physicsBody?.collisionBitMask  = basketballBitMask | bowlingballBitMask
// bowlingball collision bit mask (in binary)  = 0b10 | 0b01 = 0b11
// bowlingball collision bit mask (in decimal)  = 2 | 1 = 3 

Here you can see that if we had set the pools physicsCategory to UInt32(3), it would no longer be distinguishable from a bowlingball or basketball.

Further suggestions

Learn to name variables with purpose, even if you're just using them for testing (although, coincidentally, "object and second object" worked quite well).

Use a struct for bitmasks to simplify your code and improve readability:

struct PhysicsCategory {
    static let Obj1         : UInt32 = 0b1 << 0
    static let Obj2         : UInt32 = 0b1 << 1
    static let Obj3         : UInt32 = 0b1 << 2
    static let Obj4         : UInt32 = 0b1 << 3
}

obj1.physicsBody?.categoryBitmask = PhysicsCategory.Obj1 // etc
Comments