0

I'm currently taking an iOS game development course on Make School. The first step in this course is to. make a game which is similar to flappy bird, with some of the basics like procedural generation and infinite scrolling, so on so forth. I am not familiar with Swift, so I can't really explain the mechanics of the game yet, but I'll link the course page below:

Make School - iOS Game Development - Obstacles

In this section, the course focuses on procedurally generating obstacles from a source obstacle. Here's the code:

import SpriteKit
import GameplayKit

class GameScene: SKScene {

    var hero: SKSpriteNode!
    var scrollLayer: SKNode!
    var sinceTouch : CFTimeInterval = 0
    var spawnTimer: CFTimeInterval = 0
    let fixedDelta: CFTimeInterval = 1.0 / 60.0 /* 60 FPS */
    let scrollSpeed: CGFloat = 100
    var obstacleSource: SKNode!
    var obstacleLayer: SKNode!

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

        /* Recursive node search for 'hero' (child of referenced node) */
        hero = (self.childNode(withName: "//hero") as! SKSpriteNode)

        /* Set reference to scroll layer node */
        scrollLayer = self.childNode(withName: "scrollLayer")

        /* Set reference to obstacle layer node */
        obstacleLayer = self.childNode(withName: "obstacleLayer")

        /* allows the hero to animate when it's in the GameScene */
        hero.isPaused = false

        /* Set reference to obstacle Source node */
        obstacleSource = self.childNode(withName: "obstacle")
    }

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

        /* Apply vertical impulse */
        hero.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 300))

         /* Apply subtle rotation */
         hero.physicsBody?.applyAngularImpulse(1)

         /* Reset touch timer */
         sinceTouch = 0
    }

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

        /* Grab current velocity */
        let velocityY = hero.physicsBody?.velocity.dy ?? 0

        /* Check and cap vertical velocity */
        if velocityY > 400 {
          hero.physicsBody?.velocity.dy = 400
        }

        /* Apply falling rotation */
        if sinceTouch > 0.2 {
            let impulse = -20000 * fixedDelta
            hero.physicsBody?.applyAngularImpulse(CGFloat(impulse))
        }

        /* Clamp rotation */
        hero.zRotation.clamp(v1: CGFloat(-90).degreesToRadians(), CGFloat(30).degreesToRadians())
        hero.physicsBody?.angularVelocity.clamp(v1: -1, 3)

        /* Update last touch timer */
        sinceTouch += fixedDelta

        /* Process world scrolling */
        scrollWorld()

        /* Process obstacle scrolling*/
        updateObstacles()

        spawnTimer+=fixedDelta
    }

    func scrollWorld() {
        /* Scroll World */
        scrollLayer.position.x -= scrollSpeed * CGFloat(fixedDelta)

        /* Loop through scroll layer nodes */
        for ground in scrollLayer.children as! [SKSpriteNode] {

            /* Get ground node position, convert node position to scene space */
            let groundPosition = scrollLayer.convert(ground.position, to: self)

            /* Check if ground sprite has left the scene */
            if groundPosition.x <= -ground.size.width / 2 {

                    /* Reposition ground sprite to the second starting position */
                    let newPosition = CGPoint(x: (self.size.width / 2) + ground.size.width, y: groundPosition.y)

                    /* Convert new node position back to scroll layer space */
                    ground.position = self.convert(newPosition, to: scrollLayer)
            }
        }
    }

    func updateObstacles() {
      /* Update Obstacles */

      obstacleLayer.position.x -= scrollSpeed * CGFloat(fixedDelta)

      /* Loop through obstacle layer nodes */
      for obstacle in obstacleLayer.children as! [SKReferenceNode] {

          /* Get obstacle node position, convert node position to scene space */
          let obstaclePosition = obstacleLayer.convert(obstacle.position, to: self)

          /* Check if obstacle has left the scene */
          if obstaclePosition.x <= -26 {
          // 26 is one half the width of an obstacle

              /* Remove obstacle node from obstacle layer */
              obstacle.removeFromParent()
          }

      }

        /* Time to add a new obstacle? */
        if spawnTimer >= 1.5 {

            /* Create a new obstacle by copying the source obstacle */
            let newObstacle = obstacleSource.copy() as! SKNode
            obstacleLayer.addChild(newObstacle)

            /* Generate new obstacle position, start just outside screen and with a random y value */
            let randomPosition =  CGPoint(x: 347, y: CGFloat.random(in: 234...382))

            /* Convert new node position back to obstacle layer space */
            newObstacle.position = self.convert(randomPosition, to: obstacleLayer)

            // Reset spawn timer
            spawnTimer = 0
        }
    }
}

When I run this code in Xcode, the problem shows up in the line let newObstacle = obstacleSource.copy() as! SKNode

I checked the code a few times and even copy-pasted from the course-page, yet I still can't figure out the problem

Edit 1: Added screenshot of error, and hierarchy. enter image description here

Edit 2: Added extra resources.

Here's a link to a screen recording

Here's a link to the whole file

Ishaan Masil
  • 65
  • 1
  • 9

2 Answers2

0

Check for the nil

let newObstacle = obstacleSource.copy() as! SKNode

->

if let newObstacle = obstacleSource.copy() as? SKNode {
    //newObstacle is SKNode
}
Supran Jowti
  • 371
  • 2
  • 9
  • I don't quite get what you mean by. check for the nil. Could you expand on this? – Ishaan Masil Jun 05 '20 at 06:15
  • you do not need to force to cast the value type. your code is crashing because you are forced casting the obstacleSource.copy(). There can be two types of problems. either obstacleSource is empty. (obstacleSource == nil) or obstacleSource.copy() does not return SKNode The above condition is checking the data type of your returns to prevent the crush. – Supran Jowti Jun 05 '20 at 06:30
0

This error (Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value) could be happening for a whole lot of reasons when you're trying to access/unwrap a value that is nil.

In your case, this is happening because obstacleSource variable is nil.

Which in turns makes your app crash when you try to access it via obstacleSource.copy() as? SKNode.

Since you initialize your obstacleSource variable like so:

obstacleSource = self.childNode(withName: "obstacle")

It means that the child node with the name "obstacle" can't be found in your scene, so you should simply check your GameScene.sks file and make sure your node is properly named ("obstacle").

And it would work as expected.

Now let's say that for any reasons you're not sure that your node obstacle actually exist, then in that case instead of declaring your node as an implicitly unwrapped optional:

var obstacleSource: SKNode!

You should initialize it as an optional like so:

var obstacleSource: SKNode?

In that case, because it's declared as an optional, your app won't crash and you can make sure your variable actually holds a value by using optional binding:

if let obstacleSource = self.obstacleSource {
   // Access your node here
} else {
   // The node 'obstacle' could not be found
}

Edit: Thanks for adding a screenshot in your question! The reason why the app crashes is because your node name doesn't match. You should simply rename the referencing node "obstacle" since that's how you're accessing it in your code, and it should work just fine!

  • I've added an edit to the post, with a picture of the error and the GameScene with hierarchy – Ishaan Masil Jun 05 '20 at 06:18
  • Did you check whether the node name in your GameScene.sks is correct? –  Jun 05 '20 at 06:27
  • There is a separate scene called obstacle.sks, which I dragged into the GameScene, as you can see in the screenshot its name is SKEditorReferenceNode obstacle. Could that be the problem, should. I rename it? – Ishaan Masil Jun 05 '20 at 07:29
  • I just saw your edit. Yes that's definitely the reason why the crash is happening! Try renaming the node "obstacle" since that's how you're accessing it in your code. I updated my answer. –  Jun 05 '20 at 08:13
  • I tried renaming it to obstacle, and followed the instructions to the dot, but it still shows the same error. – Ishaan Masil Jun 05 '20 at 09:04
  • Here's a link to a screen recording: https://drive.google.com/file/d/1neqbTyxCtsfEt1CDDTNmeYIx8MifEIwD/view?usp=sharing – Ishaan Masil Jun 05 '20 at 09:08
  • And here's the entire project: https://drive.google.com/drive/folders/1rdFTFiK7oqOHs2cTBUY28bKTkXMtbjmG?usp=sharing – Ishaan Masil Jun 05 '20 at 09:08
  • I tried changing it to optional (replace ! with?) and there. was a build-time error. I included. the files in my question. Do check it out. – Ishaan Masil Jun 05 '20 at 13:40