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.
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.
Game Engine Collison Bitmask... Why 0x01 etc?
The reason for the bitmasks is that it enables you / the program to easily and very quickly compute wether a collision between two objects occurs or does not occur. Therefore: yes it is some sort of optimization.
Assuming we have the three categories
- missile
0x1 << 0
- player
0x1 << 1
- wall
0x1 << 2
Now we have a Player
instance, its category is set to player
. Its collision bitmask is set to missile | player | wall
(+
instead of |
works too) since we want to be able to collide with all three types: other players, the level walls and the bullets / missiles flying around.
Now we have a Missile
with category set to missile
and collision bitmask set to player | wall
: it does not collide with other missiles but hits players and walls.
If we now want to evaluate wether two objects can collide with each other we take the category bitmask of the first one and the collision bitmask of the second one and simply &
them:
The setup described above looks like the following in code:
let player : UInt8 = 0b1 << 0 // 00000001 = 1
let missile : UInt8 = 0b1 << 1 // 00000010 = 2
let wall : UInt8 = 0b1 << 2 // 00000100 = 4
let playerCollision = player | missile | wall // 00000111 = 7
let missileCollision = player | wall // 00000101 = 5
The subsequent reasoning is basically:
if player & missileCollision != 0 {
print("potential collision between player and missile") // prints
}
if missile & missileCollision != 0 {
print("potential collision between two missiles") // does not print
}
We are using some bit arithmetics here, each bit represents a category.
You could simply enumerate the bitmasks 1,2,3,4,5... but then you could not do any math on them. Because you do not know if a 5 as category bitmask is really a category 5 or it was an object of both categories 1 and 4.
However using only bits we can do just that: the only representation in terms of powers of 2 of a 7 is 4 + 2 + 1: therefore whatever object posses collision bitmask 7 collides with category 4, 2 and 1. And the one with bitmask 5 is exactly and only a combination of category 1 and 4 - there is no other way.
Now since we are not enumerating - each category uses one bit and the regular integer has only 32 (or 64) bits we can only have 32 (or 64) categories.
Take a look at the following and a bit more extensive code which demonstrates how the masks are used in a more general term:
let playerCategory : UInt8 = 0b1 << 0
let missileCategory : UInt8 = 0b1 << 1
let wallCategory : UInt8 = 0b1 << 2
struct EntityStruct {
var categoryBitmask : UInt8
var collisionBitmask : UInt8
}
let player = EntityStruct(categoryBitmask: playerCategory, collisionBitmask: playerCategory | missileCategory | wallCategory)
let missileOne = EntityStruct(categoryBitmask: missileCategory, collisionBitmask: playerCategory | wallCategory)
let missileTwo = EntityStruct(categoryBitmask: missileCategory, collisionBitmask: playerCategory | wallCategory)
let wall = EntityStruct(categoryBitmask: wallCategory, collisionBitmask: playerCategory | missileCategory | wallCategory)
func canTwoObjectsCollide(first:EntityStruct, _ second:EntityStruct) -> Bool {
if first.categoryBitmask & second.collisionBitmask != 0 {
return true
}
return false
}
canTwoObjectsCollide(player, missileOne) // true
canTwoObjectsCollide(player, wall) // true
canTwoObjectsCollide(wall, missileOne) // true
canTwoObjectsCollide(missileTwo, missileOne) // false
The important part here is that the method canTwoObjectsCollide
does not care about the type of the objects or how many categories there are. As long as you stick with bitmasks that is all you need to determine wether or not two objects can theoretically collide (ignoring their positions, which is a task for another day).
Intermittent errors with Bitmask Collisions
I wouldn't be so sure that the problem is solved. When I tried your code, it crashed cause one of the nodes of contact bodies was nil. I was able to produce crash when creating a physics body from texture, from rectangle, with a circle. It doesn't really matter... The problem is not that :)
The cause of this, is that you are removing your nodes before physics simulations are done.
Take a look at how one frame looks like:
So what was happening is that you remove your nodes before physics simulation is done, so Physics engine retain physics body cause its needed to finish calculations, but node is removed.
And thus nil in didBegin. So the solution is to make a variable that will hold nodes for removal:
private var trash:[SKNode] = []
Then at every place you have node with physics body do this:
(say your playerHitAsteroid method)
trash.append(laserNode)
trash.append(astroidNode)
self.run(SKAction.wait(forDuration: 2)) {[weak self] in
guard let `self` = self else {return}
self.trash.append(explosion)
}
You have few more places to change this in application. Take a look a this part too:
if livesArray.count == 0 {
trash.append(playerNode)
print("Game over")
}
and some more. But when you fix it at all places like this, you are ready to implement actual removal by overriding didSimulatePhysics
override func didSimulatePhysics() {
//first go through every node and remove it from parent
trash.map { node in
node.run(SKAction.sequence([SKAction.fadeOut(withDuration: 0.25), SKAction.removeFromParent()]))
}
trash.removeAll() // then empty thrash array before next frame
}
And finally you can change didBegin like this, just to catch this error immediately. Which won't happen if you follow this strategy which is:
- Remove nodes with physics bodies in didSimulatePhysics
- Set correctly bit masks (which as it seems, you have done correctly)
Maybe its worth of mention to be careful with Timer too. Check this out. It was long time ago, maybe something has changed, I didn't test it recently, but still I would prefer update
method or SKAction
for time related actions in my game.
So, change didBegin
like this:
func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node, let nodeB = contact.bodyB.node else {
fatalError("Physics body without its node detected!")
}
let A = contact.bodyA
let B = contact.bodyB
//PlayerLaser is A and Astroid is B
if (A.categoryBitMask & playerLaserCategory) != 0 && (B.categoryBitMask & astroidCategory) != 0 {
playerLaserHitAstroid(laserNode: A.node as! SKSpriteNode, astroidNode: B.node as! SKSpriteNode)
}
//PlayerLaser is A and Enemy is B
else if (A.categoryBitMask & playerLaserCategory) != 0 && (B.categoryBitMask & enemyCategory) != 0 {
playerLaserHitEnemy(laserNode: A.node as! SKSpriteNode, enemyNode: B.node as! SKSpriteNode)
}
//Player is A and Astroid is B
else if (A.categoryBitMask & playerCategory) != 0 && (B.categoryBitMask & astroidCategory) != 0 {
playerHitAstroid(playerNode: A.node as! SKSpriteNode, astroidNode: B.node as! SKSpriteNode)
}
//Player is A and Enemy is B
else if (A.categoryBitMask & playerCategory) != 0 && (B.categoryBitMask & enemyCategory) != 0 {
playerHitEnemy(playerNode: A.node as! SKSpriteNode, enemyNode: B.node as! SKSpriteNode)
}
}
Not needed, but I think this way of implementing contact detection (by switching mask) is a bit more readable, so if you want, take a peek.
One suggestion unrelated to this node removals... Don't use that much forced unwrappings! :D
swift skspritenodes coliding even thought there is no collision bit mask
you have
static let circle : UInt32 = 0x1 << 1
static let obstacle : UInt32 = 0x1 << 1
static let score : UInt32 = 0x1 << 1
you need
static let circle : UInt32 = 0x1 << 0
static let obstacle : UInt32 = 0x1 << 1
static let score : UInt32 = 0x1 << 2
Contact and category bitmask - does only one, or both, need to be set?
Let me try to explain all these bit masks with an example from an actual game code. This game has the following objects: ball, pitch, ground, bat, boundary, batting stumps, bowling stumps which will have physics interactions.
Sorry the code is in ObjC, but translation to Swift is straightforward.
Step 1: Set up the category bit masks, which specify the type of each object
typedef NS_OPTIONS(NSUInteger, CollisionCategory) {
CollisionCategoryBall = 1 << 1,
CollisionCategoryPitch = 1 << 2,
CollisionCategoryGround = 1 << 3,
CollisionCategoryBat = 1 << 4,
CollisionCategoryBoundary = 1 << 5,
CollisionCategoryBattingStumps = 1 << 6,
CollisionCategoryBowlingStumps = 1 << 7,
};
Step 2: Now we decide which objects have collisions with each other.
For example for the ball object, there are collisions with pitch, ground bat and boundary. This is setup as follows, a bitwise OR operation, assuming ballBody
is a SCNPhysicsBody
:
ballBody.collisionBitMask =
(CollisionCategoryPitch | CollisionCategoryGround | CollisionCategoryBat |
CollisionCategoryBoundary);
Step 3: (Optionally), if you want to be notified when two physics bodies are in contact with each other.
Here, you set up the contactTestBitMask. For the ball for example, I want to be notified when it is in contact with pitch, ground, bat, boundary. Again, to do that:
ballBody.contactTestBitMask =
(CollisionCategoryPitch | CollisionCategoryGround | CollisionCategoryBat |
CollisionCategoryBoundary);
When you set up the contactTestBitMask
, you will handle the contact notifications in the physics delegate, which will be something like this:
- (void)physicsWorld:(SCNPhysicsWorld*)world
didBeginContact:(SCNPhysicsContact*)contact {
CollisionCategory contactMask = contact.nodeA.physicsBody.categoryBitMask |
contact.nodeB.physicsBody.categoryBitMask;
if ((contactMask == (CollisionCategoryBall | CollisionCategoryPitch)) &&
!self.ballLandedOnPitch) {
NSLog(@" Ball hit the pitch");
}
...
}
So overall you just categorize your objects, then setup the collision mask which is just a bitwise OR operation of the category masks of the objects with which there will be collisions and optionally to handle the contacts of bodies with each other, set up the contact test bit mask.
But when it comes to contact testing, if A has its category mask set to 1 and contact bitmask set to 2,
I am not sure if you intend to explicitly set the contact bit mask to 2 above; the contact mask should be really be a bitwise OR operation of the category masks of the objects with which there will be contacts and you want to be notified of.
I think the docs are a bit confusing and hope the above explanation clears up your doubts.
Collision Bit Mask Hero passes right through EVERY thing, even the Ground
If hero has to detect collision with only the ground, then hero's collission bit mask should be
heroSprite.physicsBody?.collisionBitMask = GROUNDCATEGORY
To detect collision with ground and something else, you should use the OR |
operator to combine both categoryBitMasks
heroSprite.physicsBody?.collisionBitMask = GROUNDCATEGORY | SOMETHINGCATEGORY
The contactTestBitMask
is used to get a callback on contact between the two bodies. We get a callback when both objects share the same space. It doesn't handle collisions.
I think what you need is contact detection with fire and coins and collision detection with ground. So just set collision bit mask of heroSprite
to GROUNDCATEGORY
like in my first code snippet.
let HEROCATEGORY: UInt32 = 0x1 << 1
let GROUNDCATEGORY: UInt32 = 0x1 << 2
let FIRECATEGORY: UInt32 = 0x1 << 3
let COINCATEGORY: UInt32 = 0x1 << 4
let NUMBERCATEGORY: UInt32 = 0x1 << 5
heroSprite.physicsBody!.categoryBitMask = HEROCATEGORY
heroSprite.physicsBody!.collisionBitMask = GROUNDCATEGORY // changed
heroSprite.physicsBody!.contactTestBitMask = GROUNDCATEGORY | FIRECATEGORY | COINCATEGORY | NUMBERCATEGORY
grassGround.physicsBody!.categoryBitMask = GROUNDCATEGORY
grassGround.physicsBody!.collisionBitMask = HEROCATEGORY
coinSprite.physicsBody!.categoryBitMask = COINCATEGORY
coinSprite.physicsBody!.contactTestBitMask = HEROCATEGORY
Swift detecting collisions and setting enums
If you want your code to be notified when ship contacts (not collides) either a mete, fuel or an alien, you should have:
ship.physicsBody!.contactTestBitMask = ColliderType.object.rawValue | ColliderType.fuel.rawValue | ColliderType.alien.rawValue
i.e. the contact Test bitmask is equal to object
OR fuel
OR alien
.
Check out this simple Sprite-Kit project here on SO with contacts, collisions, touch events and a helper function (checkPhysics()
) which will analyse your physics setup - this function can be dropped into any sprite-Kit project and called to show what contacts and collisions will occur between which physics bodies.
Attack button in SpriteKit
Additional info:
It's worth remembering that unless you specify otherwise, everything collides with everything else and nothing contacts anything.
i.e. every node's collisonBitMask
is set to all 1
s and its contactTestbitMask
is set to all 0
s.
Contacts or collisions between nodeA and nodeB can be turned off using:
nodeA.physicsBody?.collisionBitMask &= ~nodeB.category
We logically AND nodeA's bitMask with the inverse (logical NOT, the ~ operator) of nodeB's category bitmask to 'turn off' that bit nodeA's bitMask.
Contacts or collisions between nodeA and nodeC can be turned ON at any point using:
nodeA.physicsBody?.contactTestBitMask |= nodeC.category
We logically AND nodeA's bitMask with nodeC's category bitmask to 'turn on' that bit in nodeA's bitMask.
You can make sure that every shape 'bounces off' a screen edge as follows:
// Make sure everything collides with the screen edge
enumerateChildNodes(withName: "//*") { node, _ in
node.physicsBody?.collisionBitMask |= self.edgeCategory //Add edgeCategory to the collision bit mask
}
Related Topics
Scenekit Scnscenerendererdelegate - Renderer Function Not Called
Swift 2.0 - 'Nil' or '0' Enum Arguments
Protocol Extending Encodable (Or Codable) Does Not Conform to It
Swift - Cast Int64 to Anyobject for Nsmutablearray
Swift Ternary Operator Compilation Error
String(Contentsofurl:) in Swift 3
Nstimer Does Not Invoke a Private Func as Selector
Retrieve String Value from Function with Closure in Swift
Swift: Optional Text in Optional Value
How Replace Position++ Code to Make It Swift 3 Compatible
Spacer Not Working with Form Inside a VStack
How to Copy a Struct and Modify One of Its Properties at the Same Time
How to Compare "Any" Value Types
Adding a Case to an Existing Enum with a Protocol
Protocol Extension on an Objc Protocol
Prevent Nsurlsession from Caching Responses
What's the Difference Between Using or Not Using the 'Where' Clause with Generics