Understanding SpriteKit CollisionBitMask - swift

I am learning to use SpriteKit and I am following a tutorial for colllisions. I am struggling to understand the following code:
struct PhysicsCategory {
static let None : UInt32 = 0
static let All : UInt32 = UInt32.max
static let Monster : UInt32 = 0b1 // 1
static let Projectile: UInt32 = 0b10 // 2
}
Why do we assign these things called bitMaps and how do they work later on in the code below?:
func didBegin(_ contact: SKPhysicsContact) {
// 1
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
// 2
if ((firstBody.categoryBitMask & PhysicsCategory.Monster != 0) &&
(secondBody.categoryBitMask & PhysicsCategory.Projectile != 0)) {
if let monster = firstBody.node as? SKSpriteNode, let
projectile = secondBody.node as? SKSpriteNode {
projectileDidCollideWithMonster(projectile: projectile, monster: monster)
Thanks!

BitMasks are flags used to describe an item in a binary format
so imagine you have 8 ways to describe something. (In Spritekit you have 32)
We can fit these 8 things into a single byte, since 8 bits are in a byte, allowing us to save space and perform operations faster.
Here is an example of 8 descriptions
Attackable 1 << 0
Ranged 1 << 1
Undead 1 << 2
Magic 1 << 3
Regenerate 1 << 4
Burning 1 << 5
Frozen 1 << 6
Poison 1 << 7
Now I have an archer and want to classify him. I want to say he is a living friendly unit that is ranged
I would use the categoryBitmask to classify him:
archer.categoryBitmask = Ranged
This would be represented in 1 byte as
00000010
||||||||_ Attackable
|||||||_ Ranged
||||||_ Undead
|||||_ Magic
||||_ Regenerate
|||_ Burning
||_ Frozen
|_ Poison
Now let's say his arrows are fire arrows, I would classify this like:
arrow.categoryBitmask = Burning
00100000
||||||||_ Attackable
|||||||_ Ranged
||||||_ Undead
|||||_ Magic
||||_ Regenerate
|||_ Burning
||_ Frozen
|_ Poison
and finally, we have a zombie that can be hit and regenerates over time
zombie.categoryBitmask = Attackable + Undead + Regenerate
00010101
||||||||_ Attackable
|||||||_ Ranged
||||||_ Undead
|||||_ Magic
||||_ Regenerate
|||_ Burning
||_ Frozen
|_ Poison
Now I want my arrow to only hit Attackable sprites (zombie in this case)
I would set the contactTestBitmask to tell the arrow what he can hit
arrow.contactTestBitmask = Attackable 00000001
Now we need to check when an arrow hits a zombie, this is where didBeginContact comes in
What didBeginContact will do, is check the contactTestBitmask of the moving item to the categoryBitmask that it hits by using an AND operation to find a match
In our case
arrow.contactTestBitmask = 00000001
zombie.categoryMask = 00010101 AND
--------
00000001
Since our value is > 0, a contact was successful.
This means didBegins fired.
Now that we are in didBegins, we need to determine which physics body is our arrow, and which physics body is our zombie
this is where this next statement comes in
func didBegin(_ contact: SKPhysicsContact) {
// 1
var firstBody: SKPhysicsBody
var secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
Since arrow = 00100000 and zombie = 00010101, we know that zombie has a lower value than arrow, so in this case, zombie is < arrow.
We assign firstBody to zombie, and secondBody to arrow
Now we need to provide a condition.
We want to say if an undead being is hit by a burnable object, do something.
So in code this would be
if (firstBody & Undead > 0) && (secondBody & Burning > 0)
{
//burn zombie
}
But what if the arrow was a ice arrow? We do not want to go into that if statement.
Well now we can add a second condition to allow us to freeze the zombie.
if (firstBody & Undead > 0) && (secondBody & Frozen > 0)
{
//freeze zombie
}
What these ifs are doing, is making sure that the body has certain features turned on, and then perform some action in response to them.
To find out more about how bitmasks work, I would research how to do truth tables. That is essentially what this comes down to. We are just creating a few truth tables, and trying to figure out if a statement is true, and if it is true, perform an action.

Manipulating contactTest and collison bitmasks to enable/disable specific contact and collisions.
For this example, we will used 4 bodies and will show only the last 8 bits of the bit masks for simplicity. The 4 bodies are 3 SKSpriteNodes (each with a physics body) and a boundary:
let edge = frame.insetBy(dx: 0, dy: 0)
physicsBody = SKPhysicsBody(edgeLoopFrom: edge)
Note that the 'edge' physics body is the physics body of the scene, not a node.
We define 4 unique categories
let purpleSquareCategory: UInt32 = 1 << 0 // bitmask is ...00000001
let redCircleCategory: UInt32 = 1 << 1 // bitmask is ...00000010
let blueSquareCategory: UInt32 = 1 << 2 // bitmask is ...00000100
let edgeCategory: UInt32 = 1 << 31 // bitmask is 10000...00000000
Each physics body is assigned the categories that it belongs to:
//Assign our category bit masks to our physics bodies
purpleSquare.physicsBody?.categoryBitMask = purpleSquareCategory
redCircle.physicsBody?.categoryBitMask = redCircleCategory
blueSquare.physicsBody?.categoryBitMask = blueSquareCategory
physicsBody?.categoryBitMask = edgeCategory // This is the edge for the scene itself
If a bit in a body's collisionBitMask is set to 1, then it collides (bounces off) any body that has a '1' in the same position in it's categoryBitMask. Similarly for contactTestBitMask.
Unless you specify otherwise, everything collides with everything else and no contacts are generated (your code won't be notified when anything contacts anything else):
purpleSquare.physicsBody.collisonBitMask = 11111111111111111111111111111111 // 32 '1's.
Every bit in every position is '1', so when compared to any other categoryBitMask, Sprite Kit will find a '1' so a collision will occur. If you do not want this body to collide with a certain category, you will have to set the correct bit in the collisonBitMask to '0'
and its contactTestbitMask is set to all 0s:
redCircle.physicsBody.contactTestBitMask = 00000000000000000000000000000000 // 32 '0's
Same as for collisionBitMask, except reversed.
Contacts or collisions between bodies can be turned off (leaving existing contact or collision unchanged) using:
nodeA.physicsBody?.collisionBitMask &= ~nodeB.category
We logically AND nodeA's collision bit mask with the inverse (logical NOT, the ~ operator) of nodeB's category bitmask to 'turn off' that bit nodeA's bitMask. e.g to stop the red circle from colliding with the purple square:
redCircle.physicsBody?.collisionBitMask = redCircle.physicsBody?.collisionBitMask & ~purpleSquareCategory
which can be shortened to:
redCircle.physicsBody?.collisionBitMask &= ~purpleSquareCategory
Explanation:
redCircle.physicsBody.collisonBitMask = 11111111111111111111111111111111
purpleSquareCategory = 00000000000000000000000000000001
~purpleSquareCategory = 11111111111111111111111111111110
11111111111111111111111111111111 & 11111111111111111111111111111110 = 11111111111111111111111111111110
redCircle.physicsBody.collisonBitMask now equals 11111111111111111111111111111110
redCircle no longer collides with bodies with a category of ....0001 (purpleSquare)
Instead of turning off individual bits in the collsionsbitMask, you can set it directly:
blueSquare.physicsBody?.collisionBitMask = (redCircleCategory | purpleSquareCategory)
i.e. blueSquare.physicsBody?.collisionBitMask = (....00000010 OR ....00000001)
which equals blueSquare.physicsBody?.collisionBitMask = ....00000011
blueSquare will only collide with bodies with a category or ..01 or ..10
Contacts or collisions between 2 bodies can be turned ON (without affecting any existing contacts or collisions) at any point using:
redCircle.physicsBody?.contactTestBitMask |= purpleSquareCategory
We logically AND redCircle's bitMask with purpleSquare's category bitmask to 'turn on' that bit in redcircle's bitMask. This leaves any other bits in redCircel's bitMas unaffected.
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
}
Note:
Collisions can be one-sided i.e. object A can collide (bounce off) object B, whilst object B carries on as though nothing had happened. If you want 2 object to bounce off each other, they must both be told to collide with the other:
blueSquare.physicsBody?.collisionBitMask = redCircleCategory
redcircle.physicsBody?.collisionBitMask = blueSquareCategory
Contacts however are not one-sided; if you want to know when object A touched (contacted) object B, it is enough to set up contact detection on object A with regards to object B. You do not have to set up contact detection on object B for object A.
blueSquare.physicsBody?.contactTestBitMask = redCircleCategory
We don't need redcircle.physicsBody?.contactTestBitMask= blueSquareCategory

Related

Restitution only for specific objects in SpriteKit

I'm currently making a football (soccer) game using SpriteKit, but I've run into a little problem. When the characters (players) jump, I don't want them to bounce when they hit the ground after (that looks really weird and isn't very realistic).
Fine! Just set the restitution of the ground to zero!
But not so fast ... that means the ball won't bounce either.
Here are the important parts of my code:
struct cType {
static let p1:UInt32 = 0
static let ball:UInt32 = 1
static let ground:UInt32 = 2
}
Moving on, in the didMove(to view: SKView) i define some properties of my objects:
ball.physicsBody?.categoryBitMask = cType.ball
ball.physicsBody?.collisionBitMask = cType.p1 | cType.p2 | cType.ground
ball.physicsBody?.contactTestBitMask = cType.p1 | cType.p2 | cType.ground
p1.physicsBody?.categoryBitMask = cType.p1
p1.physicsBody?.collisionBitMask = cType.p2 | cType.ball | cType.ground
p1.physicsBody?.contactTestBitMask = cType.p2 | cType.ball | cType.ground
As for the collision detection itself, I just use the didMove(_ contact: SKPhysicsContact) and it works perfectly.
Now, of course I have a player 2 (p2), but that sprite has the exact same code as player 1 (p1) except cType.p2 is changed to cType.p1 and vice versa.
I also have some code ground.physicsBody?.restitution = 0.5 but this of course sets the restitution to 0.5 for any sprite who collides with the ground. This is where I'm really unsure. So my question is: How can I make sure the ball bounces when it hits the ground (with restitution 0.5), but my players (p1, p2) don't?

SKNodes follow in a consistent speed and stop them from spazzing?

Currently doing a SoloProject for class and decided to study SpriteKit on my own. I decided to make a top-down zombie shooter and I have a lot of questions but so far these are the two main ones I can't seem to fix or find solution for.
Problem 1
Zombies slow down the closer they get to the target, If I increase the speed they just speed in from off the screen and still slowdown as they get closer (I've read somewhere putting that function in the update is bad but I still did it...)
I want to make it where they spawn with the speed of 3 and when the player moves closer or further away it stays at 3. (Currently using an analog stick code I found that was on Youtube to move my character around)
func zombieAttack() {
let location = player.position
for node in enemies {
let followPlayer = SKAction.move(to: player.position, duration: 3)
node.run(followPlayer)
//Aim
let dx = (location.x) - node.position.x
let dy = (location.y) - node.position.y
let angle = atan2(dy, dx)
node.zRotation = angle
//Seek
let velocityX = cos(angle) * 1
let velocityY = sin(angle) * 1
node.position.x += velocityX
node.position.y += velocityY
}
}
override func update(_ currentTime: TimeInterval) {
zombieAttack()
}
Problem 2
Also when multiple zombies get close to the players (function above) they start to spazz so I allowed them to overlap on top of each other to stop the spazzing.
I want to make it where they are more solid? if that is the right way to describe it. Basically I want them to huddle up around the player**.
If I add enemy to the collision it will spazz trying to get into the same position.
private func spawnZombie() {
let xPos = randomPosition(spriteSize: gameSpace.size)
let zombie = SKSpriteNode(imageNamed: "skeleton-idle_0")
zombie.position = CGPoint(x: -1 * xPos.x, y: -1 * xPos.y)
zombie.name = "Zombie\(zombieCounter)"
zombie.zPosition = NodesZPosition.enemy.rawValue
let presetTexture = SKTexture(imageNamed: "skeleton-idle_0.png")
zombie.physicsBody = SKPhysicsBody(texture: presetTexture, size: presetTexture.size())
zombie.physicsBody?.isDynamic = true
zombie.physicsBody?.affectedByGravity = false
zombie.physicsBody?.categoryBitMask = BodyType.enemy.rawValue
zombie.physicsBody?.contactTestBitMask = BodyType.bullet.rawValue
zombie.physicsBody?.collisionBitMask = BodyType.player.rawValue
zombie.zRotation = 1.5
zombie.setScale(0.2)
enemies.append(zombie)
zombieCounter += 1
run(SKAction.playSoundFileNamed("ZombieSpawn", waitForCompletion: false))
keepEnemiesSeperated() // ADDED FROM UPDATED EDIT*
addChild(zombie)
}
Let me know if I need to post more code or explain it better, I'm a five months in on learning Swift and have only a week and a half of SpriteKit experience and first time posting on StackOverFlow. Thanks all in advance!
EDIT: I am using a code I found from having a node follow at a constant speed but I don't think I'm doing it right since it is not working. I added the following code:
private func keepEnemiesSeparated() {
// Assign constrain
for enemy in enemies {
enemy.constraints = []
let distanceBetween = CGFloat(60)
let constraint = SKConstraint.distance(SKRange(lowerLimit: distanceBetween), to: enemy)
enemy.constraints!.append(constraint)
}
}
Problem 1, your zombie is moving based on time, not at a set speed. According to your code, he will always reach the player in 3 seconds. This means if he is 1 foot away, he takes 3 seconds. If he is 100 miles away, he takes 3 seconds. You need to use a dynamic duration if you are planning to use the moveTo SKAction based on the speed of the zombie. So if your zombie moves 10 points per second, you want to calculate the distance from zombie to player, and then divide by 10. Basically, your duration formula should be distance / speed
Problem 2, if you want the zombies to form a line, you are going to have to determine who the leading zombie is, and have each zombie follow the next leading zombie as opposed to all zombies following the player. Otherwise your other option is to not allow zombies to overlap, but again, you will still end up with more of a mosh pit then a line.

prevent Spritekit nodes with the same Catagorymask from colliding

I have this Spritekit project that I am working on where there is a player node that you move around the screen and then there are bullet nodes that shoot at you. My problem is that not only do the bullets hit the player, but they also hit each other. I have given the player and the bullets their own category/collision/contact masks.
I specify all the different category masks here(some of them can be ignored):
enum CategoryMask : UInt32 {
case playeragain = 11
case player = 1
case GBullet = 2
case BBullet = 3
case enemyships = 4
case coin = 5
case boss = 6
}
BBullet is the bullets that are shot at you and player is the node that the user controls.
I then assign these masks to each node:
player.physicsBody?.categoryBitMask = CategoryMask.player.rawValue
player.physicsBody?.collisionBitMask = CategoryMask.BBullet.rawValue | CategoryMask.coin.rawValue
player.physicsBody?.contactTestBitMask = CategoryMask.BBullet.rawValue | CategoryMask.coin.rawValue
bullet.physicsBody?.categoryBitMask = CategoryMask.BBullet.rawValue
bullet.physicsBody?.collisionBitMask = CategoryMask.player.rawValue
bullet.physicsBody?.contactTestBitMask = CategoryMask.player.rawValue
The collision between the bullets and the player works fine and I have set up how to handle each collision in the "Did begin contact" function. I would like to specify that I don't want the bullets to collide with each other.
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
Just to add to the answer above, with your definition of the category bits masks as:
case playeragain = 11 (binary 1011)
case player = 1 (Binary 0001)
case GBullet = 2. (Binary 0010)
case BBullet = 3. (Binary 0011)
case enemyships = 4. (Binary. 0100)
case coin = 5. (Binary 0101)
case boss = 6. (Binary 0110)
The only unique values you have (I.e. with a single bit set) are player, GBullet and enemyships. Anything defined as BBullet will match as a player or a GBullet; coin will identify as an enemyships or a player and boss as enemyships or GBullet and playeragain as something undefined (1000), GBullet or Player.
here’s a guide to collision and contactTest bit masks:
https://stackoverflow.com/a/40596890/1430420

Swift detecting collisions and setting enums

I'm trying to get my head around how detecting collision works when setting enums. I have several different nodes which I can detect collision on and have set up working however I have just winged it and don't understand how to correctly set it up.
enum ColliderType: UInt32{
case ship = 1
case object = 2
case fuel = 4
case alien = 8
case torp = 16
}
Then I have each node set up as such:
mete.physicsBody!.contactTestBitMask = ColliderType.object.rawValue
mete.physicsBody!.categoryBitMask = ColliderType.object.rawValue
mete.physicsBody!.collisionBitMask = ColliderType.object.rawValue
alien.physicsBody!.contactTestBitMask = ColliderType.torp.rawValue
alien.physicsBody!.categoryBitMask = ColliderType.alien.rawValue
alien.physicsBody!.collisionBitMask = ColliderType.alien.rawValue
fuel.physicsBody!.contactTestBitMask = ColliderType.ship.rawValue
fuel.physicsBody!.categoryBitMask = ColliderType.fuel.rawValue
fuel.physicsBody!.collisionBitMask = ColliderType.fuel.rawValue
ship.physicsBody!.contactTestBitMask = ColliderType.object.rawValue
ship.physicsBody!.categoryBitMask = ColliderType.ship.rawValue
ship.physicsBody!.collisionBitMask = ColliderType.ship.rawValue
torpedoNode.physicsBody!.contactTestBitMask = ColliderType.alien.rawValue
torpedoNode.physicsBody!.categoryBitMask = ColliderType.torp.rawValue
torpedoNode.physicsBody!.collisionBitMask = ColliderType.alien.rawValue
Then to detect a collision between the ship and a mete the following code is what I'm using:
if contact.bodyA.categoryBitMask == ColliderType.object.rawValue || contact.bodyB.categoryBitMask == ColliderType.object.rawValue
A collision between the ship and fuel I use:
if contact.bodyA.categoryBitMask == ColliderType.fuel.rawValue || contact.bodyB.categoryBitMask == ColliderType.fuel.rawValue {
Then finally a collision between a torpedo and alien I use this code:
if contact.bodyA.categoryBitMask == ColliderType.alien.rawValue || contact.bodyB.categoryBitMask == ColliderType.torp.rawValue {
Everything works perfectly however I'm now trying to detect collision between the ship and alien and I can't work out how to go about doing this. I need to try understand the logic and clean this code up.
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 1s and its contactTestbitMask is set to all 0s.
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
}

Why does SKLabelNode increment too much on Contact? (Swift)

I am trying to have my Label to increment +1 every time a sprite makes contact with a Contact node, but it increments by large numbers like +23. I think what is happening is that its taking into account every millisecond that the sprite is touching the node, but i don't know how to fix it.
Here is my Label node code within DidMoveToView:
score = 0
scoreLabelNode = SKLabelNode(fontNamed:"[z] Arista Light")
scoreLabelNode.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/4)
scoreLabelNode.zPosition = 100
scoreLabelNode.fontSize = 500
scoreLabelNode.alpha = 0.03
scoreLabelNode.text = String(score)
self.addChild(scoreLabelNode)
here is my contact node:
var contactNode = SKNode()
contactNode.position = CGPoint(x: self.frame.size.width + asteroidTexture.size().height / 15 + rocket.size.width, y: 0.0)
contactNode.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake( asteroid.size.width/16, self.frame.size.height*2 ))
contactNode.physicsBody?.dynamic = false
contactNode.physicsBody?.categoryBitMask = scoreCategory
contactNode.physicsBody?.contactTestBitMask = rocketCategory
contactNode.runAction(asteroidsMoveAndRemove)
moving.addChild(contactNode)
and here is my code where when my rocket sprite makes contact with the contactNode it increments:
func didBeginContact(contact: SKPhysicsContact) {
if moving.speed > 0 {
if ( contact.bodyA.categoryBitMask & scoreCategory ) == scoreCategory || ( contact.bodyB.categoryBitMask & scoreCategory ) == scoreCategory {
// Rocket has contact with score entity
score++
scoreLabelNode.text = String(score)
println("HIT")
}
else{
gameOver()
}
}
'moving' is when my asteroid sprites are moving, the contact nodes move with it
It's probably not every millisecond, but instead every frame, and that's why you likely end up with numbers like +23 (The collision is taking place for a 1/3 of a second and if it's running at 60 FPS that is about 20 or so frames).
One thing you could do is subclass SKNode into an actual ContactNode class that has a property hasBeenStruck that is initially set to false or something to that extent.
Then when you check contact, you first check to see if hasBeenStruck is false. If it is, register the hit and update the score and then set that ContactNode's hasBeenStruck property to true. This way, on the next 20 or so checks to see if contact has happened, it won't keep updating the label because the if condition has failed.
However, if your game allows a user to hit the same ContactNode twice and get score both times, this isn't a complete solution. If that is the case, you could use a timer or "invincibility" period for the given ContactNode.