diff --git a/Source/Rampage.xcodeproj/project.pbxproj b/Source/Rampage.xcodeproj/project.pbxproj index 1a78709..7b15af6 100644 --- a/Source/Rampage.xcodeproj/project.pbxproj +++ b/Source/Rampage.xcodeproj/project.pbxproj @@ -64,6 +64,7 @@ 01D0F5D922F80E1600682CA1 /* RampageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D0F5D822F80E1600682CA1 /* RampageTests.swift */; }; 01D0F5F122FF095E00682CA1 /* Door.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D0F5F022FF095E00682CA1 /* Door.swift */; }; 01E3963A2342758D00D02236 /* Pushwall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E396392342758D00D02236 /* Pushwall.swift */; }; + 01D0F5EF22FDF19A00682CA1 /* Bitmap+Rendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D0F5EE22FDF19900682CA1 /* Bitmap+Rendering.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -174,6 +175,7 @@ 01D0F5DA22F80E1600682CA1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 01D0F5F022FF095E00682CA1 /* Door.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Door.swift; sourceTree = ""; }; 01E396392342758D00D02236 /* Pushwall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pushwall.swift; sourceTree = ""; }; + 01D0F5EE22FDF19900682CA1 /* Bitmap+Rendering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bitmap+Rendering.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -256,6 +258,7 @@ children = ( 016E41B2228E9A5B00ACF137 /* AppDelegate.swift */, 016E41B4228E9A5B00ACF137 /* ViewController.swift */, + 01D0F5EE22FDF19900682CA1 /* Bitmap+Rendering.swift */, 01D09AF422A482450052745A /* UIImage+Bitmap.swift */, 0199F57323E242D4003E3F08 /* SoundManager.swift */, 016E41B6228E9A5B00ACF137 /* Main.storyboard */, @@ -544,6 +547,7 @@ files = ( 01D09AF522A482450052745A /* UIImage+Bitmap.swift in Sources */, 0199F57423E242D4003E3F08 /* SoundManager.swift in Sources */, + 01D0F5EF22FDF19A00682CA1 /* Bitmap+Rendering.swift in Sources */, 016E41B5228E9A5B00ACF137 /* ViewController.swift in Sources */, 016E41B3228E9A5B00ACF137 /* AppDelegate.swift in Sources */, ); diff --git a/Source/Rampage.xcodeproj/xcshareddata/xcschemes/Rampage.xcscheme b/Source/Rampage.xcodeproj/xcshareddata/xcschemes/Rampage.xcscheme index ce33339..edd97b6 100644 --- a/Source/Rampage.xcodeproj/xcshareddata/xcschemes/Rampage.xcscheme +++ b/Source/Rampage.xcodeproj/xcshareddata/xcschemes/Rampage.xcscheme @@ -50,7 +50,7 @@ private let textures: Textures public var safeArea: Rect - public init(width: Int, height: Int, textures: Textures) { - self.bitmap = Bitmap(width: width, height: height, color: .black) + public init(width: Int, height: Int, range: Range, textures: Textures) { + self.width = width + self.range = range + let columns = Int(Double(width) * (range.upperBound - range.lowerBound)) + self.bitmap = Bitmap(width: columns, height: height, color: .clear) self.textures = textures self.safeArea = Rect(min: Vector(x: 0, y: 0), max: bitmap.size) } @@ -25,16 +30,15 @@ public struct Renderer { public extension Renderer { mutating func draw(_ world: World) { let focalLength = 1.0 - let viewWidth = Double(bitmap.width) / Double(bitmap.height) + let viewWidth = Double(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 + let viewStart = viewCenter - viewPlane * (0.5 - range.lowerBound) // Cast rays - let columns = bitmap.width - let step = viewPlane / Double(columns) + let step = viewPlane / Double(width) var columnPosition = viewStart - for x in 0 ..< columns { + for x in 0 ..< bitmap.width { let rayDirection = columnPosition - world.player.position let viewPlaneDistance = rayDirection.length let ray = Ray( @@ -132,7 +136,7 @@ public extension Renderer { let weaponWidth = screenHeight * aspectRatio bitmap.drawImage( weaponTexture, - at: Vector(x: Double(bitmap.width) / 2 - weaponWidth / 2, y: 0), + at: Vector(x: Double(width) * (0.5 - range.lowerBound) - weaponWidth / 2, y: 0), size: Vector(x: weaponWidth, y: screenHeight) ) @@ -140,56 +144,65 @@ public extension Renderer { let crosshair = textures[.crosshair] let hudScale = bitmap.size.y / 64 let crosshairSize = crosshair.size * hudScale - bitmap.drawImage(crosshair, at: (bitmap.size - crosshairSize) / 2, size: crosshairSize) - - // Health icon - let healthIcon = textures[.healthIcon] - var offset = safeArea.min + Vector(x: 1, y: 1) * hudScale - bitmap.drawImage(healthIcon, at: offset, size: healthIcon.size * hudScale) - offset.x += healthIcon.size.x * hudScale + bitmap.drawImage( + crosshair, + at: Vector(x: Double(width) * (0.5 - range.lowerBound) - crosshairSize.x / 2, + y: (bitmap.size.y - crosshairSize.y) / 2), + size: crosshairSize) - // Health let font = textures[.font] let charSize = Vector(x: font.size.x / 10, y: font.size.y) - let health = Int(max(0, world.player.health)) - let healthTint: Color - switch health { - case ...10: - healthTint = .red - case 10 ... 30: - healthTint = .yellow - default: - healthTint = .green - } - for char in String(health) { - let index = Int(char.asciiValue!) - 48 - let step = Int(charSize.x) - let xRange = index * step ..< (index + 1) * step - bitmap.drawImage( - font, - xRange: xRange, - at: offset, - size: charSize * hudScale, - tint: healthTint - ) - offset.x += charSize.x * hudScale - } + var offset = safeArea.min + Vector(x: 1, y: 1) * hudScale - // Ammunition - offset.x = safeArea.max.x - let ammo = Int(max(0, min(99, world.player.ammo))) - for char in String(ammo).reversed() { - let index = Int(char.asciiValue!) - 48 - let step = Int(charSize.x) - let xRange = index * step ..< (index + 1) * step - offset.x -= charSize.x * hudScale - bitmap.drawImage(font, xRange: xRange, at: offset, size: charSize * hudScale) + if range.lowerBound == 0 { + // Health icon + let healthIcon = textures[.healthIcon] + bitmap.drawImage(healthIcon, at: offset, size: healthIcon.size * hudScale) + offset.x += healthIcon.size.x * hudScale + + // Health + let health = Int(max(0, world.player.health)) + let healthTint: Color + switch health { + case ...10: + healthTint = .red + case 10 ... 30: + healthTint = .yellow + default: + healthTint = .green + } + for char in String(health) { + let index = Int(char.asciiValue!) - 48 + let step = Int(charSize.x) + let xRange = index * step ..< (index + 1) * step + bitmap.drawImage( + font, + xRange: xRange, + at: offset, + size: charSize * hudScale, + tint: healthTint + ) + offset.x += charSize.x * hudScale + } } - // Weapon icon - let weaponIcon = textures[world.player.weapon.attributes.hudIcon] - offset.x -= weaponIcon.size.x * hudScale - bitmap.drawImage(weaponIcon, at: offset, size: weaponIcon.size * hudScale) + if range.upperBound == 1 { + // Ammunition + offset.x = safeArea.max.x - range.lowerBound * Double(width) + let ammo = Int(max(0, min(99, world.player.ammo))) + for char in String(ammo).reversed() { + let index = Int(char.asciiValue!) - 48 + let step = Int(charSize.x) + let xRange = index * step ..< (index + 1) * step + offset.x -= charSize.x * hudScale + bitmap.drawImage(font, xRange: xRange, at: offset, size: charSize * hudScale) + } + + // Weapon icon + let weaponIcon = textures[world.player.weapon.attributes.hudIcon] + offset.x -= weaponIcon.size.x * hudScale + bitmap.drawImage(weaponIcon, at: offset, size: weaponIcon.size * hudScale) + } // Effects for effect in world.effects {