Category bitmask for more than one - sprite-kit

What if I have 50 SpriteKit nodes that each of them has a different categorybitmask and I have a ball if the ball collided with one of those 50 SpriteKit nodes it for example change the color of the the collided SpriteKit to for example green
To achieve this I know I have to write the category bitmask of the SpriteKit node in contact bitmask of the ball
The question is what if if I have 50 SpriteKit nodes how can I or do I have to write all of the 50 SpriteKit nodes in the contact bitmask of the ball
I tried using a loop but I didn’t know how to implement it right
And thanks for your help

You can place many nodes under the same categoryBitmask and assign different names to them.
Example:
enum Collision: UInt32{
case ball = 1
case circle = 2
case box = 4
}
Node Examples:
let node1 = SKSpriteNode(imageNamed: "SoccerBall")
node1.name = "SoccerBall"
node1.physicsBody = SKPhysicsBody(circleOfRadius: node1.size.width)
node1.physicsBody?.categoryBitMask = Collision.ball.rawValue
node1.physicsBody?.contactTestBitMask = Collision.circle.rawValue | Collision.box.rawValue
addChild(node1)
let node2 = SKSpriteNode(imageNamed: "TennisBall")
node2.name = "TennisBall"
node2.physicsBody = SKPhysicsBody(circleOfRadius:node2.size.width)
node2.physicsBody?.categoryBitMask = Collision.ball.rawValue
node2.physicsBody?.contactTestBitMask = Collision.circle.rawValue | Collision.box.rawValue
addChild(node2)
let node3 = SKSpriteNode(imageNamed: "CartonBox")
node3.name = "CartonBox"
node3.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: node.frame.width, height: node.frame.height)
node3.physicsBody?.categoryBitMask = Collision.box.rawValue
node3.physicsBody?.contactTestBitMask = Collision.ball.rawValue
addChild(node3)
let node4 = SKSpriteNode(imageNamed: "TreasureChest")
node4.name = "TreasureChest"
node4.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: node.frame.width, height: node.frame.height)
node4.physicsBody?.categoryBitMask = Collision.box.rawValue
node4.physicsBody?.contactTestBitMask = Collision.ball.rawValue
addChild(node4)
In your contact function:
func didBegin(_ contact: SKPhysicsContact){
guard let nodeA = contact.bodyA.node else {return}
guard let nodeB = contact.bodyB.node else {return}
if nodeA.name == "SoccerBall" && nodeB.name == "CartonBox"{
//Do Something
}
if nodeA.name == "TennisBall" && nodeB.name == "CartonBox"{
//Do Something
}
if nodeA.name == "SoccerBall" && nodeB.name == "TreasureChest"{
//Do Something
}
}
And so on...
Just remember to add the SKPhysicsContactDelegate to you class Gamescene.

Related

getting the sprite attached to a SKPhysicsBody

I'm animating a sprite, and when it touches another one, a physics contact function is called. Than, in this function, I'm trying to get the sprite with touched the other. Its body is bodyA but it's a SKPhysicsBody, and it can not be converted as a SKSpriteNode. Do you have any ideas?
The function is correctly called when contact, I just try to get the sprite which body makes the contact. The final idea is getting the actions attached to the sprite with I'm looking for but I suppose when you have the sprite it's easy.
let shootCategory: UInt32 = 0x1 << 0
let enemyCategory: UInt32 = 0x1 << 1
// Declaration of the SKPhysicsBody of the sprite wich will touch the other one
sprite.physicsBody = SKPhysicsBody(circleOfRadius: (20))
sprite.physicsBody?.usesPreciseCollisionDetection = true
sprite.physicsBody?.categoryBitMask = shootCategory
sprite.physicsBody?.collisionBitMask = shootCategory | enemyCategory
sprite.physicsBody?.contactTestBitMask = shootCategory | enemyCategory
sprite.physicsBody?.affectedByGravity = false
// Declaration of the SKPhysicsBody of the sprite wich will be touched
sprite.run(SKAction.group([moveAction, fadeInAction]))
sprite.physicsBody = SKPhysicsBody(circleOfRadius: (20))
sprite.physicsBody?.usesPreciseCollisionDetection = true
sprite.physicsBody?.categoryBitMask = enemyCategory
sprite.physicsBody?.collisionBitMask = shootCategory | enemyCategory
sprite.physicsBody?.contactTestBitMask = shootCategory | enemyCategory
sprite.physicsBody?.affectedByGravity = false
func didBegin(_ contact: SKPhysicsContact) {
if (contact.bodyA.categoryBitMask == enemyCategory) && (contact.bodyB.categoryBitMask == shootCategory){
// I tried this to get the sprite wich the bodyB is attached to but it doesn't even build
let sprite: SKSpriteNode = sprite.contact.bodyB
contact.bodyA.removeAction(forKey: "moveToAction")
}
}
I finally found the solution with is really simple:
let Node = contact.bodyB.node as! SKSpriteNode

How to change physics body and sprite image of existing sprite after contact

I have created two sprites with physics bodies and handled their contact. How can I change the physics body and sprite image for one of sprites when the contact occurs? Please see relevant code below:
// circle Sprite
circleSprite.name = circleSpriteCategoryName
circleSprite.position = CGPointMake(2*self.frame.size.width/3, 2*self.frame.size.height/3)
circleSprite.zPosition = 10
self.addChild(circleSprite)
circleSprite.physicsBody = SKPhysicsBody(circleOfRadius: circleSprite.frame.size.width/2)
circleSprite.physicsBody?.friction = 0
circleSprite.physicsBody?.restitution = 1
circleSprite.physicsBody?.linearDamping = 0
circleSprite.physicsBody?.angularDamping = 0
circleSprite.physicsBody?.allowsRotation = false
circleSprite.physicsBody?.applyImpulse(CGVectorMake(4, -4))
// square sprite
let squareSprite = SKSpriteNode(imageNamed: "square.png")
squareSprite.position = CGPointMake(CGRectGetWidth(frame)*0.4, CGRectGetHeight(frame)*0.8)
squareSprite.physicsBody = SKPhysicsBody(rectangleOfSize: squareSprite.frame.size)
squareSprite.physicsBody?.friction = 0.0
squareSprite.name = squareSpriteCategoryName
squareSprite.physicsBody?.categoryBitMask = squareSpriteCategory
squareSprite.zPosition = 10
addChild(squareSprite)
// contact in didBeginContact
if firstBody.categoryBitMask == circleSpriteCategory && secondBody.categoryBitMask == squareSpriteCategory {
println("contact works")
}
EDIT 1
I would like to change update the physics body and image of circleSprite to have the following properties:
circleSprite = SKSpriteNode(imageNamed: "recto.png")
circleSprite.physicsBody = SKPhysicsBody(rectangleOfSize: circleSprite.frame.size)
circleSprite.physicsBody?.friction = 0.0
circleSprite.name = newSpriteCategoryName
circleSprite.physicsBody?.categoryBitMask = newSpriteCategory
EDIT 2
I managed to get change the sprite image thanks to help from Max_Power89. But the image has been reduced to the dimensions of the physics body. Please see the relevant code below:
if firstBody.categoryBitMask == circleSpriteCategory && secondBody.categoryBitMask == squareSpriteCategory {
if let newSprite = firstBody.node {
let newImage = SKTexture(imageNamed: "newSprite.png")
let action = SKAction.setTexture(newImage)
newSprite.runAction(action)
}
}
You can implement SKPhysicsContactDelegate
then in your didBeginContact delegate you can do something like this
func didBeginContact(contact: SKPhysicsContact) {
// Return whatever it's the other body
let other = contact.bodyA.categoryBitMask == PhisicsCategory.Player ? contact.bodyB : contact.bodyA
if other.categoryBitMask == PhisicsCategory.Playe1 {
//Change sprite
let deadTexture = SKTexture(imageNamed: "Your image name")
let action = SKAction.setTexture(deadTexture)
player1.runAction(action)
}
}

Static bodies still moving

this may be a duplicate of
Static Physics body still moving with contact
but I have not the reputation to add a comment.
My problem is with collisions and static bodies. I'm working on a little game and I have a player, so configured (from sks file):
player = worldNode.childNodeWithName("player") as! SKSpriteNode
let playerBodyTexture = SKTexture(imageNamed: "player_physicBody_mask")
player.physicsBody = SKPhysicsBody(texture: playerBodyTexture, size: CGSizeMake(player.size.width, player.size.height))
player.physicsBody!.usesPreciseCollisionDetection = true
player.physicsBody!.categoryBitMask = PhysicsCategory.player
player.physicsBody!.collisionBitMask = PhysicsCategory.ground | PhysicsCategory.enemies
player.physicsBody!.contactTestBitMask = PhysicsCategory.enemies
player.physicsBody!.restitution = 0
player.physicsBody!.friction = 0
player.physicsBody!.allowsRotation = false}
an enemy:
worldNode.enumerateChildNodesWithName("enemy") { node, _ in
let enemy = node as! SKSpriteNode
enemy.name = "enemy"
enemy.physicsBody = SKPhysicsBody(circleOfRadius: 17)
enemy.physicsBody!.dynamic = true
enemy.physicsBody!.mass = 0.4
enemy.physicsBody!.restitution = 0
enemy.physicsBody!.categoryBitMask = PhysicsCategory.enemies
enemy.physicsBody!.collisionBitMask = PhysicsCategory.ground | PhysicsCategory.player
enemy.physicsBody!.contactTestBitMask = PhysicsCategory.player}
and I have the platfrom brick, so configured:
worldNode.enumerateChildNodesWithName("fixedBlock") { node, _ in
let fixedBlock = node as! SKSpriteNode
fixedBlock.name = "fixedBlock"
let fixedBlockBodyTexture = SKTexture(imageNamed: "tripleBlocks_physicBody_mask")
fixedBlock.physicsBody = SKPhysicsBody(texture: fixedBlockBodyTexture, size: CGSizeMake(fixedBlock.size.width, fixedBlock.size.height))
fixedBlock.physicsBody!.categoryBitMask = PhysicsCategory.ground
fixedBlock.physicsBody!.collisionBitMask = PhysicsCategory.player | PhysicsCategory.enemies
fixedBlock.physicsBody!.contactTestBitMask = 0
fixedBlock.physicsBody!.dynamic = false
fixedBlock.physicsBody!.affectedByGravity = false
fixedBlock.physicsBody!.allowsRotation = false
fixedBlock.physicsBody!.pinned = false
fixedBlock.physicsBody!.friction = 0.3
fixedBlock.physicsBody!.restitution = 0}
When the player jumps on the enemies, he has an impulse (like Super Mario :-)), so I have this:
func didBeginContact(contact: SKPhysicsContact) {
let collision: UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == PhysicsCategory.player | PhysicsCategory.enemies {
self.player.physicsBody!.applyImpulse(CGVectorMake(0, 500))}}
Ok... sometimes, often, when the player jumps on enemy, the impulse appens correctly, but the ground tile moves down, also if its dynamic propriety is set on false. I tried to apply a JointFixed on the ground tile but when it is pushed down, its physic body "glitch"; I tried to print the physicsBody velocity propierty of the ground tile and when the problem occours, the velocity's dx vector is >100, but should static bodies not be affected by forces?
Any idea?
Many thanks

How to create explosion effect using image frames sprite kit swift

So I created this game where you have to shoot at objects. Now, I have an imageset that replicates an object exploding. I would like to call those images to appear in a sequence so it looks like an explosion after the projectile hits the object. Obviously the images have to be called at the exact location of where the projectile hits the object. Does anyone have any idea on how to make this happen? Here is some code.
func projectileDidCollideWithMonster(projectile:SKSpriteNode, monster:SKSpriteNode) {
projectile.removeFromParent()
monster.removeFromParent()
playerScore = playerScore + 1
playerScoreUpdate()
if (playerScore > 100) {
let reveal = SKTransition.crossFadeWithDuration(0.3)
let gameOverScene = GameOverScene(size: self.size, won: true)
self.view?.presentScene(gameOverScene, transition: reveal)
}
}
func didBeginContact(contact: SKPhysicsContact) {
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
}
if (firstBody.categoryBitMask & UInt32(laserCategory)) != 0 && (secondBody.categoryBitMask & UInt32(monsterCategory)) != 0 {
projectileDidCollideWithMonster(firstBody.node as SKSpriteNode, monster: secondBody.node as SKSpriteNode)
}
if playerScore > highScore() {
saveHighScore(playerScore)
println("New Highscore = " + highScore().description)
highScoreLabel.text = "Best score: \(highScore().description)"
} else {
println("HighScore = " + highScore().description ) // "HighScore = 100"
}
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let node = self.nodeAtPoint(location)
if node.name == "muteSound" {
if musicIsPlaying == true {
backgroundMusicPlayer.stop()
musicIsPlaying = false
} else if musicIsPlaying == false {
backgroundMusicPlayer.play()
musicIsPlaying = true
}
} else {
let touch = touches.anyObject() as UITouch
let touchLocation = touch.locationInNode(self)
let projectile = SKSpriteNode(imageNamed: "boom")
projectile.setScale(0.6)
projectile.position = player.position
projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.dynamic = true
projectile.physicsBody?.categoryBitMask = UInt32(laserCategory)
projectile.physicsBody?.contactTestBitMask = UInt32(monsterCategory)
projectile.physicsBody?.collisionBitMask = 0
projectile.physicsBody?.usesPreciseCollisionDetection = true
// 3 - Determine offset of location to projectile
let offset = touchLocation - projectile.position
// 4 - Bail out if you are shooting down or backwards
if (offset.y < 0) { return }
// 5 - OK to add now - you've double checked position
addChild(projectile)
// 6 - Get the direction of where to shoot
let direction = offset.normalized()
// 7 - Make it shoot far enough to be guaranteed off screen
let shootAmount = direction * 1000
// 8 - Add the shoot amount to the current position
let realDest = shootAmount + projectile.position
// 9 - Create the actions
let actionMove = SKAction.moveTo(realDest, duration: 2.0)
let actionMoveDone = SKAction.removeFromParent()
if !isStarted {
start()
}else{
projectile.runAction(SKAction.sequence([actionMove, actionMoveDone]))
}
}
}
}
func addMonster() {
let monster = SKSpriteNode(imageNamed: "box")
monster.setScale(0.6)
monster.physicsBody = SKPhysicsBody(rectangleOfSize: monster.size)
monster.physicsBody?.dynamic = true
monster.physicsBody?.categoryBitMask = UInt32(monsterCategory)
monster.physicsBody?.contactTestBitMask = UInt32(laserCategory)
monster.physicsBody?.collisionBitMask = 0
monster.name = "box"
var random : CGFloat = CGFloat(arc4random_uniform(320))
monster.position = CGPointMake(random, self.frame.size.height + 10)
self.addChild(monster)
}
For you explosion you could create an SKSpriteNode that play the frames you mentioned:
1. You're going to need the images as an array of SKTextures. You said you've got you images in an image set so the easiest thing to do may be to create an array using a for loop, for example:
// I don't know how many images you've got, so I'll use 10.
var textures: [SKTexture] = []
for i in 0..<10 {
let imageName = "explosion\(i)"
textures.append(SKTexture(imageNamed: imageName))
}
Alternatively, which is what I would recommend, is to create a Texture Atlas of your images. (For more information on texture atlases see here) To create an atlas, make a folder with the extension .atlas and adding all your explosion images to it. (Then add this to your project). Here's an extension I wrote to get your sprites out of a texture atlas, ready for animation:
extension SKTextureAtlas {
func textureArray() -> [SKTexture] {
var textureNames = self.textureNames as! [String]
// They need to be sorted because there's not guarantee the
// textures will be in the correct order.
textureNames.sort { $0 < $1 }
return textureNames.map { SKTexture(imageNamed: $0) }
}
}
And here's how to use it:
let atlas = SKTextureAtlas(named: "MyAtlas")
let textures = atlas.textureArray()
2. Now you've got your textures you need to create an SKSpriteNode and animate it:
let explosion = SKSpriteNode(texture: textures[0])
let timePerFrame = // this is specific to your animation.
let animationAction = SKAction.animateWithTextures(textures, timePerFrame: timePerFrame)
explosion.runAction(animationAction)
3. Add the sprite to your scene and position it correctly. To add it in the correct place you could use the contactPoint variable on SKPhysicsContact, after checking it was the projectile hitting an object.
func didBeginContact(contact: SKPhysicsContact) {
// Other stuff...
explosion.position = contact.contactPoint
self.addChild(explosion)
}
Hope that helps!

iOS Swift didBeginContact not being called

I have been struggling for the past two days to get two SKSpriteNodes to register a collision and evoke didBegin#contact.
I've set their bit masks 'categoryBitMask', 'contactTestBitMask' and 'collisionTestBitMask' for both objects.
I've also set the 'dynamic' property for both to 'true'
initPhysics() seems to set up the physicsWorld okay.
All I'm expecting is that didBegin#Contact is called, but it is not
//Set up Physicsbody bit masks
let playerCarBitMask: UInt32 = 0x1 << 1
let slowCarBitMask: UInt32 = 0x1 << 2
//initPhysics
func initPhysics() {
println("(((((((((((((( Initiating Physicsbody ))))))))))))))")
self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = CGVector.zeroVector
println("self.physicsWorld.contactDelegate = \(self.physicsWorld.contactDelegate)")
}
//setupPlayer
func setupPlayer() {
car = SKSpriteNode(imageNamed: "redCarUp")
car.setScale(2.0)
car.position = CGPoint(x: 800, y: 400)
car.zPosition = 100
car.name = "car"
gameNode.addChild(car)
let carBody = SKPhysicsBody(
rectangleOfSize: car.frame.size, center: car.position)
carBody.dynamic = true
carBody.categoryBitMask = playerCarBitMask
carBody.contactTestBitMask = slowCarBitMask
carBody.mass = 5
carBody.collisionBitMask = slowCarBitMask
car.physicsBody = carBody
println("carBody = \(carBody)")
println("carBody.dynamic = \(carBody.dynamic)")
println("carBody.mass = \(carBody.mass)")
println("carBody.categoryBitMask = \(carBody.categoryBitMask)")
println("carBody.contactTestBitMask = \(carBody.contactTestBitMask)")
println("carBody.collisionBitMask = \(carBody.contactTestBitMask)")
slowCar = SKSpriteNode(imageNamed: "blueCarUp")
slowCar.setScale(2.0)
let slowCarScenePos = CGPoint(
x: 680,
y: 2048)
slowCar.position = gameNode.convertPoint(slowCarScenePos, fromNode: self)
println("slowCar.position = \(slowCar.position) ****")
slowCar.zPosition = 80
slowCar.name = "slowCar"
let slowCarBody = SKPhysicsBody(
rectangleOfSize: slowCar.frame.size, center: slowCar.position)
println("slowCar = \(slowCar) ****")
slowCarBody.dynamic = true
slowCarBody.categoryBitMask = slowCarBitMask
slowCarBody.contactTestBitMask = playerCarBitMask
slowCarBody.mass = 5
slowCarBody.collisionBitMask = playerCarBitMask
slowCar.physicsBody = slowCarBody
gameNode.addChild(slowCar)
}
func didBeginContact(contact: SKPhysicsContact!) {
println("*******************PhysicsContact********************")
}
'didBeginContact' has been changed to 'didBegin' in swift 3
func didBegin(_ contact: SKPhysicsContact) {
//stuff
}
I had a code from swift 2 and 'didBeginContact' was sitting there but wasn't being called. After quite a white I figured out that the function was changed. So, I thought my answer could help someone.
If you want make a contact between car and slowCar you have to init the categoryBitMask of both physicsBodies (I think you did). See the code below to get contact between two physicsBodies. When there is a contact it returns your display function :
//init your categoryBitMask :
let carCategory:UInt32 = 0x1 << 0
let SlowCarCategory:UInt32 = 0x1 << 1
//init car
car.physicsBody?.categoryBitMask = carCategory
car.physicsBody?.contactTestBitMask = slowCarCategory
//init slowCar
slowCar.physicsBody?.categoryBitMask = slowCarCategory
slowCar.physicsBody?.contactTestBitMask = CarCategory
// set your contact function
func didBeginContact(contact: SKPhysicsContact!)
{
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
}
if ((firstBody.categoryBitMask & carCategory) != 0 && (secondBody.categoryBitMask & slowCarCategory) != 0)
{
displayfunction(firstBody.node as SKSpriteNode, car: secondBody.node as SKSpriteNode)
}
}
func displayFunction (slowCar : SKSpriteNode, car : SKSpriteNode)
It turned out to be a simple problem. In my original code I was setting parameters for the SKPhysicsBody detection frame like so:
let carBody = SKPhysicsBody(
rectangleOfSize: car.frame.size, center: car.position)
Similarly I was doing the same for the second node that I was testing physics collisions for.
Simply removing the 'centre:' parameters like so:
let carBody = SKPhysicsBody(rectangleOfSize: car.frame.size)
for the two sprite nodes solved the problem and the nodes now crash into each other and push themselves aside as expected.
Please Note that contact will not be detected between two static bodies
(node.physicsBody?.isDynamic = false)