Daniel Krupenin
Daniel Krupenin

Reputation: 21

How to correctly set up collisions in spritekite Swift?

I am creating a game in which balls spawn on top and after they fall down and bounce off the ground(this works),I am working on collisions now, my bullets hit the ball and kind of bounces off. If you need any additional code I will post it. (Edited)

import Foundation
import SpriteKit

Did move to view

struct PhysicsCategory {
    static let none: UInt32 = 0b0
    static let player: UInt32 = 0b1
    static let ball: UInt32 = 0b10
    static let bullet: UInt32 = 0b100
    static let ground: UInt32 = 0b1000
}

class GameScene: SKScene, SKPhysicsContactDelegate {


override func didMove(to view: SKView) {
    self.physicsWorld.contactDelegate = self
    let sceneBody = SKPhysicsBody(edgeLoopFrom: self.frame)
    sceneBody.friction = 0
    self.physicsBody = sceneBody
    player.position = CGPoint(x: self.size.width/2, y: self.size.height / 8)
    var timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(spawnBullet), userInfo: nil, repeats: true)
    player.size = CGSize(width: 100, height: 100)
    player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width/2)
    player.physicsBody?.affectedByGravity = false
    player.physicsBody?.isDynamic = true

    ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width/2)
    ball.physicsBody?.restitution = 1
    ball.physicsBody?.linearDamping = 0


    bullet.size = CGSize(width: 30, height: 30)
    bullet.zPosition = -5
    bullet.position = CGPoint(x: player.position.x, y: player.position.y)
    let action = SKAction.moveTo(y: self.size.height + 30, duration: 1.0)
    bullet.run(SKAction.repeatForever(action))
    bullet.physicsBody = SKPhysicsBody(circleOfRadius: bullet.size.width/2)
    bullet.name = "bullet"
    player.physicsBody?.categoryBitMask = PhysicsCategory.player
    player.physicsBody?.collisionBitMask = PhysicsCategory.none
    player.physicsBody?.contactTestBitMask = PhysicsCategory.ball

    bullet.physicsBody?.categoryBitMask = PhysicsCategory.bullet
    bullet.physicsBody?.collisionBitMask = PhysicsCategory.none
    bullet.physicsBody?.contactTestBitMask = PhysicsCategory.ball


    ball.physicsBody?.categoryBitMask = PhysicsCategory.ball
    ball.physicsBody?.contactTestBitMask = PhysicsCategory.bullet
    ball.physicsBody?.collisionBitMask = PhysicsCategory.ground

    ground.physicsBody?.categoryBitMask = PhysicsCategory.ground
    ground.physicsBody?.collisionBitMask = PhysicsCategory.ball
    ground.physicsBody?.contactTestBitMask = PhysicsCategory.none
    player.name = "player"
    addChild(player)
    ballSpawner(delay: 2.0)
    spawnGround()
}
//didBegin function
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

I THINK THIS PART IS WRONG Is my case right? Because when I try to write a bullet and ball it gives me an error - " Binary operator '|' cannot be applied to two 'SKSpriteNode' operands". So only default case is called.

    switch contactMask {
  //This case never gets called, i can't see it in the console when i run it
    case PhysicsCategory.bullet | PhysicsCategory.ball:
        let bullet = contact.bodyA.categoryBitMask == 
PhysicsCategory.bullet ? contact.bodyA.node : contact.bodyB.node
        let ball = contact.bodyA.categoryBitMask == 
PhysicsCategory.ball ? contact.bodyA.node : contact.bodyB.node
        collisionBulletAndBall(ball: (ball)!, bullet: (bullet)!)
        print("bullet and ball have contacted.")
    case PhysicsCategory.bullet:
        print("bullet contact")
    case PhysicsCategory.ball & PhysicsCategory.bullet:
        print("collision ball with bullet")
    default:
        print("Some other contact occurred")
    }
}

func gameOver() {
    print("game over")
}

func collisionBulletAndBall(ball: SKNode, bullet: SKNode) {

    ball.removeFromParent()
    bullet.removeFromParent()
    print("collision")
}
 @objc func spawnBullet() {
    var bullet = SKSpriteNode(imageNamed: "bullet")
    bullet.size = CGSize(width: 30, height: 30)
    bullet.zPosition = -5
    bullet.position = CGPoint(x: player.position.x, y: player.position.y)
    let action = SKAction.moveTo(y: self.size.height + 30, duration: 1.0)
    bullet.run(SKAction.repeatForever(action))
    bullet.name = "bullet"
    self.addChild(bullet)
    bullets.append(bullet)
}
 func spawnScoreBall() {
    var ballSize = CGSize(width: 30, height: 30)
    //Random Size
    let randomSize = arc4random() % 3
    switch randomSize {
    case 1:
        ballSize.width *= 1.2
        ballSize.height *= 1.2
    case 2:
        ballSize.width *= 1.5
        ballSize.height *= 1.5
    default:
        break
    }
    var ball = SKSpriteNode(imageNamed: "ball")
    ball.size = ballSize
    let y = size.height-ballSize.height/2
    //Random x
    var randomX = CGFloat(arc4random() % UInt32(size.width - ballSize.width))
    randomX -= size.width/2-ballSize.width*4
    ball.position = CGPoint(x: randomX, y: y)
    //Moving logic
    //        let moveDownAction = SKAction.moveBy(x: 0, y: -size.height + ball.size.height, duration: 2.0)
    //        let destroyAction = SKAction.removeFromParent()
    //        let sequenceAction = SKAction.sequence([moveDownAction, destroyAction])
    //        ball.run(sequenceAction)
    //Rotation
    var rotateAction = SKAction.rotate(byAngle: 1, duration: 1)
    let randomRotation = arc4random() % 2
    if randomRotation == 1 {
        rotateAction = SKAction.rotate(byAngle: -1, duration: 1)
    }
    let repeatForeverAction = SKAction.repeatForever(rotateAction)
    ball.run(repeatForeverAction)
    ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.size.width/2)
    ball.physicsBody?.affectedByGravity = true
    ball.physicsBody?.restitution = 1
    ball.physicsBody?.linearDamping = 0
    ball.zPosition = 3
    blocks.append(ball)
    self.addChild(ball)
}

Spawn ground action

func spawnGround() {
    ground = SKSpriteNode(color: UIColor.white, size: CGSize(width: self.frame.size.width, height: 50))
    ground.position = CGPoint(x: self.size.width/2, y: 0)
 }

Upvotes: 2

Views: 197

Answers (1)

Steve Ives
Steve Ives

Reputation: 8134

player and bullet don't appear to have physics bodies (there is no player.physicsBody = SKPhysicsBody... and bullet.physicsBody = SKPhysicsBody...

ball's physicsbody is assigned way after you assign the physics body's attributes:

ball.physicsBody?.categoryBitMask = PhysicsCategory.ball
    ball.physicsBody?.contactTestBitMask = PhysicsCategory.bullet | PhysicsCategory.ground | PhysicsCategory.player
    ball.physicsBody?.collisionBitMask = PhysicsCategory.bullet | PhysicsCategory.ground | PhysicsCategory.player

and then later

ball.physicsBody = SKPhysicsBody...

so all those ball.physicsBody assignments did nothing, as physicsBody? was 'nil'

Edit : Your didBegin looks a bit messy. Try 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 PhysicsCategory.bullet | PhysicsCategory.ball:
      let bullet = contact.bodyA.categoryBitMask == bullet ? contact.bodyA.node : contact.bodyB.node
      let ball = contact.bodyA.categoryBitMask == ball ? contact.bodyA.node : contact.bodyB.node
      ball.removeFromParent()
      bullet.removeFromParent()
      print("bullet and ball have contacted.")
   default:
      print("Some other contact occurred")
 }

I removed the setting of the ball's physics body's isDynamic and affectedByGravity properties as you are removing it.

Upvotes: 2

Related Questions