I have two types of collision. First one is the spaceShip and asteroid (this is already handled) but I can't handle second type of collisions, where my spaceShipLazer hits enemySpaceShips. In second case I have drugging/multiple collisions or sometimes perfect collision.
My enemySpaceShip is an EnemySpaceShip class which is a subclass of SKSpriteNode.
So the question is about second collision. Is has multiple collisions that is not expected. How I can fix it?
func didBeginContact(contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == CollisionCategories.PlayerSpaceShip && contact.bodyB.categoryBitMask == CollisionCategories.Asteroid || contact.bodyB.categoryBitMask == CollisionCategories.PlayerSpaceShip && contact.bodyA.categoryBitMask == CollisionCategories.Asteroid {
if !gameOver && !playerWasHit {
playerWasHit = true
self.pauseTheGame()
//spaceShip vs asteroid animation
let fadeOutAction = SKAction.fadeOutWithDuration(0.1)
fadeOutAction.timingMode = SKActionTimingMode.EaseOut
let fadeInAction = SKAction.fadeInWithDuration(0.1)
fadeInAction.timingMode = SKActionTimingMode.EaseOut
let blinkAction = SKAction.sequence([fadeOutAction, fadeInAction])
let blinkRepeatAction = SKAction.repeatAction(blinkAction, count: 3)
let delayAction = SKAction.waitForDuration(0.2)
let gameOverAction = SKAction.runBlock({ () -> Void in
self.gameSettings.lives--
self.gameDelegate?.gameDelegateDidUpdateLives()
if self.gameSettings.lives > 0 {
self.respawn()
} else {
self.gameSettings.recordScores(self.gameSettings.currentScore)
self.gameDelegate?.gameDelegateGameOver(self.gameSettings.currentScore)
self.gameOver = true
self.pauseTheGame()
}
})
let gameOverSequence = SKAction.sequence([blinkRepeatAction, delayAction, gameOverAction])
spaceShipLayer.runAction(gameOverSequence)
}
if soundOn {
let hitSoundAction = SKAction.playSoundFileNamed("hitSound.wav", waitForCompletion: true)
runAction(hitSoundAction)
}
//second case playerLazer vs enemySpaceShip
} else if contact.bodyA.categoryBitMask == CollisionCategories.PlayerLaser && contact.bodyB.categoryBitMask == CollisionCategories.EnemySpaceShip || contact.bodyB.categoryBitMask == CollisionCategories.PlayerLaser && contact.bodyA.categoryBitMask == CollisionCategories.EnemySpaceShip {
contact.bodyA.node?.removeFromParent()
contact.bodyB.node?.removeFromParent()
}
}
I found decision that makes everything perfect:
just second part should be like this:
if
contact.bodyA.categoryBitMask == CollisionCategories.PlayerLaser &&
contact.bodyB.categoryBitMask == CollisionCategories.EnemySpaceShip ||
contact.bodyA.categoryBitMask == CollisionCategories.EnemySpaceShip &&
contact.bodyB.categoryBitMask == CollisionCategories.PlayerLaser
{
contact.bodyA.node?.physicsBody?.categoryBitMask = CollisionCategories.None
contact.bodyB.node?.physicsBody?.categoryBitMask = CollisionCategories.None
contact.bodyA.node?.removeFromParent()
contact.bodyB.node?.removeFromParent()
self.addPoints(5)
}
}
So I've made .None categoryBitMask and applied it after collision detection so now points and spriteNode behavior is correct!
Related
Not sure which part is the problem. I'm doing a spriteKit shooter game and the collision isn't working. The spaceships are not being destroyed.
It must have to do with the collision somewhere...
The projectiles are just moving the enemy over slightly but not destroying them.
struct physicsCategory {
static let player : UInt32 = 1
static let enemy : UInt32 = 2
static let projectile : UInt32 = 3
}
func didBeginContact(contact: SKPhysicsContact) {
let firstBody : SKPhysicsBody = contact.bodyA
let secondBody : SKPhysicsBody = contact.bodyB
if ((firstBody.categoryBitMask == physicsCategory.projectile) && (secondBody.categoryBitMask == physicsCategory.enemy) || (firstBody.categoryBitMask == physicsCategory.enemy) && (secondBody.categoryBitMask == physicsCategory.projectile)) {
projectileCollision(enemyTemp: firstBody.node as! SKSpriteNode, projectileTemp: secondBody.node as! SKSpriteNode)
}
if ((firstBody.categoryBitMask == physicsCategory.enemy) && (secondBody.categoryBitMask == physicsCategory.player) || (firstBody.categoryBitMask == physicsCategory.player) && (secondBody.categoryBitMask == physicsCategory.enemy)) {
enemyPlayerCollision(enemyTemp: firstBody.node as! SKSpriteNode, playerTemp: secondBody.node as! SKSpriteNode)
}
}
///////////
func projectileCollision(enemyTemp: SKSpriteNode, projectileTemp: SKSpriteNode){
enemy.removeFromParent()
projectile.removeFromParent()
score = score + 1
updateScore()
}
////////////
func enemyPlayerCollision(enemyTemp: SKSpriteNode, playerTemp: SKSpriteNode) {
mainLabel.fontSize = 50
mainLabel.alpha = 1.0
mainLabel.text = "Game Over"
player.removeFromParent()
isAlive = false
waitThenMoveToTiltleScreen()
}
Your first issue is static let projectile : UInt32 = 3
If you want to declare a unique bitMasks, it needs to be in a power of 2 (2^0,2^1,2^2,..etc)
What 3 really means, is your projectile is both a player and an enemy ( 1 = 2^0, 2 = 2^1)
Your second issue is your logic
let f = firstBody.categoryBitMask
let s = secondbody.categoryBitmask
let p = physicsCategory.projectile
let x = physicsCategory.player
let e = physicsCategory.enemy
This is your projectile if statements:
if ((f == p) && (s == e) || (f == e) && (s == p)) {
projectileCollision(enemyTemp: firstBody.node as! SKSpriteNode, projectileTemp: secondBody.node as! SKSpriteNode)
}
In your if statement, you are saying either first or second could be the enemy, but in your projectile collision method, you are claiming that first body is always the enemy, and second body is always the projectile.
You need to handle the condition where your first body is projectile, and your second body is enemy, because chances are, your projectile will move into enemy, so it would be the first body.
thanks ( sorry I'm fairly new to spriteKit) I'm nearly there I think as I've written this code below and it worked better.
just with one small problem! some of the projectiles just go through the enemys.and only some destroy.
func didBegin(_ contact: SKPhysicsContact) {
let firstBody : SKPhysicsBody = contact.bodyA
let secondBody : SKPhysicsBody = contact.bodyB
if ((firstBody.categoryBitMask == physicsCategory.enemy) && (secondBody.categoryBitMask == physicsCategory.projectile) || (firstBody.categoryBitMask == physicsCategory.projectile) && (secondBody.categoryBitMask == physicsCategory.enemy)) {
projectileCollision(enemy: firstBody.node as! SKSpriteNode, projectile: secondBody.node as! SKSpriteNode)
}
else if ((firstBody.categoryBitMask == physicsCategory.enemy) && (secondBody.categoryBitMask == physicsCategory.player) || (firstBody.categoryBitMask == physicsCategory.player) && (secondBody.categoryBitMask == physicsCategory.enemy)) {
enemyPlayerCollision(enemy: firstBody.node as! SKSpriteNode, person: secondBody.node as! SKSpriteNode)
}
}
///////////
func projectileCollision(enemy: SKSpriteNode, projectile: SKSpriteNode){
enemy.removeFromParent()
projectile.removeFromParent()
score = score + 1
updateScore()
}
////////////
func enemyPlayerCollision(enemy: SKSpriteNode, person: SKSpriteNode) {
mainLabel.fontSize = 50
mainLabel.alpha = 1.0
mainLabel.text = "Game Over"
person.removeFromParent()
enemy.removeFromParent()
isAlive = false
waitThenMoveToTiltleScreen()
}
I want to add a bunch of different rocks and other dangerous objects that the player can collide with and die. How would I do this effectively? Now if I would copy paste these functions, it would surely work. But it seems like a huge amount of unnecessary code.
Sidenote: I'm very new to xcode, swift 2 & Sprite-kit.
func didBeginContact(contact: SKPhysicsContact) {
let firstBody : SKPhysicsBody = contact.bodyA
let secondBody : SKPhysicsBody = contact.bodyB
if ((firstBody.categoryBitMask == PhysicsCategory.Rock) && (secondBody.categoryBitMask == PhysicsCategory.Bullet) || (firstBody.categoryBitMask == PhysicsCategory.Bullet) && (secondBody.categoryBitMask == PhysicsCategory.Rock)) {
CollisionWithBullet(firstBody.node as! SKSpriteNode, Bullet: secondBody.node as! SKSpriteNode)
}
if ((firstBody.categoryBitMask == PhysicsCategory.Rock) && (secondBody.categoryBitMask == PhysicsCategory.Player) || (firstBody.categoryBitMask == PhysicsCategory.Player) && (secondBody.categoryBitMask == PhysicsCategory.Rock)) {
CollisionWithPlayer(firstBody.node as! SKSpriteNode, Player: secondBody.node as! SKSpriteNode)
}
}
func CollisionWithPlayer(Rock: SKSpriteNode, Player: SKSpriteNode){
let ScoreDefault = NSUserDefaults.standardUserDefaults()
ScoreDefault.setValue(Score, forKey: "Score")
ScoreDefault.synchronize()
let HighscoreDefault = NSUserDefaults.standardUserDefaults()
if (HighscoreDefault.valueForKey("Highscore") != nil){
Highscore = HighscoreDefault.valueForKey("Highscore") as! NSInteger
} else {
Highscore = 0
}
if (Score > Highscore){
let HighscoreDefault = NSUserDefaults.standardUserDefaults()
HighscoreDefault.setValue(Score, forKey: "Highscore")
}
self.view?.presentScene(EndScene())
ScoreLabel.removeFromSuperview()
}
So what you're likely looking for here is the bitwise OR operator and the bitwise AND operator.
With SpriteKit, physicsBodies can have multiple categories assigned to them. For instance, if we have the following categories set up:
enum CollisionCategories : UInt32 {
case Player = 1
case Enemy = 2
case Rock = 4
case Bullet = 8
}
we can set up the player category and contact bitmasks as follows:
let player = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 50, height: 50))
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.size)
player.physicsBody?.categoryBitMask = CollisionCategories.Player.rawValue
player.physicsBody?.contactTestBitMask = CollisionCategories.Enemy.rawValue
and two enemy category and contact bitmasks as follows:
let rockEnemy = SKSpriteNode(color: UIColor.greenColor(), size: CGSize(width: 25, height: 25))
rockEnemy.physicsBody = SKPhysicsBody(rectangleOfSize: rockEnemy.size)
rockEnemy.physicsBody?.categoryBitMask = CollisionCategories.Enemy.rawValue | CollisionCategories.Rock.rawValue
rockEnemy.physicsBody?.contactTestBitMask = CollisionCategories.Player.rawValue
let bulletEnemy = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 25, height: 25))
bulletEnemy.physicsBody = SKPhysicsBody(rectangleOfSize: bulletEnemy.size)
bulletEnemy.physicsBody?.categoryBitMask = CollisionCategories.Enemy.rawValue | CollisionCategories.Bullet.rawValue
bulletEnemy.physicsBody?.contactTestBitMask = CollisionCategories.Player.rawValue
Notice that on setting the category bitmasks, I am using the bitwise OR operator ('|'). This is a nice way of setting the bitmasks to two different categories at the same time. This way, a sprite can be both a rock and an enemy or a bullet and an enemy, etc.
See the image below for an idea of what is happening bitwise under the hood.
In our contact detection function, we can use the bitwise AND operator ('&') to see if our contact possesses a certain category.
func didBeginContact(contact: SKPhysicsContact) {
let firstBody : SKPhysicsBody = contact.bodyA
let secondBody : SKPhysicsBody = contact.bodyB
if (firstBody.categoryBitMask & CollisionCategories.Player.rawValue == CollisionCategories.Player.rawValue &&
secondBody.categoryBitMask & CollisionCategories.Enemy.rawValue == CollisionCategories.Enemy.rawValue) {
print("The collision was between the Player and an Enemy")
}
else if (firstBody.categoryBitMask & CollisionCategories.Enemy.rawValue == CollisionCategories.Enemy.rawValue &&
secondBody.categoryBitMask & CollisionCategories.Player.rawValue == CollisionCategories.Player.rawValue) {
print("The collision was between the Player and an Enemy")
}
}
This way, you can have enemies with multiple categories, but you can always do one check to see if a node is in fact an enemy, regardless of what other categories they might possess.
I have a shooter app that is crashing when the bullet hits two overlapping nodes. I've tried everything, I tried checking if the bodies were nil but it wouldnt allow me, I'm not sure how to make this work anymore. here's the code:
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 & photonCategory) != 0 && (secondBody.categoryBitMask & alientCategory) != 0){
aliensCollideWithBullets(firstBody.node as! SKSpriteNode, alien: secondBody.node as! SKSpriteNode)
// firstBody.node?.removeFromParent()
// secondBody.node?.removeFromParent()
}
}
func aliensCollideWithBullets(torpedo:SKSpriteNode, alien:SKSpriteNode) {
print("hit")
torpedo.removeFromParent()
alien.removeFromParent()
aliensDestroyed++
trumpsDestroyedLabel.text = "\(aliensDestroyed) Trumps"
if (aliensDestroyed > 10) {
}
}
the line crashing is:
if ((firstBody.categoryBitMask & photonCategory) != 0 && (secondBody.categoryBitMask & alientCategory) != 0){
aliensCollideWithBullets(firstBody.node as! SKSpriteNode, alien: secondBody.node as! SKSpriteNode)
// firstBody.node?.removeFromParent()
// secondBody.node?.removeFromParent()
}
Any help is appreciated.
Check bodyA and bodyB for nil. I had the same issue and solved it with this line of code:
if contact.bodyA.node != nil && contact.bodyB.node != nil
I have a bird, multiple desks and a star. When the bird collides with one of the desks, the didBeginContact method works fine, but when it collides with the star, nothing happens. I will copy the code where I added some print statements so it is easier to see what is happening and then I will paste the print statements that it prints out.
Bird (just the initialization of the physicsBody part):
class Bird: SKSpriteNode {
var sc: SKScene!
init(sc: SKScene) {
let texture = SKTexture(imageNamed: "hero")
let size = texture.size()
self.sc = sc
super.init(texture: texture, color: UIColor.clearColor(), size: CGSizeMake(size.width/2, size.height/2))
self.zPosition = Layer.Bird.rawValue
self.name = "bird"
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width/2)
self.physicsBody?.categoryBitMask = PhysicsCategory.Bird
self.physicsBody?.collisionBitMask = PhysicsCategory.Desk
self.physicsBody?.contactTestBitMask = PhysicsCategory.Desk | PhysicsCategory.StarSpecial | PhysicsCategory.Star
self.physicsBody?.allowsRotation = true
self.physicsBody?.restitution = 0
self.physicsBody?.usesPreciseCollisionDetection = true
}
// other methods and the required init
}
The star class is constructed the same way so I will just copy physicsBody related code:
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.width/2, center: self.position)
self.physicsBody?.affectedByGravity = false
self.physicsBody?.pinned = true
self.physicsBody?.categoryBitMask = PhysicsCategory.StarSpecial
self.physicsBody?.collisionBitMask = PhysicsCategory.None
self.physicsBody?.contactTestBitMask = PhysicsCategory.Bird
self.physicsBody?.usesPreciseCollisionDetection = true
self.physicsBody?.allowsRotation = false
self.physicsBody?.restitution = 0
self.physicsBody?.usesPreciseCollisionDetection = true
And the didBeginContact method in GameScene:
func didBeginContact(contact: SKPhysicsContact) {
print("something has collided")
if contact.bodyA.categoryBitMask == PhysicsCategory.Bird && contact.bodyB.categoryBitMask == PhysicsCategory.Desk {
birdSprite.BirdDidCollideWithDesk(contact.bodyA.node as! Bird, desk: contact.bodyB.node as! Desk, scoreClass: scoreClass, scoreLabel: scoreLabel)
print("bird and desk have collided")
} else if contact.bodyA.categoryBitMask == PhysicsCategory.Desk && contact.bodyB.categoryBitMask == PhysicsCategory.Bird {
print("desk and bird have collided")
birdSprite.BirdDidCollideWithDesk(contact.bodyB.node as! Bird, desk: contact.bodyA.node as! Desk, scoreClass: scoreClass, scoreLabel: scoreLabel)
}
if contact.bodyA.categoryBitMask == PhysicsCategory.Bird && contact.bodyB.categoryBitMask == PhysicsCategory.StarSpecial {
print("collided into star1")
starSpecial.removeStar(contact.bodyB.node as! Star)
} else if contact.bodyA.categoryBitMask == PhysicsCategory.StarSpecial && contact.bodyB.categoryBitMask == PhysicsCategory.Bird {
print("collided into star2")
starSpecial.removeStar(contact.bodyB.node as! Star)
}
}
If I run it, I get the following messages:
If the bird collides with a desk: "something has collided", "desk and bird have collided"
If the bird collides with the star: "something has collided"
Neither of second two conditions is met, so the message "collided into star1" or "collided into star2" is not printed out.
Any idea what could be causing this?
I've read similar questions posted here, but none of them fully address my problem.
I've only been working with iOS for a few months.
My collisions are working 90% of the time, but occasionally I'm getting collisions with same object. This should never happen because one of the 2 objects should explode.
Sometimes this leads to fatal crashes, seemingly because it tries to run the subsequent code after its already been executed.
Basically when the ship has a shield I want the asteroid to explode rather than the ship. I have put in the "if bonus == 0" statement to accomplish this.
I've posted the relevant code, if you need more, please let me know.
Have I over complicated the code? Or should I be taking another approach?
enum PhysicsCategory : UInt32 {
case None = 0
case objectGroup = 1
case birdGroup = 2
case shieldGroup = 4
case gapGroup = 8
case boundaryGroup = 16
case bonusGroup = 32
}
....
func didBeginContact(contact: SKPhysicsContact) {
if bonus == 0 {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
....
case PhysicsCategory.birdGroup.rawValue | PhysicsCategory.objectGroup.rawValue:
if contact.bodyA.categoryBitMask == PhysicsCategory.birdGroup.rawValue {
gameOverActions()
} else if contact.bodyB.categoryBitMask == PhysicsCategory.birdGroup.rawValue {
gameOverActions()
}
default:
fatalError("other collision: \(contactMask)")
}
} else if bonus > 0 {
println("Bonus > 0")
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
....
case PhysicsCategory.birdGroup.rawValue | PhysicsCategory.objectGroup.rawValue:
if contact.bodyA.categoryBitMask == PhysicsCategory.birdGroup.rawValue {
let secondBody = contact.bodyB.node
let asteroidExplosionNode = secondBody?.position
//asteroidExplosion()
let asteroidExplosionEmitter3 = SKEmitterNode(fileNamed: "explosionSmoke.sks")
asteroidExplosionEmitter3.position = asteroidExplosionNode!
asteroidExplosionEmitter3.name = "asteroidExplosionEmitter3"
asteroidExplosionEmitter3.zPosition = 25
asteroidExplosionEmitter3.targetNode = self
movingObjects.addChild(asteroidExplosionEmitter3)
secondBody?.removeFromParent()
bonus--
bonusUsed++
bonusIndicator.text = "x\(bonus)"
bonusIndicatorShadow.text = "x\(bonus)"
if bonus >= 1 {
shieldAnimate()
} else if bonus == 0 {
shield.removeFromParent()
}
} else if contact.bodyB.categoryBitMask == PhysicsCategory.birdGroup.rawValue {
let secondBody = contact.bodyA.node
let asteroidExplosionNode = secondBody?.position
//asteroidExplosion()
let asteroidExplosionEmitter3 = SKEmitterNode(fileNamed: "explosionSmoke.sks")
asteroidExplosionEmitter3.position = asteroidExplosionNode!
asteroidExplosionEmitter3.name = "asteroidExplosionEmitter3"
asteroidExplosionEmitter3.zPosition = 25
asteroidExplosionEmitter3.targetNode = self
movingObjects.addChild(asteroidExplosionEmitter3)
secondBody?.removeFromParent()
bonus--
bonusUsed++
bonusIndicator.text = "x\(bonus)"
bonusIndicatorShadow.text = "x\(bonus)"
if bonus >= 1 {
shieldAnimate()
} else if bonus == 0 {
shield.removeFromParent()
}
}
default:
fatalError("other collision: \(contactMask)")
}
}
}
Since you are removing the node on collision you can check if the parent is nil, before executing the rest of the code.
let secondBody = contact.bodyB.node
if secondBody?.parent != nil {
// Asteroid explosion and bonus code.
}