Search code examples
swiftsprite-kitskphysicsworld

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))
}
}

Solution

  • 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