There's something that's always puzzled me about category bit masks, and I'm reaching a point where I'm going to need a greater understanding of them. I understand how they work on a fundamental level. Say I was making a dungeon crawler basic hack and slash capabilities. I might use a collection of categories like these:
enum PhysicsCategory{
static let none: UInt32 = 0
static let playerCategory: UInt32 = 0b1
static let enemyCategory: UInt32 = 0b10
static let weaponCategory: UInt32 = 0b100
static let collectibleCategory: UInt32 = 0b1000
static let enemyProjectileCategory: UInt32 = 0b10000
}
This would probably suffice, I could test if I'm attacking the enemy, they're attacking me, etc. That said, if I wanted to make a dungeon crawler with different enemy classes, different weapon types, and different enemy weaknesses and strengths, I feel like I'd run out of categories really fast:
enum PhysicsCategory{
static let none: UInt32 = 0
static let playerCategory: UInt32 = 0b1
static let toxicWeaponCategory: UInt32 = 0b10
static let iceWeaponCategory: UInt32 = 0b100
static let explosiveWeaponCategory: UInt32 = 0b1000
static let bluntWeaponCategory: UInt32 = 0b10000
static let toxicEnemyCategory: UInt32 = 0b100000
static let iceEnemyCategory: UInt32 = 0b1000000
static let explosiveEnemyCategory: UInt32 = 0b10000000
}
I run out of options for enemies and haven't even gotten to things like collectibles, environmental objects, or bosses whose weaknesses and/or strengths make entirely new combinations. How are these things typically accounted for? What I'm trying to make demands more than what you see above and the books / guides I've read only explain this on a very basic level.
categoryBitmasks are used mostly for contact and collision detection and to establish what has made contact with what at a high level i.e. player with collectible, enemy with weapon etc. I'd be tempted to subclass these nodes (enemies, collectibles, bosses) and make the 'effect' (toxic, ice, explosive) a property within the node so that after making contact with something, you can call the appropriate routine from didBegin() based upon the enemy 'effectType'.
All contacts go through didBegin() anyway, and you're going to have to test the toxic/ice/explosive category anyway to work out how to handle the contact, so changing this logic to test a property within the node contacted won't actually add much (if any) code to your program.
Edit: You could code your didBegin() like this:
func didBegin(_ contact: SKPhysicsContact) {
print("didBeginContact entered for \(String(describing: contact.bodyA.node!.name)) and \(String(describing: contact.bodyB.node!.name))")
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case playerCategory | enemyCategory:
print("Playerand enemy have contacted.")
let playerNode = contact.bodyA.categoryBitMask == playerCategory ? contact.bodyA.node : contact.bodyB.node
let enemyNode = contact.bodyA.categoryBitMask == enemyCategory ? contact.bodyA.node : contact.bodyB.node
let enemyType = enemyNode.contactType // toxic, ice, explosive
processCollision(between: playerNode and: enemyNode ofType: enemyType)
default:
print("Some other contact occurred")
}
Although you'd be more likely to extract the enemey's type in processCollision()
If you're not familiar with Swift's argument labels for parameters, the definition of processCollision could be:
func: processCollision(between node: SKSpriteNode, and node2: SkSpriteNode ofType type: string)
which allows you to refer to node1 and node2 in the function, but the calling syntax is more readable. Also type would probably be a enum of the available enemy types.
Edit
A few links to some SK guides you may (or may not) find useful:
My step-by-step guide for collisions and contacts:
https://stackoverflow.com/a/51041474/1430420
And a guide to collision and contactTest bit masks:
https://stackoverflow.com/a/40596890/1430420
Manipulating bit masks to turn individual collision and contacts off and on.
https://stackoverflow.com/a/46495864/1430420
Small sample project with contacts, collision and touches
https://stackoverflow.com/a/43605825/1430420
Related
In my current app, I need to have multiple collisions with different nodes. I am pretty unfamiliar with the physics-world pertaining to Xcode. Within my ColliderType struct, contact with all nodes seem to work, except anything past the value type of 3. I am pretty confused with this stuff already, any help? Let me know if I should provide more code.
struct ColliderType {
static let player: UInt32 = 1
static let enemy: UInt32 = 2
static let coin: UInt32 = 3
static let arrow: UInt32 = 4
}
The problem was solved. I was missing some of the required contactTestBitMasks, and collisionBitMasks. To recap, the problem was that contact between the arrow and the enemy were not working. I thought this was something to do with my ColliderType Struct, however it was a simple error of not including the following when making my arrow:
arrow.physicsBody?.contactTestBitMask = ColliderType.enemy
arrow.physicsBody?.collisionBitMask = ColliderType.enemy
Im building some game with SpriteKit that include balls as SKShapeNode. I create a class that define the balls and their properties (including SKPhysicsBody). the balls should run on the screen, and the frame is the screen border (by using edgeLoopFrom: self.frame). I also created a path node that is located at the top of the screen. now, I want to do that if some ball reach the top border of the frame so some function will execute.
I read some about it and i'm not sure what is the right way to do so, if by using contactBitMask or if there is another and better option.
If the right way is by contactBitMask - do I have to set a struct for the balls node or can I set it inside their class?
thanks!
If I'm getting this right, when a ball hits the path node that is located at the top half of the screen you want a function to be called.
First, I'm not sure if a path node is more efficient than a sprite node, in fact I have never really used path nodes, but here is what you can do.
Spritekit
check out the link above. What you need to do is implement the SKPhysicsContactDelegate. This will allow you to access the functions didBegin() and didEnd(). These functions are called when a contact is made within the physicsworld.
class YourClass: SKScene, SKPhysicsContactDelegate {
func didBegin(_ contact: SKPhysicsContact) {
}
func didEnd(_ contact: SKPhysicsContact) {
}
}
In order for these functions to be called, you need to set the physicsworld's contactDelegate to the class that will handle the calls. This would be your scene and a good place to set this is the didMove() function.
class YourClass: SKScene, SKPhysicsContactDelegate {
func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
}
Now when a contact happens that is detectable, your didBegin() will be called, and when the contact ends the didEnd() will be called.
Now we need to give our nodes some physicsbodies and we can set the different bitmasks on them in order to detect collisions/contacts. These are the 3 bitmasks we are concerned about:
categoryTestBitMask
collisionTestBitMask
contactTestBitMask
categoryTestBitMask: You can give nodes of similar type a category, example "ball" could be the category. All your different ball objects could have this same category. I use the "noCollision" category for when I want to detect a contact, but I don't want a collision to happen. You are limited to 32 different categories though so don't go crazy with tons of different ones.
collisionTestBitMask: Give your "ball" a category that you want a collision to happen with. Ex: set the collisiontestmask for your "ball" to the category bitmask of "wall". A collision is when 2 objects will physically run into each other; so your ball will bounce off the walls.
contactTestBitMask: a Contact is when 2 nodes overlap. So instead of the ball bouncing off something, it would call the contact method for our delegate. Note that you can set both the collision and contact bit masks to the same thing.
Now how do we set these masks. I use a Struct so that I can assign names to the bitmasks and set these 3 different masks with code. Something like this:
struct Mask {
static var ball: UInt32 = 0b10 //2
static var wall: UInt32 = 0b100 //4
static var pathNode: UInt32 = 0b1000 //8
}
now within code you can set the masks:
let ball = SKSpriteNode()
ball.name = "ball"
ball.physicsBody = SKPhysicsBody()
ball.physicsBody.categoryTestBitMask = Mask.ball
ball.physicsBody.collisionTestBitMask = Mask.wall
ball.physicsBody.contactTestBitMask = Mask.pathNode | Mask.wall
let pathNode = SKSpriteNode()
pathNode.name = "pathNode"
pathNode.physicsBody = SKPhysicsBody()
pathNode.physicsBody.categoryTestBitMask = Mask.pathNode
pathNode.physicsBody.collisionTestBitMask = 0
pathNode.physicsBody.contactTestBitMask = Mask.pathNode
Lets look at what we are saying here. We create a ball object and we set its category to "ball", we say we want it to have collisions with "wall" objects and we want our contact delegate functions to trigger with "pathNode" objects OR "wall" objects. Our pathNode object will have no collisions, and will have contacts with the ball.
Basically the ball will bounce off the walls, and will pass through the pathNode. It will call the contact delegate functions didbegin() and didend() with both the pathNode and wall objects.
Not finished yet... So when the function is called, how do we handle this? When the didbegin or didend function is called, it has a parameter of "contact". this contact param has 2 bodies to work with and these are the bodies that contacted each other. There are multiple ways we can handle this, but I'll just show you a simple way for now.
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA!.node!.name == "ball" {
// bodyA is our ball
switch contact.bodyB!.node!.name {
case "pathNode":
thisIsMyBallHitPathNodeFunction()
case "wall":
thisIsMyBallHitWallFunction()
default:
break
}
}
else if contact.bodyB!.node!.name == "ball" {
// bodyB is our ball
switch contact.bodyA!.node!.name {
case "pathNode":
thisIsMyBallHitPathNodeFunction()
case "wall":
thisIsMyBallHitWallFunction()
default:
break
}
}
}
Update:
What we are doing here is figuring out the type of bodyA and bodyB. So it starts with bodyA, if bodyA is a "ball", then we know that bodyA is a "ball and bodyB is the thing the ball came in contact with. We then use a switch statement to figure out what bodyB is. Once we know what bodyB is, we call the function that we need to call for that specific contact between these 2 nodes.
Then you just put your code into those specified functions of what you want to do.
This could be a lot to take in at once, If you are new I would suggest trying this out and trying to get it to work. After, I would youTube some videos on how to do this. It is a good idea to see how different people handle the same thing and then you can decide for your self on how to do it. This might not be the most elegant way of handling the contacts, but it works well and with some practice it will become second nature, Good luck!
In the didBegin function below, one of the nodes has a category bit mask of 4294967295. However, this category is never assigned to any node.
Here are all the bit masks in use:
struct PhysicsCategory {
static let None : UInt32 = 0
static let All : UInt32 = UInt32.max
static let Player : UInt32 = 0b1 // 1
static let WorldBorder : UInt32 = 0b10 // 2
static let TopWorldBorder : UInt32 = 0b100 // 4
static let RightWorldBorder : UInt32 = 0b1000 // 8
static let Pellet : UInt32 = 0b10000
}
To repeat, the All category, which corresponds to 4294967295, is never assigned to any node. So why is there a physics body with this category bit mask? Is this category bit mask ever implicitly assigned to a physics body?
func didBegin(_ contact: SKPhysicsContact) {
print("Collision was detected: \(contact.bodyA.categoryBitMask). \(contact.bodyB.categoryBitMask).")
}
categoryBitMask is a UInt32 and its max value is 4294967295 which is also its default value (all bits set). Quote from docs:
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.
The default value is 0xFFFFFFFF (all bits set).
I am using SpriteKit in Swift and have the following situation:
I construct a struct in the superclass level that has a list of UInt32s to represent the various bitmasks that I need to establish types of physics bodies. (This is stupid because struct properties cannot be overridden in the subclass. Considering removing.)
In the subclass I create a physics body that has collision, contact, and category bitmasks of UInt32(4), referenced by a static constant from the superclass struct.
These bodies fall through the ground that is created in the superclass with bitmasks of UInt(2) from the struct property.
When I change the static constant from UInt(4) to an unsigned int 32 of any odd number between 0 - 32, the bodies do not fall through the ground. Using any even number makes the bodies fall through the ground.
Superclass.swift
struct physicsBitMasks {
static let one = UInt32(1) //for player
static let two = UInt32(2) //for ground
static let three = UInt32(3) //for you don't need to know
static let four = UInt32(4) //for enemy
...
ground.physicsBody?.categoryBitMask = physicsBitMasks.one
ground.physicsBody?.contactTestBitMask = physicsBitMasks.one
ground.physicsBody?.collisionBitMask = physicsBitMasks.two
}
Subclass.swift
Subclass: Superclass {
//create enemy
thing.physicsBody?.categoryBitMask = physicsBitMasks.four
thing.physicsBody?.contactTestBitMask = physicsBitMasks.four
thing.physicsBody?.collisionBitMask = physicsBitMasks.four
}
Enemy falls through ground
REDEFINE
Superclass.swift
struct physicsBitMasks {
static let one = UInt32(1) //for player
static let two = UInt32(2) //for ground
static let three = UInt32(3) //for you don't need to know
static let four = UInt32(5) // or 7, 9, ...31 for enemy
}
Enemy does not fall through ground.
What is going on?
Wouldn't playerMask, groundMask, mysteryMask, and enemyMask be better names than one, two, three, and four?
Also, because they are bit mask components, you need each to have a single unique bit set. But 3, for example, contains the bits of 1 and 2. Let's define them this way:
struct BodyMask {
static let Player = UInt32(1)
static let Ground = UInt32(2)
static let EmbarrassingSecret = UInt32(4)
static let Enemy = UInt32(8)
}
Now you need to set the various masks on your bodies. You should set each's body's categoryBitMask to the mask for its type:
ground.physicsBody?.categoryBitMask = BodyMask.Ground
player.physicsBody?.categoryBitMask = BodyMask.Player
enemies.forEach { $0.physicsBody?.categoryBitMask = BodyMask.Enemy }
twilightNovel.physicsBody?.categoryBitMask = BodyMask.EmbarrassingSecret
You need to set each body's collisionBitMask based on what other bodies you want it to bounce off of. That is, do you want an enemy to bounce off the ground? Then the enemy's collisionBitMask must include the BodyMask.Ground bit. Do you want the ground to bounce off of an enemy? Or bounce off of anything else? Probably not. Note that this is asymmetric! The enemy can bounce of the ground without the ground bouncing. That is, Newton's third law (for every action there is an equal and opposite reaction) doesn't have to apply.
If you want a body to bounce off of multiple other body types, you can bitwise-or the masks together. Let's assume you want enemies to bounce off of the ground and each other.
ground.physicsBody?.collisionBitMask = 0
player.physicsBody?.collisionBitMask = BodyMask.Ground
enemies.forEach { $0.physicsBody?.collisionBitMask = BodyMask.Ground | BodyMask.Enemy }
twilightNovel.physicsBody?.collisionBitMask = BodyMask.Ground
Finally, if you want to be notified when two bodies collide (regardless of whether they bounce), you can use contactTestBitMask. You probably want to know when the player touches an enemy, or when the player does something embarrassing, even though you don't necessarily want the player to bounce off of enemies or trashy novels.
ground.physicsBody?.contactTestBitMask = 0
player.physicsBody?.contactTestBitMask = BodyMask.Enemy | BodyMask.EmbarrassingSecret
enemies.forEach { $0.physicsBody?.contactTestBitMask = 0 }
twilightNovel.physicsBody?.contactTestBitMask = 0
I was wondering if anyone would be able to offer me some help with using 3 nodes, I understand if I just have 2 nodes E.g. body A and body B but I'm struggling with adding a third body, I hoped it was as simple as body C but that wasn't the case.
This is the related code below:
struct CollisionCategoryBitmask {
static let Player: UInt32 = 0x00
static let Enemy1: UInt32 = 0x01
static let Enemy2: UInt32 = 0x02
}
func didBeginContact(contact: SKPhysicsContact) {
var updateHud = false
_ = (contact.bodyA.node != player) ? contact.bodyA.node : contact.bodyB.node
updateHud = slowDown(player)
}
This function below is intended to slow the player down when contacting Enemy1
func slowDown (player: SKNode) -> Bool {
player.physicsBody?.velocity = CGVector(dx: 0, dy: -50.0)
return true
}
and this below is intended to get the game to end when contacting Enemy2
func endGame (player: SKNode) -> Bool {
endOfGame()
return true
}
At the moment both functions work, but only one at a time, so when the player contacts either Enemy 1 or 2. I can currently either get the player to slow down or get the game to end by just changing the
updateHud = slowDown(player) line. So how can I adjust the code to have the two different outcomes happen when the player touches either Enemy 1 or 2.
Thanks
You are not understanding how didBeginContact works. Basically what is happening is when any 2 nodes make a contact, the first thing that the system will do is make sure that the category and contact bit masks line up to make a valid contact. If it does, then didBeginContact gets called. now inside this function, you have the 2 arbitrary nodes that made contact, called bodyA, and bodyB. What you need to do first, is figure out what bodyA and bodyB are. So the first thing you do, is check the categoryBitMask.
if(contact.bodyA.categoryBitMask == HERO) then contact.bodyA is a hero
Now that we know what categories these nodes refer to, we can then check what the node actually is. To do this, you can either check the name of the node
if(contact.bodyA.node.name == "Hero") then bodyA is the hero
Or compare the node itself
if(contact.bodyA.node == heroNode) then bodyA is the hero
Now do the same for bodyB.
You have established at this point what your nodes are, you can proceed with the outcome results that you need based on this information.
If Hero hits both enemy nodes at the same time, then you need to handle this as well. What is going to happen is 2 calls to didBeginContact will happen, you need to find a way to remember this, and do your contact code in either the didEvaluateActions, or didSimulatePhysicsMethod. I do not remember which of these follows didBeginContact, so you will have to test it out on your own.