What Are Sprite Kit'S "Category Mask" and "Collision Mask"

What are Sprite Kit's Category Mask and Collision Mask?

  • The Category bit mask tells Sprite-Kit what sort of object this is.
  • The Collision bit mask tells Sprite Kit what objects this object
    collides with (i.e. will hit and bounce off).
  • The ContactTest bit mask tells Sprite-Kit what contacts you want to
    be notified about i.e. when this object touches another object.

Collisions are handled automatically by the Sprite-Kit game engine; contacts are handled by your code - when a contact happens that you are interested in, your code (didBeginContact'for Swift 2,'didBegin(contact:) for Swift 3 and later) is called.

You need to think of these bitmasks in binary and for simplicity, we'll start with simple category bit masks, whereby each object in your scene belongs to only one category.

E.g. in your scene you have a player, a player-missile, an enemy and the screen-edge. We'll assume that the bit masks are 8-bit instead of 32-bit. Think of each number as a range of 8 binary digits (8 bits) each of which is a 0 or 1.

The objects in your scene have to have unique categories, so we'll assign them as follows:

player.categoryBitMask        = 00000001  (1 in decimal)
playerMissile.categoryBitMask = 00000010 (2 in decimal)
enemy.categoryBitMask = 00000100 (4 in decimal)
screenEdge.categoryBitMask = 00001000 (8 in decimal)

If you use numbers that are, in decimal, something other than a power of 2 (1, 2, 4, 8, 16, 32, 64, 128 etc) then more than 1 bit will be set in the category bit mask which complicates things (it means that this object belongs to multiple categories) and your code has to get more complicated.

Now think about what bounces off what (the collisions). Let's say everything bounces off everything else except the missile goes through the screen edge.

The collision bit masks for each object consists of the bits that represent the objects that this object collides with i.e.:

player.collsionBitMask         = 00001111 (15 in decimal) (the bits for all other objects are set)
playerMissile.collisionBitMask = 00000111 (7) (all object EXCEPT screenEdge)
enemy.collisonBitMask = 00001111 (15) (everything)
screenEdge.collisonBitMask = 00000000 (0) (collides with nothing)

Now we think about which object interactions we are interested in. We want to know when:

  • player and enemy touch
  • enemy and player touch or the enemy and the player's missile touch.

This is represented by:

player.contactTestBitMask = 00000100 (4) (the enemy's categoryBitMask)
enemy.contractTestBitMask = 00000011 (3) (the player's categoryBitMask combined with the missile's categoryBitMask))

Note that if you want to know when A and B touch, it's enough just to have A's contactTestBitMask (CTBM) to include B's categoryBitMask; you don't have to have B's CTBM set to A's category too. But if you want them to bounce off each other, then each object's collisionBitMask must include the other's categoryBitMask. If A's collisonbitMask includes B's category, but not vice versa, then when the 2 collide, A will bounce off but B will be unaffected by it.

For your specific example, (Category Mask to 32 and collision mask to 11), the categoryBitMask is 32 which is 00100000 (sticking with only 8 bits). The collisionBitMask is 11, which is 8 + 4 + 1 so in binary this is '00001101' which means it collides with objects with a categoryBitMask of 8, 4 or 1, so I assume that there are objects in your scene with these categoryBitMasks.

By default everything bounces off everything else i.e. the collisionBitMask is all '1's i.e. b'111111..' and nothing notifies of contacts with anything else i.e. contactTestBitMask is all '0's.

Also - all of this applies to physicsBodies, not nodes.

How does collisionBitMask work? Swift/SpriteKit

You can't get desired behaviour because you haven't set category, contact and collision bit masks properly. Here is an example on how you can set this to work:

greenBall.physicsBody?.categoryBitMask = GreenBallCategory //Category is GreenBall
greenBall.physicsBody?.contactTestBitMask = RedBarCategory | WallCategory //Contact will be detected when GreenBall make a contact with RedBar or a Wall (assuming that redBar's masks are already properly set)
greenBall.physicsBody?.collisionBitMask = GreenBallCategory | RedBallCategory | WallCategory //Collision will occur when GreenBall hits GreenBall, RedBall or hits a Wall

redBall.physicsBody?.categoryBitMask = RedBallCategory //Category is RedBall
redBall.physicsBody?.contactTestBitMask = GreenBarCategory | GreenBallCategory | WallCategory //Contact will be detected when RedBall make a contact with GreenBar , GreenBall or a Wall
redBall.physicsBody?.collisionBitMask = RedBallCategory | GreenBallCategory | WallCategory //Collision will occur when RedBall meets RedBall, GreenBall or hits a Wall

let borderBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsBody = borderBody
self.physicsBody?.friction = 0
borderBody.contactTestBitMask = RedBallCategory | GreenBallCategory //Contact will be detected when red or green ball hit the wall
borderBody.categoryBitMask = WallCategory
borderBody.collisionBitMask = RedBallCategory | GreenBallCategory // Collisions between RedBall GreenBall and a Wall will be detected

I would recommend you to read docs about categoryBitMask which is a mask that defines which categories a physics body belongs to:

Every physics body in a scene can be assigned to up to 32 different
categories, each corresponding to a bit in the bit mask. You define
the mask values used in your game. In conjunction with the
collisionBitMask and contactTestBitMask properties, you define which
physics bodies interact with each other and when your game is notified
of these interactions.

contactTestBitMask - A mask that defines which categories of bodies cause intersection notifications with a current physics body.

When two bodies share the same space, each body’s category mask is
tested against the other body’s contact mask by performing a logical
AND operation. If either comparison results in a nonzero value, an
SKPhysicsContact object is created and passed to the physics world’s
delegate. For best performance, only set bits in the contacts mask for
interactions you are interested in.

collisionBitmask - A mask that defines which categories of physics bodies can collide with this physics body.

When two physics bodies contact each other, a collision may occur.
This body’s collision mask is compared to the other body’s category
mask by performing a logical AND operation. If the result is a nonzero
value, this body is affected by the collision. Each body independently
chooses whether it wants to be affected by the other body. For
example, you might use this to avoid collision calculations that would
make negligible changes to a body’s velocity.

So basically, to set up all these, you should ask your self something like:

Okay, I have a green ball, and a red ball , and wall objects on the scene. Between which bodies I want collisions to occur, or when I want to register contacts? I want a green and a red ball to collide with each other and to collide against the walls. Not a problem. I will first set up categories properly, and then I will set collision bit masks like this:

greenBall.collisionBitMask = GreenBallCategory | RedBallCategory | WallCategory; //greenBall will collide with greenBall, redBall and a wall
redBall.collisionBitMask = GreenBallCategory | RedBallCategory | WallCategory

wall.collisionBitMask = GreenBall | RedBall

Now, I want to detect when some contacts occur (in didBeginContact: method)... But I don't want to get notified about all possible contacts, but rather to be notified just about contacts between balls (contacts between balls and a wall will be ignored). So lets set contactTestBitMasks to achieve this:

greenBall.contactTestBitMask = GreenBallCategory | RedBallCategory;
redBall.contactTestBitMask = GreenBallCategory | RedBallCategory;

And that's it. The important thing is when you not using contact detection, you should not set contactTestBitMask. This is because of performance reasons. If you don't need collision detection, and you are interested only in detecting contacts, you can set collisionBitMask = 0.

Important:

Make sure you have set physics world's contact delegate in order to use didBeginContact and didEndContact methods:

self.physicsWorld.contactDelegate = self; //where self is a current scene

Hope this helps a bit.

prevent Spritekit nodes with the same Catagorymask from colliding

It's bit mask in binary.

So it's better to use some bit operator:

enum CategoryMask : UInt32 {
case playeragain = 0b00000001
case player = 0b00000010
case GBullet = 0b00000100
case BBullet = 0b00001000
case enemyships = 0b00010000
case coin = 0b00100000
case boss = 0b01000000
}



// case playeragain = 1<<0 //1
// case player = 1<<1 // 2
// case GBullet = 1<<2 // 4
// case BBullet = 1<<3 //8
// case enemyships = 1<<4 //16
// case coin = 1<<5 //32
// case boss = 1<<6 //64

//

This should give you what you really what.

It's an example from Apple Document and redball will hit ground and blue ball will not.

    let ground = SKShapeNode()

redBall.physicsBody?.collisionBitMask = 0b0001
blueBall.physicsBody?.collisionBitMask = 0b0010
ground.physicsBody?.categoryBitMask = 0b0001

Can't understand how collision bit mask works

That is not how collision handling works. When two bodies are in intersection, physics engine performs logical AND operator between current's body collisionBitMask and other's body categoryBitMask:

When two physics bodies contact each other, a collision may occur.
This body’s collision mask is compared to the other body’s category
mask by performing a logical AND operation. If the result is a nonzero
value, this body is affected by the collision. Each body independently
chooses whether it wants to be affected by the other body. For
example, you might use this to avoid collision calculations that would
make negligible changes to a body’s velocity.

The source.

So the result depending on how you set categoryBitMask on those two bodies. A default value for categoryBitMask is 0xFFFFFFFF, means all bits set. So when you perform & between 0xFFFFFFFF and 0b10 or 0b01, the result would be non-zero value, thus the collision.

So for example, setting your bodies like this:

spriteA.physicsBody?.categoryBitMask = 0b01
spriteA.physicsBody?.collisionBitMask = 0b01

and

spriteB.physicsBody?.categoryBitMask = 0b10
spriteB.physicsBody?.collisionBitMask = 0b10

will give you the result you want. Also, this is probably not the exact setup you need, it is just a simple example, and you will have to change values according to your needs. In this case, spriteA will collide only with bodies which have categoryBitMask set to 0b01. Same goes for spriteB, it will collide with bodies which have categoryBitMask set to 0b10.

Also, in the case if you don't want these sprites to be able to collide with anything, simply set their collisionBitMask properties to 0.

Physics contacts in SpriteKit

First, you need to define what your sprites are, this is what the categoryBitMask is for. Think of this as a binary name for your sprite, with each digit being a unique name. Now it is possible to give your sprite more than 1 name, but for simplicity purposes, let's keep it to one.

contactBitMask tells the sprites what names it should be looking for.

So during the physics phase, the engine will take a given sprite, and look for all other physics sprites with the search name provided in the contactBitMask.

After this, evaluations are performed against the original sprite and the sprites in the filtered list.

On a contact, didBegin(contact:) is called, and contact contains all the information you need, including the 2 contact bodies. You then check the categoryBitMask to get the name of the sprites in question, and you do your conditioning based on these names.

How to interpret this Swift SpriteKit example code of a physics body bitmask system

collisionMask is computed property that returns a UInt32 value that can be used as a physics body's collision bit mask. It's easier to understand how this computed property works if it is broken down into its functional parts.

But first, let's add an array of the RPColliderType objects that the PlayerBot should collide with to the definedCollisions dictionary:

RPColliderType.definedCollisions[.PlayerBot] = [.Obstacle, .TaskBot]

At this point, the definedCollisions dictionary contains a single item with PlayerBot and [.Obstacle, .TaskBot] as the key and value, respectively. Think of this as the categories that can collide with a PlayerBot are Obstacle and TaskBot.

We can now use .PlayerBot to retrieve the value (i.e., the array) from the dictionary:

let array = RPColliderType.definedCollisions[.PlayerBot]

Since collisionMask is defined in the RPColliderType, self is used as the dictionary key. Also, array is an optional since a value corresponding to the key may not exist in the dictionary.

The code then combines the array of RPColliderType objects into a single RPColliderType object using the reduce method. reduce takes two arguments: an initial value (with the same type as the elements of the array) and a function (or closure) that takes a value as an argument and returns a value. In this case, the initial value is a new RPColliderType object and the closure's argument and returned value are also RPColliderType objects:

array?.reduce(RPColliderType(), aFunction)

Apple's code uses a trailing closure instead of passing a function to reduce. From the docs,

If you need to pass a closure expression to a function as the function’s final argument and the closure expression
is long, it can be useful to write it as a trailing closure instead. A trailing closure is a closure expression that is
written outside of (and after) the parentheses of the function call it supports.

reduce iterates over the array and calls the closure with the initial value and each array element as arguments and the returned value is used as the initial value for the next iteration:

let mask = array?.reduce(RPColliderType()) {
initial, colliderType in
return initial.union(colliderType)
}

where initial keeps the intermediate union of the RPColliderType array elements and colliderType is the current element of array.

At this point, mask is an RPColliderType object that we can convert to a UInt32 with

mask?.rawValue

which is the returned value of the collisionMask computed property.



Related Topics



Leave a reply



Submit