0

So I keep getting this error at random times during the play of the game, "fatal error: unexpectedly found nil while unwrapping an Optional value" in my asteroid battle game. I'll post the code here and if anyone could help figure it out I would appreciate it. If you don't understand any part you can ask me

import SpriteKit
import Darwin

struct PhysicsCatagory {
    static let Asteroids : UInt32 = 1 //00000000000000000000000000000001
    static let Bullet : UInt32 = 2 //0000000000000000000000000000010
    static let Fighter : UInt32 = 3 //0000000000000000000000000000100

    }

class GameScene: SKScene, SKPhysicsContactDelegate {

var Score = Int()
var scoreLabel = UILabel()

var Fighter = SKSpriteNode(imageNamed: "Fighter")

override func didMoveToView(view: SKView) {
    /* Setup your scene here */

    physicsWorld.contactDelegate = self

    Fighter.position = CGPointMake(self.size.width / 2, self.size.height / 7)
    Fighter.physicsBody = SKPhysicsBody(rectangleOfSize: Fighter.size)
    Fighter.physicsBody?.affectedByGravity = false
    Fighter.physicsBody?.categoryBitMask = PhysicsCatagory.Fighter
    Fighter.physicsBody?.contactTestBitMask = PhysicsCatagory.Asteroids
    Fighter.physicsBody?.dynamic = false

    var Timer = NSTimer.scheduledTimerWithTimeInterval(0.25, target: self, selector: Selector("spawnBullets"), userInfo: nil, repeats: true)

    var asteriodsTimer = NSTimer.scheduledTimerWithTimeInterval(0.35 , target: self, selector: Selector("spawnAsteroids"), userInfo: nil, repeats: true)

    var furiousAsteriodsTimer = NSTimer.scheduledTimerWithTimeInterval(0.9 , target: self, selector: Selector("spawnFuriousAsteroids"), userInfo: nil, repeats: true)


    self.addChild(Fighter)

    scoreLabel.text = "\(Score)"
    scoreLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 40))
    scoreLabel.backgroundColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 0.3)
    scoreLabel.textColor = UIColor.whiteColor()

    self.view?.addSubview(scoreLabel)

}

func didBeginContact(contact: SKPhysicsContact) {

    let firstBody : SKPhysicsBody = contact.bodyA
    let secondBody : SKPhysicsBody = contact.bodyB

    if ((firstBody.categoryBitMask == PhysicsCatagory.Asteroids) && (secondBody.categoryBitMask == PhysicsCatagory.Bullet) || (firstBody.categoryBitMask == PhysicsCatagory.Bullet) && (secondBody.categoryBitMask == PhysicsCatagory.Asteroids)){

        bulletHitAsteroids(firstBody.node as! SKSpriteNode, Bullet: secondBody.node as! SKSpriteNode)

    }
}

func bulletHitAsteroids(Asteroids: SKSpriteNode, Bullet: SKSpriteNode){

    Asteroids.removeFromParent()
    Bullet.removeFromParent()

    Score++
    scoreLabel.text = "\(Score)"

}

func bulletHitFuriousAsteroids(FuriousAsteroids: SKSpriteNode, Bullet: SKSpriteNode){

    FuriousAsteroids.removeFromParent()
    Bullet.removeFromParent()

    Score = Score + 2
    scoreLabel.text = "\(Score)"
}


func spawnBullets(){

    let Bullet = SKSpriteNode(imageNamed: "Bullet")
    Bullet.zPosition = -5
    Bullet.position = CGPointMake(Fighter.position.x, Fighter.position.y)

    let action = SKAction.moveToY(self.size.height + 30, duration: 0.9)
    let actionDone = SKAction.removeFromParent()
    Bullet.runAction(SKAction.sequence([action, actionDone]))

    Bullet.physicsBody = SKPhysicsBody(rectangleOfSize: Bullet.size)
    Bullet.physicsBody?.categoryBitMask = PhysicsCatagory.Bullet
    Bullet.physicsBody?.contactTestBitMask = PhysicsCatagory.Asteroids
    Bullet.physicsBody?.affectedByGravity = false
    Bullet.physicsBody?.dynamic = false

    self.addChild(Bullet)

}



func spawnAsteroids(){
    let Asteroids = SKSpriteNode(imageNamed: "Asteroid")
    let minValue = self.size.width / 8
    let maxValue = self.size.width - 20
    let spawnPoint = UInt32(maxValue - minValue)
    Asteroids.position = CGPoint(x: CGFloat(arc4random_uniform(spawnPoint)) , y: self.size.height)

    Asteroids.physicsBody = SKPhysicsBody(rectangleOfSize: Asteroids.size)
    Asteroids.physicsBody?.categoryBitMask = PhysicsCatagory.Asteroids
    Asteroids.physicsBody?.contactTestBitMask = PhysicsCatagory.Bullet
    Asteroids.physicsBody?.affectedByGravity = false
    Asteroids.physicsBody?.dynamic = true


    let action = SKAction.moveToY(-50, duration: 2.5)
    let actionDone = SKAction.removeFromParent()

    Asteroids.runAction(SKAction.sequence([action, actionDone]))

    self.addChild(Asteroids)

}

func spawnFuriousAsteroids(){
    let FuriousAsteroids = SKSpriteNode(imageNamed: "FuriousAsteroid")
    let minValue = self.size.width / 8
    let maxValue = self.size.width - 20
    let spawnPoint = UInt32(maxValue - minValue)
    FuriousAsteroids.position = CGPoint(x: CGFloat(arc4random_uniform(spawnPoint)) , y: self.size.height)

    FuriousAsteroids.physicsBody = SKPhysicsBody(rectangleOfSize: FuriousAsteroids.size)
    FuriousAsteroids.physicsBody?.categoryBitMask = PhysicsCatagory.Asteroids
    FuriousAsteroids.physicsBody?.contactTestBitMask = PhysicsCatagory.Bullet
    FuriousAsteroids.physicsBody?.affectedByGravity = false
    FuriousAsteroids.physicsBody?.dynamic = true


    let action = SKAction.moveToY(-50, duration: 1.5)
    let actionDone = SKAction.removeFromParent()

    FuriousAsteroids.runAction(SKAction.sequence([action, actionDone]))

    self.addChild(FuriousAsteroids)

}


override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
   /* Called when a touch begins */

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

        Fighter.position.x = location.x
    }
}

override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {

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

        Fighter.position.x = location.x
    }
}

override func update(currentTime: CFTimeInterval) {
    /* Called before each frame is rendered */
}
}
RapidReverse
  • 13
  • 1
  • 10
  • 2
    I suggest you run this in the debugger so that you know *where* the error occurs. That will probably give you the answer. – Jan Doggen Jan 29 '16 at 13:21
  • You should have some warning messages, can you post them here? – Rana Jan 29 '16 at 15:11
  • I suspect the `Fighter` category should be 4 not 3. Safer to use `0x1 << 0`, `0x1 << 1`, `0x1 << 2`, ... for your categories. – 0x141E Jan 29 '16 at 20:56
  • @RapidReverse Note that 0100 is a binary representation of number 4. Number 3 would be 0011. Here is one extremely good example about [bit-masks](http://stackoverflow.com/questions/34904564/game-engine-collison-bitmask-why-0x01-etc/34911876#34911876) One thing you should consider is to have two distinct categories for asteroids and furious asteroids. That way, you could easily differentiate between those two type of asteroids. There are other ways, but this would be an easiest solution. – Whirlwind Jan 31 '16 at 07:09

1 Answers1

0

It is crashing because one of the bodies doesn't have its node. When crash happen, try to type something like this in debugger console :

po firstBody.node, or po secondBody.node. One of these two will be nil.

This is happening when more than two bodies make contact as in your example, where you can have a situation where bullet and a two asteroids might be in a contact at the same time. Because SpriteKit can check for a contact only between two bodies at the time, something like this will happen:

  • didBeginContact is invoked with two physicsBodies passed in (bullet and first asteroide)
  • now, you are force unwrapping nodes associated with those bodies and removing the asteroid and the bullet from its parent

then, because there are more contacts occurred

  • didBeginContact is invoked again, with another two physics bodies, but one of those physicsBodies is bullet's physics body again. Another body is , let say furious asteroid.
  • now, again you are force unwrapping nodes associated with those two physics bodies, but one of them (bullet) is nil.

Solution would be something like this in your didBeginContact method:

 if let firstNode = firstBody.node as? SKSpriteNode, let secondNode = secondBody.node as? SKSpriteNode {

   bulletHitAsteroids(firstNode, Bullet: secondNode)
}

This way, one bullet will destroy only one asteroid. Which I guess is what you want and what make sense after all.

Whirlwind
  • 14,286
  • 11
  • 68
  • 157
  • Ok it wouldn't crash now, but my fighter disappear when the asteroids which is what I wanted, but not now. – RapidReverse Jan 30 '16 at 00:00
  • also another problem I'm having is that the score only goes up by 1 and not 2. And I don't know how to move my score label to the top middle and increase the font size – RapidReverse Jan 30 '16 at 00:04
  • @RapidReverse First fix what 0x141E pointed in his comment ( use 0x1 << 0 etc. to set categories) and handle contact between fighter and asteroids. About the score. It works like you've set it. You have Score++ in one place(bulletHitAsteroids), and Score+=2 on the other place(bulletHitFuriousAsteroids). – Whirlwind Jan 30 '16 at 00:10
  • yes but after I added the code you gave me, every time an asteroid hit my fighter it disappear. And i did for the scoring Score += 2 , but, in my score label it only goes up by one at a time. – RapidReverse Jan 31 '16 at 02:23
  • @RapidReverse How do you expect to increment the score by 2 without running the code responsible for that action? From the code you've provided it can be clearly seen that method bulletHitFuriousAsteroids is never called, thus, the score can't be incremented by two. Also it can be seen that contact between fighter and asteroids is not handled at all. There are few more things which are wrong in your code , but this question was about crashes and that is fixed, so you should ask another question about further issues. – Whirlwind Jan 31 '16 at 06:51
  • Ok, so. I have understood what you said about the scoring. but when I added the code. if let firstNode = firstBody.node as? SKSpriteNode, let secondNode = secondBody.node as? SKSpriteNode { bulletHitAsteroids(firstNode, Bullet: secondNode) } My "Fighter" character dissapear from the screen when a asteroid touched is fighter. It won't crash anymore. but my fighter dissapear. And I don't know why. – RapidReverse Feb 05 '16 at 09:08
  • @RapidReverse that is strange, because it works for me (fighter stays on screen) if I make those changes on the code you've posted. – Whirlwind Feb 05 '16 at 11:08
  • Ok so i try putting it in `if ((firstBody.categoryBitMask == PhysicsCatagory.Asteroids) && (secondBody.categoryBitMask == PhysicsCatagory.Bullet) || (firstBody.categoryBitMask == PhysicsCatagory.Bullet) && (secondBody.categoryBitMask == PhysicsCatagory.Asteroids))` And now the Fighter won't dissapear. But, I notice that it still crashes. And I think it is from when the FuriousAsteroid is travelling down, faster than normal asteroid. It **hit** the normal asteroid. And if i shoot the asteroid that is being it, the game crashes, but I don't know how to fix it. – RapidReverse Feb 05 '16 at 12:56