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