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)
isnil
orBitmap(image: UIImage(named: name)!)
isnil
. Have you checked which is returningnil
?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]"