0

I am making a little FlappyBird clone and have got everything working as it should until I changed the physics body of the bird to be exact to the texture. Now what it does is when it flies through the gap in the pipes it counts 30 points instead of just 1 point.

This only happens when I use the texture exact physics body which I need because the bird isn't round nor rectangular.

How would I go about making the collision so it only collides once with each gap node. I have tried setting the categoryBitBask to 0 after the contact but then all the gaps after don't add to the point count anymore at all.

Here is the full game code:

var score = 0

class GameScene: SKScene, SKPhysicsContactDelegate {

var bird = SKSpriteNode()
var bg = SKSpriteNode()
var ground = SKSpriteNode()

var scoreLabel = SKLabelNode(fontNamed: "Candice")
var gameOverLabel = SKLabelNode(fontNamed: "Candice")

var countbg = SKSpriteNode()

var timer = Timer()

enum ColliderType: UInt32 {
    case Bird = 1
    case Object = 2
    case Gap = 4
}

var gameOver = false

let swooshSound = SKAction.playSoundFileNamed("sfx_swooshing.wav", waitForCompletion: false)
let pointSound = SKAction.playSoundFileNamed("sfx_point.wav", waitForCompletion: false)
let hitSound = SKAction.playSoundFileNamed("sfx_hit.wav", waitForCompletion: false)

@objc func makePipes() {

    let movePipes = SKAction.move(by: CGVector(dx: -2 * self.frame.width, dy: 0), duration: TimeInterval(self.frame.width / 150))
    let removePipes = SKAction.removeFromParent()
    let moveAndRemovePipes = SKAction.sequence([movePipes, removePipes])

    let gapHeight = bird.size.height * 2.8

    let movementAmount = arc4random() % UInt32(self.frame.height) / 2

    let pipeOffset = CGFloat(movementAmount) - self.frame.height / 4

    let pipeTexture = SKTexture(imageNamed: "pipe1.png")
    let pipe1 = SKSpriteNode(texture: pipeTexture)
    pipe1.position = CGPoint(x: self.frame.midX + self.frame.width, y: self.frame.midY + pipeTexture.size().height / 2 + gapHeight / 2 + pipeOffset)
    pipe1.zPosition = 2
    pipe1.run(moveAndRemovePipes)

    pipe1.physicsBody = SKPhysicsBody(rectangleOf: pipeTexture.size())
    pipe1.physicsBody!.isDynamic = false

    pipe1.physicsBody!.contactTestBitMask = ColliderType.Object.rawValue
    pipe1.physicsBody!.categoryBitMask = ColliderType.Object.rawValue
    pipe1.physicsBody!.collisionBitMask = ColliderType.Object.rawValue

    self.addChild(pipe1)

    let pipe2Texture = SKTexture(imageNamed: "pipe2.png")
    let pipe2 = SKSpriteNode(texture: pipe2Texture)
    pipe2.position = CGPoint(x: self.frame.midX + self.frame.width, y: self.frame.midY - pipeTexture.size().height / 2 - gapHeight / 2 + pipeOffset)
    pipe2.zPosition = 2
    pipe2.run(moveAndRemovePipes)

    pipe2.physicsBody = SKPhysicsBody(rectangleOf: pipe2Texture.size())
    pipe2.physicsBody!.isDynamic = false

    pipe2.physicsBody!.contactTestBitMask = ColliderType.Object.rawValue
    pipe2.physicsBody!.categoryBitMask = ColliderType.Object.rawValue
    pipe2.physicsBody!.collisionBitMask = ColliderType.Object.rawValue

    self.addChild(pipe2)

    let gap = SKNode()

    gap.position = CGPoint(x: self.frame.midX + self.frame.width, y: self.frame.midY + pipeOffset)
    gap.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 1, height: gapHeight))
    gap.physicsBody!.isDynamic = false
    gap.run(moveAndRemovePipes)

    gap.physicsBody!.contactTestBitMask = ColliderType.Bird.rawValue
    gap.physicsBody!.categoryBitMask = ColliderType.Gap.rawValue
    gap.physicsBody!.collisionBitMask = ColliderType.Gap.rawValue

    self.addChild(gap)
}

func didBegin(_ contact: SKPhysicsContact) {

    if gameOver == false {

    if contact.bodyA.categoryBitMask == ColliderType.Gap.rawValue || contact.bodyB.categoryBitMask == ColliderType.Gap.rawValue {

        score += 1
        scoreLabel.text = String(format: "%05d", score)
        run(pointSound)

    } else {

        self.speed = 0
        run(hitSound)
        gameOver = true
        timer.invalidate()
        bird.removeFromParent()

        let changeSceneAction = SKAction.run(changeScene)
        self.run(changeSceneAction)
        }
    }
}

//MARK: Change to Game Over Scene
func changeScene(){

    let sceneToMoveTo = GameOverScene(size: self.size)
    sceneToMoveTo.scaleMode = self.scaleMode
    let myTransition = SKTransition.fade(withDuration: 0.5)
    self.view!.presentScene(sceneToMoveTo, transition: myTransition)
}

override func didMove(to view: SKView) {

    self.physicsWorld.contactDelegate = self

    setupGame()
}

func setupGame() {

    timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(self.makePipes), userInfo: nil, repeats: true)

    let groundTexture = SKTexture(imageNamed: "ground.png")
    let moveGroundAnimation = SKAction.move(by: CGVector(dx: -groundTexture.size().width, dy: 0), duration: 7)
    let shiftGroundAnimation = SKAction.move(by: CGVector(dx: groundTexture.size().width, dy: 0), duration: 0)
    let moveGroundForever = SKAction.repeatForever(SKAction.sequence([moveGroundAnimation, shiftGroundAnimation]))

    var i: CGFloat = 0
    while i < 3 {

        ground = SKSpriteNode(texture: groundTexture)
        ground.position = CGPoint(x: self.size.width * i, y: self.size.height / 7.65)
        ground.zPosition = 3
        ground.run(moveGroundForever)
        self.addChild(ground)

        i += 1
    }

    let bottom = SKNode()
    bottom.position = CGPoint(x: self.frame.midX, y: self.size.height / 7)
    bottom.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: self.frame.width, height: 1))
    bottom.physicsBody!.isDynamic = false

    bottom.physicsBody!.contactTestBitMask = ColliderType.Object.rawValue
    bottom.physicsBody!.categoryBitMask = ColliderType.Object.rawValue
    bottom.physicsBody!.collisionBitMask = ColliderType.Object.rawValue
    self.addChild(bottom)

    let bgTexture = SKTexture(imageNamed: "bg.png")
    bg = SKSpriteNode(texture: bgTexture)
    bg.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
    bg.size = self.frame.size
    bg.zPosition = 1
    self.addChild(bg)

    let birdTexture = SKTexture(imageNamed: "flappy1.png")
    let bird2Texture = SKTexture(imageNamed: "flappy2.png")
    let bird3Texture = SKTexture(imageNamed: "flappy3.png")
    let bird4Texture = SKTexture(imageNamed: "flappy4.png")
    let bird5Texture = SKTexture(imageNamed: "flappy5.png")
    let bird6Texture = SKTexture(imageNamed: "flappy6.png")

    let animation = SKAction.animate(with: [birdTexture, bird2Texture, bird3Texture, bird4Texture, bird5Texture, bird6Texture], timePerFrame: 0.1)
    let makeBirdFlap = SKAction.repeatForever(animation)


    bird = SKSpriteNode(texture: birdTexture)
    bird.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
    bird.setScale(1)
    bird.zPosition = 6

    bird.run(makeBirdFlap)

    self.addChild(bird)

    bird.physicsBody = SKPhysicsBody.init(circleOfRadius: birdTexture.size().height / 2)
    //bird.physicsBody = SKPhysicsBody(texture: birdTexture, size: birdTexture.size())
    bird.physicsBody!.isDynamic = false
    bird.physicsBody!.contactTestBitMask = ColliderType.Object.rawValue
    bird.physicsBody!.categoryBitMask = ColliderType.Bird.rawValue
    bird.physicsBody!.collisionBitMask = ColliderType.Bird.rawValue

    let countbg = SKSpriteNode(imageNamed: "count_bg.png")
    countbg.position = CGPoint(x: self.size.width / 4.8, y: self.size.height * 0.94)
    countbg.setScale(0.8)
    countbg.zPosition = 4
    addChild(countbg)

    scoreLabel.fontSize = 80
    scoreLabel.text = String(format: "%05d", score)
    scoreLabel.fontColor = SKColor(red: 218/255, green: 115/255, blue: 76/255, alpha: 1)
    scoreLabel.position = CGPoint(x: self.size.width / 4, y: self.size.height * 0.94)
    scoreLabel.zPosition = 5
    addChild(scoreLabel)
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if gameOver == false {
        bird.physicsBody!.isDynamic = true
        bird.physicsBody!.velocity = CGVector(dx: 0, dy: 0)
        bird.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 280))
        //run(swooshSound)
    } else {
        gameOver = false
        score = 0
        self.speed = 1
        self.removeAllChildren()
        setupGame()
    }
}

override func update(_ currentTime: TimeInterval) {
    // Called before each frame is rendered
}
}
  • You are not going wrong; that is normal with physics engines. Just ignore the extraneous contacts. – trojanfoe Feb 12 '19 at 13:58
  • What do you mean ignore? –  Feb 12 '19 at 13:59
  • 1
    Once you have "noted" the collision, set some flag in one/both objects. Clear that flag when the `didEnd()` method is called. If you are in `didBegin()` and that flag is set then you can safely ignore the collision. I assume SpriteKit is using Box2D, possibly with ccd enabled, and that stuff happens. – trojanfoe Feb 12 '19 at 14:02
  • OK still kinda new to pogromming, so not sure where to implement that. I know that there is no didEnd function in this code. –  Feb 12 '19 at 14:06
  • Check [this answer](https://stackoverflow.com/a/35275577/299924) on a duplicate question. – trojanfoe Feb 12 '19 at 14:12
  • 1
    Possible duplicate of [Why are didBeginContact called multiple times?](https://stackoverflow.com/questions/24228274/why-are-didbegincontact-called-multiple-times) – trojanfoe Feb 12 '19 at 14:12
  • setting the contact.bodyA.categoryBitMask = 0 after contact works, but then it stays like that stops counting all together in the upcoming pipes. I think my issue is a little bit different from the other Question. Cause contact needs to happen all the time and the game doesn't resent after every contact. –  Feb 12 '19 at 14:20
  • Yeah I don't really like that approach as I assume you wouldn't get a `didEnd()` for that collision either. If you put your collision logic into the bird then you could simply have an instance variable flag that allows further collisions until `didEnd()` is called. – trojanfoe Feb 12 '19 at 14:28
  • Thanks for your help, but I don't quite get what you mean, beginner here. –  Feb 12 '19 at 15:11
  • Wait.... this can’t work. Bird is not dynamic, and gap is not dynamic..... what is causing a contact? – Knight0fDragon Feb 13 '19 at 12:33
  • It definitely does work, as long as I don't use the texture specific physics body. –  Feb 13 '19 at 12:55
  • I am sure your project works, your example does not work. That means something else is going on that you are not telling us. – Knight0fDragon Feb 13 '19 at 15:29
  • I added the whole code to the post now so you can see it, sorry for the inconvenience. –  Feb 13 '19 at 21:01

1 Answers1

0

If you would be using RxSwift, you would be able to easily get rid of those extra events easily by using debounce() or throttle() or distinctUntilChanged(). If you are willing to try this approach, give RxSpriteKit framework a go. Otherwise, store a timestamp of the last contact and ignore the following contacts until some time period elapses.

Maxim Volgin
  • 3,957
  • 1
  • 23
  • 38