Detect collision between two objects in Swift

10,942

Here are a few items missing from your checklist:

  1. One of the physics bodies must be dynamic
  2. The physics bodies must be moved by a force/impulse or by setting their velocities
  3. The positions of the physics bodies should match their corresponding sprite/shape nodes

For (2), you are moving each bullet by changing its position over time with an SKAction.

For (3), the position of each bullet starts at (0, 0), while the shape is drawn at the top-center of the scene. Since the physics body is centered at the shape node's position, there is a mismatch between the locations of the shape and the physics body; the bullet's physics body never makes contact with the soldier. To resolve this, try

    var bullet = SKShapeNode(rectOfSize: CGSizeMake(10, 40))
    bullet.position = CGPointMake(self.size.width/2.0, self.size.height)

Also, the physics body of the soldier is half the radius of its shape.

Share:
10,942
Don P
Author by

Don P

@ Facebook currently

Updated on June 04, 2022

Comments

  • Don P
    Don P almost 2 years

    Collision detection is pretty simple in Swift - yet on this particular project, body collision is not triggering the didBeginContact event as usual.

    Here is my checklist for two bodies colliding (using soldiers and bullets):

    1. Add SKPhysicsContactDelegate to the class.
    2. set the physicsWorld appropriately, usually: self.physicsWorld.contactDelegate = self
    3. Create categories for each group of nodes you will have. E.g. let bulletCategory = 0x1 << 0
    4. Create SKNodes for each bullet and soldier.
    5. Give each bullet and soldier a physics body with a matching shape.
    6. Set each bullet's categoryBitMask to the bulletCategory (soldiers are set to soldierCategory).
    7. Set each bullet's contactBitMask to the soldierCategory (soldiers are set to bulletCategory).
    8. Define didBeginContact() handler.

    Below is my complete code. It is a minimal ~20 line "Hello World" for collision.

    If you create a new "Game" and copy paste this into the GameScene.swift, it will run, just no collision events will be fired.

    import SpriteKit
    
    class GameScene: SKScene, SKPhysicsContactDelegate {
        var soldier = SKShapeNode(circleOfRadius: 40)
    
        let soldierCategory:UInt32 = 0x1 << 0;
        let bulletCategory:UInt32 = 0x1 << 1;
    
        override func didMoveToView(view: SKView) {
            self.physicsWorld.contactDelegate = self
    
            // THE soldier
            soldier.fillColor = SKColor.redColor()
            soldier.position = CGPoint(x: CGRectGetMidX(self.frame), y: 40)
            soldier.physicsBody = SKPhysicsBody(circleOfRadius: 20)
            soldier.physicsBody!.dynamic = false
            soldier.physicsBody!.categoryBitMask = soldierCategory
            soldier.physicsBody!.contactTestBitMask = bulletCategory
            soldier.physicsBody!.collisionBitMask = 0
    
    
            // bulletS
            var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("makeBullet"), userInfo: nil, repeats: true)
    
            self.addChild(soldier)
        }
    
        func makeBullet() {
            var bullet = SKShapeNode(rect: CGRect(x: CGRectGetMidX(self.frame), y: self.frame.height, width: 10, height: 40), cornerRadius: CGFloat(0))
            bullet.fillColor = SKColor.redColor()
    
            bullet.physicsBody = SKPhysicsBody(rectangleOfSize: CGSize(width: 10, height: 40))
            bullet.physicsBody?.dynamic = false
            bullet.physicsBody?.categoryBitMask = bulletCategory
            bullet.physicsBody?.contactTestBitMask = soldierCategory
            bullet.physicsBody?.collisionBitMask = soldierCategory
    
            var movebullet = SKAction.moveByX(0, y: CGFloat(-400), duration: 1)
            var movebulletForever = SKAction.repeatActionForever(movebullet)
            bullet.runAction(movebulletForever)
    
            self.addChild(bullet)
        }
    
        func didBeginContact(contact: SKPhysicsContact) {
            print("CONTACT")
        }
    
        override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
            /* Called when a touch begins */
        }
    
        override func update(currentTime: CFTimeInterval) {
            /* Called before each frame is rendered */
        }
    }
    
    • Don P
      Don P over 9 years
      Wait a second. If a physicsBody is not "dynamic" can it not collide with anything?
  • Don P
    Don P over 9 years
    This is awesome, thank you. Is there a way where I can debug or see the positions of the physics bodies? I want to see how they are moving / set differently than the objects, but I can't color them in.
  • 0x141E
    0x141E over 9 years
    Set view.showsPhysics = true in didMoveToView or in your view controller.
  • Don P
    Don P over 9 years
    Do both physicsbodies need to be dynamic? In another project, I made a clone of FlappyBirdy, and only one object is dynamic (the bird), while the pipes are not dynamic, and just moved using SKAction.moveByX(), the contact event still occurs
  • 0x141E
    0x141E over 9 years
    I suggest you use a performSelector SKAction to call makeBullet instead of using a NSTimer. Actions pause/resume appropriately when you pause the game (with view.paused = true) and are removed automatically when you transition scenes.
  • Don P
    Don P over 9 years
    So right now when a bullet collides with a soldier, it will push the soldier. Do you know how to make them trigger a collision, but continue through eachother?
  • 0x141E
    0x141E over 9 years
    You can set bullet.physicsBody?.collisionBitMask = 0 to prevent the bullet from colliding with anything, or you can set bullet.physicsBody?.collisionBitMask &= ~soldierCategory to prevent the bullet from colliding with the soldier only. By default, all bits are set so it will collide with all categories.