Multiple Contacts Registering

105 Views Asked by At

Any help would be greatly appreciated. I am relatively new to this and just feel like I beginning to understand coding.

My issue -----

I am having difficult trying to fix a problem with a SpriteKit tutorial that I have been enhancing as a way to hone and improve my skills as a newbie.

I am experiencing multiple contacts when my player “crashes” on the ground.

More than one life is removed, which causes my game to generate a 'Attemped to add a SKNode which already has a parent: ’ error adding the player back to the scene. I have tried player.removeFromParent everywhere I can think of inside my code.

The game works flawlessly as long as I “crash” into “enemies” in the air. Once the player contacts the ground, it’s all over. If I “kill” the player upon contact with the ground, there is no issue, but I want the game to continue as long as the player still has lives whether the contact the ground or and enemy.

I really believe the issue would be fixed if the multiple contact problem could be resolved.

class GameScene: SKScene, SKPhysicsContactDelegate {

// Set up the Texure Atlases
var images = SKSpriteNode()
var textureAtlas = SKTextureAtlas()
var textureArray = [SKTexture]()

var touchingScreen = false

var obstacle = SKSpriteNode()


// Generates a Random number between -350 and 350 (the center of the axis being 0)
let rand = GKRandomDistribution(lowestValue: -350, highestValue: 350)

var timer: Timer?


// This method is called when the Game Launches
override func didMove(to view: SKView) {

    // Adds a pixel perfect physicsBody to the player
    player.physicsBody = SKPhysicsBody(texture: player.texture!, size: player.texture!.size())
    player.physicsBody?.categoryBitMask = 1
    player.position = CGPoint(x: -400, y: 275)

    // Disables the affects of a collisions (+pushing, rotation etc.) on the player when a collision with another SKSpriteNode occurs
    player.physicsBody?.allowsRotation = false
    player.physicsBody?.collisionBitMask = 0

    addChild(player)

    physicsWorld.gravity = CGVector(dx: 0, dy: -2)
    physicsWorld.contactDelegate = self

    timer = Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)
}

// This method is called when the User touches the Screen
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    touchingScreen = true
}

// This method is called when the User stops touching the Screen
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    touchingScreen = false
}

// This method is called before each frame is rendered
override func update(_ currentTime: TimeInterval) {

    // Constrains the player to the scene area
    if player.position.y > 275 {
        player.position.y = 275
    }

    // Moves the player up when the screen is being touched
    if touchingScreen {
        player.physicsBody?.velocity = CGVector(dx: 0, dy: 200
        )
    }
}

func didBegin(_ contact: SKPhysicsContact) {
    // Exits the method if either node is nil (doesn't exist)
    guard let nodeA = contact.bodyA.node else { return }
    guard let nodeB = contact.bodyB.node else { return }

    // Check to see if either node is player and, if so, call the playerHit method and pass in the other node
    if nodeA == player {
        playerHit(nodeB)

    } else if nodeB == player {
        playerHit(nodeA)
    }
}

func createEnemy() {

    // Check for Bonus Creation
    checkForBonusCreation()

    // Choose a Random Enemy
    let pickEnemy = Int(arc4random_uniform(3))

    switch pickEnemy {

    case 0:
        obstacle = SKSpriteNode(imageNamed: "enemy-balloon")
        animateBalloon()

    case 1:
        obstacle = SKSpriteNode(imageNamed: "enemy-bird")
        animateBird()

    case 2:
        obstacle = SKSpriteNode(imageNamed: "enemy-plane")
        animatePlane()

    default:
        return
    }

    // Positions the enemy
    obstacle.zPosition = -2
    obstacle.position.x = 768
    obstacle.size = (CGSize(width: obstacle.size.width * 0.7, height: obstacle.size.width * 0.7))

    // Prevents the obstacle from being spawned too low on the scene
    if obstacle.position.y < -150 {
        obstacle.position.y = -150
    }

    addChild(obstacle)

    // Adds pixel perfect collision detection to the enemies
    obstacle.physicsBody = SKPhysicsBody(texture: obstacle.texture!, size: obstacle.texture!.size())

    // Then we set isDynamic to false so grivty will not affect the obstacles
    obstacle.physicsBody?.isDynamic = false

    // Assigns 1 to it's contactTestBitMask so that it know to detect a collision with the player
    obstacle.physicsBody?.contactTestBitMask = 1

    // Names the obstacle so we can track collisions properly
    obstacle.name = "enemy"

    // Spawn an enemy at a random y-axis
    obstacle.position.y = CGFloat(rand.nextInt())

    // Moves the obstacles across to and off the left hand side of the screen over 9 seconds athen removes thier nodes so they don't chew up memory
    let move = SKAction.moveTo(x: -768, duration: 9)
    let remove = SKAction.removeFromParent()
    let action = SKAction.sequence([move, remove])
    obstacle.run(action)
}

func playerHit(_ node: SKNode) {

    if node.name == "enemy" {

        player.removeFromParent()
        node.removeFromParent()
        lives -= 1
        balloonPop()
        showLivesRemaining()

    } else if node.name == "ground" {

        player.removeFromParent()
        lives -= 1
        balloonPop()
        showLivesRemaining()
    }
}

func balloonPop() {

    player.removeFromParent()  
}

func showLivesRemaining() {

    if lives >= 3 {
        lives = 3

    } else if lives <= 0 {
        lives = 0
        player.removeFromParent()
    }
}

// Jump to the restart or quit scene
func restartGame() {

    player.removeFromParent()

    let wait = SKAction.wait(forDuration: 2.0)

    let showPlayer = SKAction.run {
        player.position = CGPoint(x: -400, y: 275)
        self.addChild(player)
    }

    let sequence = SKAction.sequence([wait, showPlayer])

    run(sequence)
}

// Displays GAME OVER and takes the player to the "Restart or Quit" choice scene
func endGame() {

    player.removeFromParent()

    let gameOver = SKSpriteNode(imageNamed: "game-over")
    gameOver.zPosition = 15
    addChild(gameOver)

    // Waits 2 seconds and fade into the Restart Scene
    DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {

        if let scene = RestartScene(fileNamed: "RestartScene") {
            scene.scaleMode = .aspectFill
            self.view?.presentScene(scene, transition: SKTransition.crossFade(withDuration: 1.5))
        }
    }
}

}

1

There are 1 best solutions below

5
Ron Myschuk On

What a lot of games do is give a buffer period after a player is hit where they flash the player or turn the player red for a couple of seconds. during this time the player is not prone to more hits and gives the player an opportunity to move to safety.

func didBegin(_ contact: SKPhysicsContact) {

    let contactAName = contact.bodyA.node?.name
    let contactBName = contact.bodyB.node?.name

    //don't detect contact if the player is hit
    if ((contactAName == "player") || (contactBName == "player")) && !player.isHit {

         if (contactAName == "ground") || (contactBName == "ground") {
             print("groundcontact with player")

             player.isHit = true
             //good idea to flash player or pulse player to let user know player is hit
             self.run(.wait(forDuration: 3.0)) {
                 //wait for 3 seconds and make the player hittable again
                 self.player.isHit = false
             }
             return
        }
    }
}

Edit

the contact detection code fires at up to 60 times a second. When your player hits an enemy you remove that enemy, making more contact to the enemy not possible. However when the player hits the ground the ground does not get removed and thus player keeps losing lives. after the player hits the ground try bumping the player up a 100px away from the ground and see if that stops the multiple contact issue.