Thread 1: Swift runtime failure: Unexpectedly found nil while unwrapping an Optional value

In the fps im making, I am now working on mapping the levels. I have already made the walls and provided texture on them, and now I'm doing the same to the floor. I am able to code a red floor, however whenever I try to code texture on it I get the following error:

"Thread 1: Swift runtime failure: Unexpectedly found nil while unwrapping an Optional value"

The code appears at this line in ViewController.swift

return Textures(loader: { name in Bitmap(image: UIImage(named: name)!)!})

Here's the full code for ViewController.swift:

import Engine

private let joystickRadius: Double = 40
private let maximumTimeStep: Double = 1 / 20
private let worldTimeStep: Double = 1 / 120

private func loadTextures() -> Textures
{
  return Textures(loader: { name in Bitmap(image: UIImage(named: name)!)!})
}

private func loadMap() -> Tilemap {
  let jsonURL = Bundle.main.url(forResource: "Map", withExtension: "json")!
  let jsonData = try! Data(contentsOf: jsonURL)
  return try! JSONDecoder().decode(Tilemap.self, from: jsonData)
}

class ViewController: UIViewController {
  private let imageView = UIImageView()
  private let panGesture = UIPanGestureRecognizer()
  private var world = World(map: loadMap())
  private var lastFrameTime = CACurrentMediaTime()
  private let textures = loadTextures()

  override func viewDidLoad() {
    super.viewDidLoad()
    setUpImageView()

    let displayLink = CADisplayLink(target: self, selector: #selector(update))
    displayLink.add(to: .main, forMode: .common)

    view.addGestureRecognizer(panGesture)
  }

  private var inputVector: Vector {
    switch panGesture.state {
    case .began, .changed:
      let translation = panGesture.translation(in: view)
      var vector = Vector(x: Double(translation.x), y: Double(translation.y))
      vector /= max(joystickRadius, vector.length)
      panGesture.setTranslation(CGPoint(
        x: vector.x * joystickRadius,
        y: vector.y * joystickRadius
      ), in: view)
      return vector
    default:
      return Vector(x: 0, y: 0)
    }
  }

  @objc func update(_ displayLink: CADisplayLink) {
    let timeStep = min(maximumTimeStep, displayLink.timestamp - lastFrameTime)
    let inputVector = self.inputVector
    let rotation = inputVector.x * world.player.turningSpeed * worldTimeStep
    let input = Input(speed: -inputVector.y, rotation: Rotation(sine: sin(rotation), cosine: cos(rotation)))
    let worldSteps = (timeStep / worldTimeStep).rounded(.up)
    for _ in 0 ..< Int(worldSteps) {
      world.update(timeStep: timeStep / worldSteps, input: input)
    }
    lastFrameTime = displayLink.timestamp

    let width = Int(imageView.bounds.width), height = Int(imageView.bounds.height)
    var renderer = Renderer(width: width, height: height, textures: textures)
    renderer.draw(world)

    imageView.image = UIImage(bitmap: renderer.bitmap)
  }

  func setUpImageView() {
    view.addSubview(imageView)
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
    imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    imageView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
    imageView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
    imageView.contentMode = .scaleAspectFit
    imageView.backgroundColor = .black
    imageView.layer.magnificationFilter = .nearest
  }
}

Here's the code for other files that deal with the texturing of the floor and the level in general:

Textures.swift

{
  case wall, wall2
  case floor, ceiling
}

public struct Textures
{
  private let textures: [Texture: Bitmap]
}

public extension Textures
{
  init(loader: (String) -> Bitmap)
  {
    var textures = [Texture: Bitmap]()
    for texture in Texture.allCases
    {
      textures[texture] = loader(texture.rawValue)
    }
    self.init(textures: textures)
  }
  subscript(_ texture: Texture) -> Bitmap
  {
    return textures[texture]!
  }
}

Renderer.swift:

  public private(set) var bitmap: Bitmap
  private let textures: Textures

  public init(width: Int, height: Int, textures: Textures) {
    self.bitmap = Bitmap(width: width, height: height, color: .black)
    self.textures = textures
  }
}

public extension Renderer {
  mutating func draw(_ world: World) {
    let focalLength = 1.0
    let viewWidth = Double(bitmap.width) / Double(bitmap.height)
    let viewPlane = world.player.direction.orthogonal * viewWidth
    let viewCenter = world.player.position + world.player.direction * focalLength
    let viewStart = viewCenter - viewPlane / 2

    // Cast rays
    let columns = bitmap.width
    let step = viewPlane / Double(columns)
    var columnPosition = viewStart
    for x in 0 ..< columns {
      let rayDirection = columnPosition - world.player.position
      let viewPlaneDistance = rayDirection.length
      let ray = Ray(
        origin: world.player.position,
        direction: rayDirection / viewPlaneDistance
      )
      let end = world.map.hitTest(ray)
      let wallDistance = (end - ray.origin).length
       
      // Draw wall
      let wallHeight = 1.0
      let distanceRatio = viewPlaneDistance / focalLength
      let perpendicular = wallDistance / distanceRatio
      let height = wallHeight * focalLength / perpendicular * Double(bitmap.height)
      let wallTexture: Bitmap
      let wallX: Double
      if end.x.rounded(.down) == end.x
      {
        wallTexture = textures[.wall]
        wallX = end.y - end.y.rounded(.down)
      }
      else
      {
        wallTexture = textures[.wall2]
        wallX = end.x - end.x.rounded(.down)
      }
      let textureX = Int(wallX * Double(wallTexture.width))
      let wallStart = Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2 - 0.001)
      bitmap.drawColumn(textureX, of: wallTexture, at: wallStart, height: height)
       
      // Draw floor
      let floorTexture = textures[.floor]
      let floorStart = Int(wallStart.y + height) + 1
      for y in min(floorStart, bitmap.height) ..< bitmap.height {
        let normalizedY = (Double(y) / Double(bitmap.height)) * 2 - 1
        let perpendicular = wallHeight * focalLength / normalizedY
        let distance = perpendicular * distanceRatio
        let mapPosition = ray.origin + ray.direction * distance
        let tileX = mapPosition.x.rounded(.down), tileY = mapPosition.y.rounded(.down)
        let textureX = Int((mapPosition.x - tileX) * Double(floorTexture.width))
        let textureY = Int((mapPosition.y - tileY) * Double(floorTexture.height))
        bitmap[x, y] = floorTexture[textureX, textureY]
      }
      columnPosition += step
    }
  }
}

The rest of the code can be found in the link below: https://github.com/KingDecorpel12/RampageFPS/tree/main/RetroRampage/Source

Any and all help will be greatly appreciated!

  • If the line is causing Unexpectedly found nil, UIImage(named: name) is nil or Bitmap(image: UIImage(named: name)!) is nil. Have you checked which is returning nil?

  • I haven't been able to find out which is returning nil. Whenever I run the code and look at the Debug navigator it says the following for the line that the error appears in:

    "closure #1 in loadTextures()" "loadTextures() [inlined]"

Add a Comment

Accepted Reply

look at the Debug navigator

Debug Navigator is quite useless when finding which is being nil and you may need to modify your code:

private func loadTextures() -> Textures {
    return Textures(loader: { name in
        guard let image = UIImage(named: name) else {
            fatalError("UIImage for \(name) cannot be created")
        }
        guard let bitmap = Bitmap(image: image) else {
            fatalError("Bitmap for \(name) (image: \(image)) cannot be created")
        }
        return bitmap
    })
}

Generally, you use toooo.... many forced unwrappings (!) in your code. I recommend you to learn safe ways to work with Optionals.

  • I appreciate it the code you provided worked thanks for the help.

Add a Comment

Replies

I haven't been able to find out which is returning nil. Whenever I run the code and look at the Debug navigator it says the following for the line that the error appears in:

"closure #1 in loadTextures()" "loadTextures() [inlined]"

look at the Debug navigator

Debug Navigator is quite useless when finding which is being nil and you may need to modify your code:

private func loadTextures() -> Textures {
    return Textures(loader: { name in
        guard let image = UIImage(named: name) else {
            fatalError("UIImage for \(name) cannot be created")
        }
        guard let bitmap = Bitmap(image: image) else {
            fatalError("Bitmap for \(name) (image: \(image)) cannot be created")
        }
        return bitmap
    })
}

Generally, you use toooo.... many forced unwrappings (!) in your code. I recommend you to learn safe ways to work with Optionals.

  • I appreciate it the code you provided worked thanks for the help.

Add a Comment