Easy SpriteKit Contact Detection (Space Shooter Game) Not Wotking Properly - swift

I'm trying to make a simple Space Shooter game. The contact should happen either between the torpedo and the alien or the shuttle and the alien. The problem is that this second contact (shuttle vs. alien) only happens after the first kind of contact has happend (torpedo vs. alien) and further more they're not always precise. This is a struct created outside the class
struct PhysicsCategory {
static let alien : UInt32 = 1
static let torpedo : UInt32 = 2
static let shuttle : UInt32 = 3 }
Shuttle:
shuttle.physicsBody = SKPhysicsBody(rectangleOfSize: shuttle.size)
shuttle.physicsBody?.categoryBitMask = PhysicsCategory.shuttle
shuttle.physicsBody?.contactTestBitMask = PhysicsCategory.alien
shuttle.physicsBody?.dynamic = false
shuttle.physicsBody?.affectedByGravity = false
Torpedo:
torpedo.physicsBody = SKPhysicsBody(rectangleOfSize: torpedo.size)
torpedo.physicsBody?.categoryBitMask = PhysicsCategory.torpedo
torpedo.physicsBody?.contactTestBitMask = PhysicsCategory.alien
torpedo.physicsBody?.affectedByGravity = false
torpedo.physicsBody?.dynamic = false
Alien:
alien.physicsBody = SKPhysicsBody(rectangleOfSize: torpedo.size)
alien.physicsBody?.categoryBitMask = PhysicsCategory.alien
alien.physicsBody?.contactTestBitMask = PhysicsCategory.torpedo
alien.physicsBody?.affectedByGravity = false
alien.physicsBody?.dynamic = true
Finally, here's my contact code:
func didBeginContact(contact: SKPhysicsContact) {
var firstBody : SKPhysicsBody = contact.bodyA
var secondBody : SKPhysicsBody = contact.bodyB
if ((firstBody.categoryBitMask == PhysicsCategory.alien) && (secondBody.categoryBitMask == PhysicsCategory.torpedo)) ||
((firstBody.categoryBitMask == PhysicsCategory.torpedo) && (secondBody.categoryBitMask == PhysicsCategory.alien)) {
self.contactWithTorpedo(firstBody.node as! SKSpriteNode, torpedo: secondBody.node as! SKSpriteNode)
} else if ((firstBody.categoryBitMask == PhysicsCategory.shuttle) && (secondBody.categoryBitMask == PhysicsCategory.alien)) {
self.contactWithShuttle(firstBody.node as! SKSpriteNode, shuttle: secondBody.node as! SKSpriteNode)
}
}
func contactWithTorpedo (alien: SKSpriteNode, torpedo : SKSpriteNode) {
alien.removeFromParent()
torpedo.removeFromParent()
score++
scoreLabel.text = "score: " + "\(score)"
}
func contactWithShuttle (alien:SKSpriteNode, shuttle:SKSpriteNode) {
alien.removeFromParent()
shuttle.removeFromParent()
self.view?.presentScene(EndScene())
}
I'm not really sure where the problem is, plus I've seen a couple of tutorials do the same. I don't know if it's relevant by the way, but this is not an iOS game but an OSX. Thank you in advance!

You might find it less confusing to restructure your didBeginContact as follows, as this avoids the firstBody/secondbody stuff and the complicated if...then conditions to see what has contacted what:
func didBeginContact(contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch contactMask {
case PhysicsCategory.alien | PhysicsCategory.torpedo:
// alien and torpedo have contacted
contact.bodyA.removeFromParent()
contact.bodyB.removeFromParent()
score += 1
scoreLabel.text = "score: " + "\(score)"
case PhysicsCategory.alien | PhysicsCategory.shuttle:
// alien and shuttle have contacted
contact.bodyA.removeFromParent()
contact.bodyB.removeFromParent()
self.view?.presentScene(EndScene())
default :
//Some other contact has occurred
print("Some other contact")
}
}
You can just add as many PhysicsCategory.enemy | PhysicsCategory.player cases as you need for all the contacts that you have to take action for in your game. Code each potential contact individually and you won't loose yourself in if...then...else.
if you do need to reference only one of the nodes involved in a contact, (e.g. to remove the player after an enemy hits it), you can do it like this:
let playerNode = contact.bodyA.categoryBitMask == PhysicsCategory.player ? contact.bodyA.node! : contact.bodyB.node!
playernode.removefromParent

I would recommend you to read the the docs about SKPhysicsBody.
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
First of all I would change the PhysicsCategory to
struct PhysicsCategory {
static let alien : UInt32 = 0x1 << 1
static let torpedo : UInt32 = 0x1 << 2
static let shuttle : UInt32 = 0x1 << 3
}
Then
alien.physicsBody?.contactTestBitMask = PhysicsCategory.torpedo | PhysicsCategory.shuttle
Hope this helps.

So I've actually managed to solve my problem yesterday. I'm posting the updated code in case it could help someone.
Outside the class:
struct PhysicsCategory {
static let player : UInt32 = 0x1 << 0
static let bullet : UInt32 = 0x1 << 1
static let enemy : UInt32 = 0x1 << 2}
And then, after applying the phyics to each sprite as i wrote before, inside the class:
//Contact with bullet
func contactWithBullet(enemy : SKSpriteNode, bullet: SKSpriteNode) {
enemy.removeFromParent()
bullet.removeFromParent()
score += 1
updateLabels()
}
//contact with player
func contactWithPlayer(player : SKSpriteNode, enemy : SKSpriteNode) {
enemy.removeFromParent()
lives -= 1
updateLabels() //another function that changes the score and lives labels
}
//CONTACT DETECTION
func didBeginContact(contact: SKPhysicsContact) {
let firstBody : SKPhysicsBody = contact.bodyA
let secondBody : SKPhysicsBody = contact.bodyB
if (firstBody.categoryBitMask == PhysicsCategory.enemy && secondBody.categoryBitMask == PhysicsCategory.bullet || firstBody.categoryBitMask == PhysicsCategory.bullet && secondBody.categoryBitMask == PhysicsCategory.enemy) {
contactWithBullet(firstBody.node as! SKSpriteNode, bullet: secondBody.node as! SKSpriteNode)
checkScore()
enemiesInWave -= 1
} else if (firstBody.categoryBitMask == PhysicsCategory.enemy && secondBody.categoryBitMask == PhysicsCategory.player || firstBody.categoryBitMask == PhysicsCategory.player && secondBody.categoryBitMask == PhysicsCategory.enemy) {
contactWithPlayer(firstBody.node as! SKSpriteNode, enemy: secondBody.node as! SKSpriteNode)
checkLives()
enemiesInWave -= 1
}
}

Related

game over is not working when player and enemy collide

This is my code:
func didBegin(_ 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 & CollisionTypes.enemy.rawValue != 0) && (secondBody.categoryBitMask & CollisionTypes.missile.rawValue != 0)) {
if let enemy = firstBody.node as? SKSpriteNode,
let missile = secondBody.node as? SKSpriteNode {
missileCollidedWithEnemy(missileNode: missile, enemyNode: enemy)
let gameOverScene = GamerOverScene(size: self.size)
self.view?.presentScene(gameOverScene)
}
}
if ((firstBody.categoryBitMask & CollisionTypes.enemy.rawValue != 0) && (secondBody.categoryBitMask & CollisionTypes.player.rawValue != 0)) {
if let enemy = firstBody.node as? SKSpriteNode,
let player = secondBody.node as? SKSpriteNode {
playerCollidedWithEnemy(enemyNode: enemy, playerNode: player)
}
}
if (contact.bodyA.categoryBitMask == playerCategory) {
contact.bodyA.node?.physicsBody?.collisionBitMask = 0
contact.bodyA.node?.physicsBody?.categoryBitMask = 0
} else if (contact.bodyB.categoryBitMask == playerCategory) {
contact.bodyB.node?.physicsBody?.collisionBitMask = 0
contact.bodyB.node?.physicsBody?.categoryBitMask = 0
}
if contact.bodyA.categoryBitMask == enemyCategory {
contact.bodyB.node?.removeFromParent()
contact.bodyB.node?.removeAllActions()
} else if contact.bodyB.categoryBitMask == enemyCategory {
contact.bodyA.node?.removeFromParent()
contact.bodyA.node?.removeAllActions()
}
So my game over does work but only when enemy and missile collide. When I take these lines of code:
let gameOverScene = GamerOverScene(size: self.size)
self.view?.presentScene(gameOverScene)
and place it into the second if statement so game over can show when player and enemy collide, it doesn't work. What's even more confusing is that when I delete the whole first if statement and place the line of code into the second one, the same thing happens, the enemy collides with the missile, and game over pops up.
You are getting expected behavior. You placed the code to show the Game Over scene when the missile and enemy collide.
Move the code to show the Game Over scene to the if statement where the player and enemy collide. You have to do that to get the scene to show when the player and enemy collide.
If the Game Over scene still only appears when the missile and enemy collide, you most likely have a problem with your category bit masks or the raw values of your collision types.

How to make one node disappear upon contact

So currently I am trying to make an app where when the player collides with the enemy, the enemy disappears. I have achieved this by writing this code;
func didBegin(_ contact: SKPhysicsContact) {
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if contact.bodyA.node?.name == "Player" {
firstBody = contact.bodyA
secondBody = contact.bodyB
}else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if firstBody.node?.name == "Player" && secondBody.node?.name == "Enemy" {
}
if contact.bodyA.categoryBitMask == 1 && contact.bodyB.categoryBitMask == 2 {
self.enumerateChildNodes(withName: "Enemy") { (node:SKNode, nil) in
if node.position.y < 550 || node.position.y > self.size.height + 550 {
node.removeFromParent()
}
}
}
}
However, because I'm enumeratingChildNodes with the name "Enemy", every enemy disappears on screen. I only want the one I hit to disappear. Any help? Thanks!
You'll want to replace this:
self.enumerateChildNodes(withName: "Enemy") { (node:SKNode, nil) in
if node.position.y < 550 || node.position.y >
self.size.height + 550 {
node.removeFromParent()
}
}
}
With something like this:
if contact.bodyA.node?.name == "Enemy" {
contact.bodyA.node?.removeFromParent()
} else if contact.bodyB.node?.name == "Enemy" {
contact.bodyB.node?.removeFromParent()
}
Contact bodyA and bodyB are the 2 nodes which have made contact with each other. The IF statement just checks to see which one is the enemy, then removes it.
JohnL has posted the correct answer, but you might find it helpful to structure 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("Player and enemy have contacted.")
let enemyNode = contact.bodyA.categoryBitMask == enemyCategory ? contact.bodyA.node : contact.bodyB.node
enemyNode.removeFromParent
default:
print("Some other contact occurred")
}
}
(The print statements are for debugging and can be removed)
This code doesn't bother assigning the bodies in the collision until required and logically ANDs the 2 category bit masks in order to ascertain what has hit what, and then using the 'switch' to process each collision. You could add extra switch cases for other collisions. We then use the ternary operator to get the 'enemy' node (Functionally the same as JohnL's 'if...then...) and remove it.

Why is my code never detecting any contact with my SKPhysicsBody?

The problem I am running in to is that the code I created to detect contact is not working. I wanted it to work when the ball category touched the obstacle category. What is wrong with my code? Thank you so much, any help is appreciated!
Heres where I define the categories:
let ballCategory : UInt32 = 0x1 << 1
let obstacleCategory : UInt32 = 0x1 << 2
Heres where I create the physics aspect of my ball:
leftBall.physicsBody = SKPhysicsBody(rectangleOf: leftBall.size)
leftBall.physicsBody?.categoryBitMask = ballCategory
leftBall.physicsBody?.contactTestBitMask = obstacleCategory
leftBall.physicsBody?.affectedByGravity = false
leftBall.physicsBody?.isDynamic = false
leftBall.physicsBody?.collisionBitMask = 0
Heres where I create the physics aspect of an obstacle:
obstacleSquare.physicsBody = SKPhysicsBody(rectangleOf: obstacleSquare.size)
obstacleSquare.physicsBody?.categoryBitMask = obstacleCategory
obstacleSquare.physicsBody?.contactTestBitMask = ballCategory
obstacleSquare.physicsBody?.affectedByGravity = false
obstacleSquare.physicsBody?.isDynamic = false
obstacleSquare.physicsBody?.collisionBitMask = 1
And heres my collision detection function:
func didBegin(_ contact: SKPhysicsContact) {
print("CONTACT")
if contact.bodyA.categoryBitMask == obstacleCategory {
print("CONTACT")
gameOver()
}
if contact.bodyB.categoryBitMask == obstacleCategory {
print("CONTACT")
gameOver()
}
if contact.bodyA.categoryBitMask == ballCategory {
print("CONTACT")
gameOver()
}
if contact.bodyB.categoryBitMask == ballCategory {
print("CONTACT")
gameOver()
}
}
Both of your object’s physics bodies have their isDynamic Property set to false. At least one of them needs to have this property set to true for any contacts to be registered.

SKShapeNode fillcolor comparison

I'm working on a little game where I have to match some colours. I'm working only with SKShapeNodes; but I cannot seem to get this code to work. I'm only getting the "Different color" print.
func didBegin(_ contact: SKPhysicsContact) {
let firstBody = contact.bodyA
let secondBody = contact.bodyB
if firstBody.categoryBitMask == PhysicsCategory.colorNode && secondBody.categoryBitMask == PhysicsCategory.colorBox || firstBody.categoryBitMask == PhysicsCategory.colorBox && secondBody.categoryBitMask == PhysicsCategory.colorNode {
let firstNode = contact.bodyA.node as? SKShapeNode
let secondNode = contact.bodyB.node as? SKShapeNode
if firstNode?.fillColor == secondNode?.fillColor {
/* TODO: Update score label */
print("Same Color")
point = +1
}
else {
print("Differnet color!")
/* TODO: Game over */
}
}
}
Any help appreciated :-)
I found the solution. One of the SKShapeNodes was made into a child of a SKNode containing the PhysicsBody.
Code works perfectly now.

didBeginContact not called : this instance is unique to me

So, I am still experimenting with Sprite Kit for my first time ever, and I would like to test for collision. So, I searched around a bit in Apple's documentation, around Stack Overflow, online tutorials, and other forums. However, I was unable to find something a tip or code that makes what I am doing work. So, here are the relevant pieces of code:
This is the code for an obstacle:
func createObstacle(){
var ball = SKShapeNode(circleOfRadius: 20)
var width = UInt32(self.frame.width)
var random_number = arc4random_uniform(width)
ball.position = CGPointMake(CGFloat(random_number), frame.height+20)
ball.strokeColor = SKColor.blackColor()
ball.glowWidth = 1.0
ball.fillColor = SKColor.darkGrayColor()
ball.physicsBody = SKPhysicsBody(circleOfRadius: 20)
ball.physicsBody!.affectedByGravity = true
ball.physicsBody?.categoryBitMask = 6
ball.physicsBody?.dynamic = true
self.addChild(ball)
}
This is relevant code for the thing that it would collide with:
let circle = SKShapeNode(circleOfRadius: 20)
circle.physicsBody = SKPhysicsBody(circleOfRadius: 20)
circle.fillColor = SKColor.blueColor()
circle.strokeColor = SKColor.blueColor()
circle.glowWidth = 1.0
circle.physicsBody?.categoryBitMask = 4
circle.physicsBody?.dynamic = true
circle.physicsBody?.affectedByGravity = false
And this is the code for contact:
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 == 4 && secondBody.categoryBitMask == 6) || (firstBody.categoryBitMask == 6 && secondBody.categoryBitMask == 4)){
println("HI")
}else{println("NO")}
}
Sadly, nothing is being printed at all, so something's wrong. Any idea why this doesn't work?
Your class should have delegate SKPhysicsContactDelegate.
class GameScene: SKScene, SKPhysicsContactDelegate {
In didMoveToView write this:
physicsWorld.contactDelegate = self
EDIT
Define CategoryBitMask like this
struct PhysicsCategory {
static let circleCategory : UInt32 = 0b1 // 1
static let ballCategory : UInt32 = 0b10 // 2
}
Give CategoryBitMask to circle and ball
circle.physicsBody?.categoryBitMask = PhysicsCategory.circleCategory
ball.physicsBody?.categoryBitMask = PhysicsCategory.ballCategory
Then check contact like this:
(func didBeginContact(contact: SKPhysicsContact) {
if ((contact.bodyA.categoryBitMask == 0b1 && contact.bodyB.categoryBitMask == 0b10 ) || ( contact.bodyA.categoryBitMask == 0b1 && contact.BodyB.categoryBitMask == 0b1 ))
println("Contact")
}
}
Sorry for typos didnt used editor