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!
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.