0

I have a restartButton that must appear when two bodies collide and when it happens for the first time, all goes great - bodies collide--> restartButton appears--> I restart level by touching restartButton. It was a "good, right restart" And here problem starts...

After level been restarted, if I touch at center of the screen(where restartButton must appear when is called) game crashes, saying following:

"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attemped to add a SKNode which already has a parent: name:'(null)' particleTexture: 'enemyPart.png' (100 x 100) position:{721.33929, 175.39999} accumulatedFrame:{{inf, inf}, {inf, inf}}' ** First throw call stack: (0x2a0fa137 etc.) libc++abi.dylib: terminating with uncaught exception of type NSException"

but restartButton is invisible and it couldn't even be there because no bodies have collided.

If after that "good restart" some enemy collide with player, restartButton appears for a moment and player, enemy1, enemy2, enemy3 are fadingOut from scene.

I would appreciate if someone can help

Here is code where you can see all that stuff:

import SpriteKit
import UIKit



let player = SKEmitterNode(fileNamed: "playerPart.sks")
let enemy1 = SKEmitterNode(fileNamed: "ePart.sks")
let enemy2 = SKEmitterNode(fileNamed: "ePart.sks")
let enemy3 = SKEmitterNode(fileNamed: "ePart.sks")
let restartButton = SKSpriteNode(imageNamed: "restartButton")
let playerCat: UInt32 = 0x1 << 0
let enemyCat: UInt32 = 0x1 << 1


class Level2: SKScene, SKPhysicsContactDelegate {

    override func didMoveToView(view: SKView) {

        physicsWorld.contactDelegate = self


        initWorld()
        movements()

        player.physicsBody = SKPhysicsBody(circleOfRadius: 50)
        player.position = CGPointMake(819.2 , 693.8)
        player.zPosition = 1
        player.physicsBody?.categoryBitMask = playerCat
        player.physicsBody?.contactTestBitMask = enemyCat
        player.targetNode = self
        self.addChild(player)

        enemy1.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(102, 102))
        enemy1.position = CGPointMake(819.2, 175.4)
        enemy1.zPosition = 1
        enemy1.physicsBody?.affectedByGravity = false
        enemy1.physicsBody?.dynamic = true
        enemy1.physicsBody?.allowsRotation = false
        enemy1.physicsBody?.categoryBitMask = enemyCat
        enemy1.physicsBody?.contactTestBitMask = playerCat
        enemy1.physicsBody?.collisionBitMask = 0x0
        enemy1.targetNode = self
        enemy1.particleBirthRate = 150
        enemy1.particleLifetime = 10
        enemy1.particleLifetimeRange = 20
        enemy1.particlePositionRange = CGVectorMake(50, 60)
        enemy1.emissionAngle = 0
        enemy1.emissionAngleRange = 0
        enemy1.particleSpeed = 0
        enemy1.particleSpeedRange = 0


        enemy2.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(102, 102))
        enemy2.position = CGPointMake(614.4, 386.6)
        enemy2.zPosition = 1
        enemy2.physicsBody?.affectedByGravity = false
        enemy2.physicsBody?.dynamic = true
        enemy2.physicsBody?.allowsRotation = false
        enemy2.physicsBody?.categoryBitMask = enemyCat
        enemy2.physicsBody?.contactTestBitMask = playerCat
        enemy2.physicsBody?.collisionBitMask = 0x0
        enemy2.targetNode = self
        enemy2.particleBirthRate = 150
        enemy2.particleLifetime = 10
        enemy2.particleLifetimeRange = 20
        enemy2.particlePositionRange = CGVectorMake(50, 60)
        enemy2.emissionAngle = 0
        enemy2.emissionAngleRange = 0
        enemy2.particleSpeed = 0
        enemy2.particleSpeedRange = 0

        enemy3.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(102, 102))
        enemy3.position = CGPointMake(409.6, 181.8)
        enemy3.zPosition = 1
        enemy3.physicsBody?.affectedByGravity = false
        enemy3.physicsBody?.dynamic = true
        enemy3.physicsBody?.allowsRotation = false
        enemy3.physicsBody?.categoryBitMask = enemyCat
        enemy3.physicsBody?.contactTestBitMask = playerCat
        enemy3.physicsBody?.collisionBitMask = 0x0
        enemy3.targetNode = self
        enemy3.particleBirthRate = 150
        enemy3.particleLifetime = 10
        enemy3.particleLifetimeRange = 20
        enemy3.particlePositionRange = CGVectorMake(50, 60)
        enemy3.emissionAngle = 0
        enemy3.emissionAngleRange = 0
        enemy3.particleSpeed = 0
        enemy3.particleSpeedRange = 0


    func initWorld() {

        self.addChild(enemy1)
        self.addChild(enemy2)
        self.addChild(enemy3)

    }


    func movements() {

        let move11 = SKAction.moveTo(CGPointMake(819.2, 386.6), duration: 1.5)
        let move12 = SKAction.moveTo(CGPointMake(614.4, 386.6), duration: 1.5)
        let move13 = SKAction.moveTo(CGPointMake(614.4, 175.4), duration: 1.5)
        let move14 = SKAction.moveTo(CGPointMake(819.2, 175.4), duration: 1.5)
        let enemy1m = SKAction.sequence([move11, move12, move13, move14])
        let enemy1move = SKAction.repeatActionForever(enemy1m)

        let move21 = SKAction.moveTo(CGPointMake(614.4, 591.4), duration: 1.5)
        let move22 = SKAction.moveTo(CGPointMake(409.6, 591.4), duration: 1.5)
        let move23 = SKAction.moveTo(CGPointMake(409.6, 386.6), duration: 1.5)
        let move24 = SKAction.moveTo(CGPointMake(614.4, 386.6), duration: 1.5)
        let enemy2m = SKAction.sequence([move21, move22, move23, move24])
        let enemy2move = SKAction.repeatActionForever(enemy2m)

        let move31 = SKAction.moveTo(CGPointMake(409.6, 386.6), duration: 1.5)
        let move32 = SKAction.moveTo(CGPointMake(204.8, 386.6), duration: 1.5)
        let move33 = SKAction.moveTo(CGPointMake(204.8, 181.8), duration: 1.5)
        let move34 = SKAction.moveTo(CGPointMake(409.6, 181.8), duration: 1.5)
        let enemy3m = SKAction.sequence([move31, move32, move33, move34])
        let enemy3move = SKAction.repeatActionForever(enemy3m)

        enemy1.runAction(enemy1move)
        enemy2.runAction(enemy2move)
        enemy3.runAction(enemy3move)
    }


    func didBeginContact(contact: SKPhysicsContact) {
        let collision:UInt32 = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask)

        if collision == (playerCat | enemyCat) {
            self.removeAllActions()
            self.runAction(SKAction.waitForDuration(0.1), completion: {
                self.runAction(SKAction.waitForDuration(0.2), completion:
                    {self.removeAllActions()
                        self.removeChildrenInArray([enemy1, enemy2, enemy3, player])})

                restartButton.size = CGSizeMake(200, 200)
                restartButton.position = CGPointMake(512, 384)
                restartButton.zPosition = 1
                self.addChild(restartButton)
            })


        }
    }




    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {

        for touch: AnyObject in touches {
            let location = touch.locationInNode(self)

            if (restartButton .containsPoint(location)) {
                restartButton.runAction(fadeAway)
                restartButton.removeFromParent()
                println(1)
                self.runAction(SKAction.waitForDuration(1.5), completion: {
                let repeatLevel = SKTransition.fadeWithDuration(2)
                let level2 = Level2(fileNamed: "Level2")
                self.view?.presentScene(level2, transition:     repeatLevel)


                })
            }

        }
    }

    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */
    }
}

2 Answers2

0

Your restartButton is having its size set for the first time when your playerCat and enemyCat first collide, which is fine.

Since the restartButton now has a size. You can check if your touch is within the bounds of the restartButton.

if (restartButton .containsPoint(location)) {

Like you have done so, but at no point do you check wether the restartButton is added to the scene. A quick fix could possibly be:

if (restartButton.parent != nil && restartButton .containsPoint(location)) {

If dont specifically need to check if it with the bounds of the node. You could directly check using this instead. Which will eliminate the need to check for a parent.

if (self.nodeAtPoint(location) == restartButton) {

Another thing i noticed, in your collision detection, you never check if it has already collided. So you might run the same code multiple times, where you just keep removing all actions and then adding a new one.

You could add a simple have variable to prevent redudancy

var detectionMade = false

and reset at

override func didMoveToView(view: SKView) {
    physicsWorld.contactDelegate = self

    initWorld()
    movements()

    detectionMade = false
    ...

and set to true when first colliding and checking

if collision == (playerCat | enemyCat) && !detectionMade {
    detectionMade = true
    ...
  • After adding this line `if (restartButton.parent == nil && restartButton .containsPoint(location)) {` restartButton appears after a collision, but when I press it, nothing happens – Andriy Zherebyatyev Apr 12 '15 at 12:21
  • My apologies, looks like i was writing that a bit too fast. Of course it should be restartButton.parent != nil, since you want to make sure that it has been added to the scene. I edited my answer, and also added an alternative way of checking if the button is being clicked, where there is no need to check for the parent. – martin meincke Apr 12 '15 at 16:35
0

@martinmeincke I've done it! By doing THIS:

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {

        for touch: AnyObject in touches {
            let location = touch.locationInNode(self)

            if (restartButton .containsPoint(location) && restartButton.parent == nil) {
                restartButton.runAction(fadeAway)

                println(1)



                }
            }

        }


    override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
        for touch: AnyObject in touches {
            let location = touch.locationInNode(self)

            if (restartButton .containsPoint(location)) {
                restartButton.removeFromParent()
                self.runAction(SKAction.waitForDuration(1.5), completion: {
                    let repeatLevel = SKTransition.fadeWithDuration(2)
                    let level2 = Level2(fileNamed: "Level2")
                    self.view?.presentScene(level2, transition: repeatLevel)
            })

        }
    }
    }