swag swag - 20 days ago 10
Swift Question

didBeginContact not being called Swift

I am working on an app that when the central ball colour matches a smaller ball that is flying towards the central ball colour the player scores a point.
So for this to work I need the didBeginContact function to call when the "enemy" and the "mainBall" collide. The only thing that is moving is the enemy ball as it flies towards the stationary mainBall
I believe I have set the bit masks up correctly but the didBeginContact function is not being called.
Can someone please help?

Here is my code

struct bitMasks{
static let mainBallMask:UInt32 = 0x1 << 0
static let enemyBall:UInt32 = 0x1 << 1
}
class GameScene: SKScene,SKPhysicsContactDelegate {

var mainBall = SKSpriteNode()
var ballSetToMainColor = true
var enemyTimer = Timer()
var hits = 0

override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
mainBall = childNode(withName: "centralBall") as! SKSpriteNode
mainBall.zPosition = 1.0
mainBall.physicsBody = SKPhysicsBody(circleOfRadius: mainBall.size.width/2)

mainBall.physicsBody?.categoryBitMask = bitMasks.mainBallMask
mainBall.physicsBody?.collisionBitMask = bitMasks.enemyBall
mainBall.physicsBody?.contactTestBitMask = bitMasks.enemyBall

mainBall.physicsBody?.isDynamic = false
mainBall.physicsBody?.affectedByGravity = false
enemyTimer = Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(enemies), userInfo: nil, repeats: true)
}


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
touchesCheckForChangedBall(touches: touches)
}

func didBegin(_ contact: SKPhysicsContact) {
let firstBody = contact.bodyA.node as! SKSpriteNode
let secondBody = contact.bodyB.node as! SKSpriteNode
if firstBody.name == "enemy" && secondBody.name == "centralBall"{
collisionMain(enemy: firstBody)
}else if firstBody.name == "centralBall" && secondBody.name == "enemy"{
collisionMain(enemy: secondBody)
}
}

func collisionMain(enemy: SKSpriteNode){
hits += 1
enemy.removeAllActions()
enemy.removeFromParent()
print(hits)

}

func touchesCheckForChangedBall(touches: Set<UITouch>){
for t in touches {
let location = t.location(in: self)
let nodes = self.nodes(at: location)
if nodes.isEmpty == false {
if let name = nodes[0].name{
if name == "centralBall"{
changeMainBallColor()
}
}
}
}
}

func changeMainBallColor(){
ballSetToMainColor = !ballSetToMainColor
if ballSetToMainColor == true{
mainBall.texture = SKTexture(imageNamed: "ball-mainColor")
}else{
mainBall.texture = SKTexture(imageNamed: "ball-secondaryColor")
}
}

override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}

func getRandomImageColor()->String{
let randVal = arc4random_uniform(2)
if randVal == 0{
return "ball-secondaryColor"
}else{
return "ball-mainColor"
}
}

func enemies(){
let color = getRandomImageColor()
let enemy = SKSpriteNode(imageNamed: color)
enemy.size = CGSize(width: 30, height: 30)
enemy.physicsBody = SKPhysicsBody(circleOfRadius: enemy.size.width/2)
enemy.physicsBody?.categoryBitMask = bitMasks.enemyBall
enemy.physicsBody?.collisionBitMask = bitMasks.mainBallMask
enemy.physicsBody?.contactTestBitMask = bitMasks.mainBallMask
enemy.name = "enemyBall"
enemy.physicsBody?.isDynamic = true
enemy.physicsBody?.affectedByGravity = false
let randomPositionNumber = arc4random() % 3
switch randomPositionNumber{
case 0:
enemy.position.x = 0
let posY = arc4random_uniform(UInt32(frame.size.height))
enemy.position.y = CGFloat(posY)
self.addChild(enemy)
break
case 1:
enemy.position.y = frame.size.height
let posX = arc4random_uniform(UInt32(frame.size.width))
enemy.position.x = CGFloat(posX)
self.addChild(enemy)
break
case 2:
enemy.position.x = frame.size.width
let posY = arc4random_uniform(UInt32(frame.size.height))
enemy.position.y = CGFloat(posY)
self.addChild(enemy)
break
default:
break
}
enemy.run(SKAction.move(to: mainBall.position, duration: 3))
}
}

Answer

As mentioned above it seems you have misspelled your node names. You are looking for "enemy" in the collision method but named your enemies "enemyBall". Thats why we should always create constants to avoid this e.g.

let enemyName = "Enemy"

and than use it like so

enemy.name = enemyName

You can also try writing your collision method like this, which should make it slightly nicer to read and you only need 1 if statement per collision. Also this way you do not need node names to compare bodies.

func didBegin(_ contact: SKPhysicsContact) {

    let firstBody: SKPhysicsBody
    let secondBody: SKPhysicsBody

    if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
        firstBody = contact.bodyA
        secondBody = contact.bodyB
    } else {
        firstBody = contact.bodyB
        secondBody = contact.bodyA
    }

    // Main ball with enemy or enemy with main ball
    if (firstBody.categoryBitMask == bitMasks.mainBall) && (secondBody.categoryBitMask == bitMasks.enemy) {
        // do something
    }
}

I would also try to follow the swift guidlines, classes, enums and structs should start with capital letters.

Hope this helps

Comments