Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Part 17
Browse files Browse the repository at this point in the history
nicklockwood committed Apr 25, 2020
1 parent c6b3edf commit de28dd9
Showing 16 changed files with 337 additions and 96 deletions.
12 changes: 12 additions & 0 deletions Source/Engine/Font.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// Font.swift
// Engine
//
// Created by Nick Lockwood on 21/04/2020.
// Copyright © 2020 Nick Lockwood. All rights reserved.
//

public struct Font: Decodable {
public let texture: Texture
public let characters: [String]
}
78 changes: 78 additions & 0 deletions Source/Engine/Game.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// Game.swift
// Engine
//
// Created by Nick Lockwood on 07/10/2019.
// Copyright © 2019 Nick Lockwood. All rights reserved.
//

public protocol GameDelegate: AnyObject {
func playSound(_ sound: Sound)
func clearSounds()
}

public enum GameState {
case title
case starting
case playing
}

public struct Game {
public weak var delegate: GameDelegate?
public let levels: [Tilemap]
public private(set) var world: World
public private(set) var state: GameState
public private(set) var transition: Effect?
public let font: Font
public var titleText = "TAP TO START"

public init(levels: [Tilemap], font: Font) {
self.state = .title
self.levels = levels
self.world = World(map: levels[0])
self.font = font
}
}

public extension Game {
var hud: HUD {
return HUD(player: world.player, font: font)
}

mutating func update(timeStep: Double, input: Input) {
guard let delegate = delegate else {
return
}

// Update transition
if var effect = transition {
effect.time += timeStep
transition = effect
}

// Update state
switch state {
case .title:
if input.isFiring {
transition = Effect(type: .fadeOut, color: .black, duration: 0.5)
state = .starting
}
case .starting:
if transition?.isCompleted == true {
transition = Effect(type: .fadeIn, color: .black, duration: 0.5)
state = .playing
}
case .playing:
if let action = world.update(timeStep: timeStep, input: input) {
switch action {
case .loadLevel(let index):
let index = index % levels.count
world.setLevel(levels[index])
delegate.clearSounds()
case .playSounds(let sounds):
sounds.forEach(delegate.playSound)
}
}
}
}
}
33 changes: 33 additions & 0 deletions Source/Engine/HUD.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// HUD.swift
// Engine
//
// Created by Nick Lockwood on 19/04/2020.
// Copyright © 2020 Nick Lockwood. All rights reserved.
//

public struct HUD {
public let healthString: String
public let healthTint: Color
public let ammoString: String
public let playerWeapon: Texture
public let weaponIcon: Texture
public let font: Font

public init(player: Player, font: Font) {
let health = Int(max(0, player.health))
switch health {
case ...10:
self.healthTint = .red
case 10 ... 30:
self.healthTint = .yellow
default:
self.healthTint = .green
}
self.healthString = String(health)
self.ammoString = String(Int(max(0, min(99, player.ammo))))
self.playerWeapon = player.animation.texture
self.weaponIcon = player.weapon.attributes.hudIcon
self.font = font
}
}
1 change: 1 addition & 0 deletions Source/Engine/Texture.swift
Original file line number Diff line number Diff line change
@@ -32,4 +32,5 @@ public enum Texture: String, CaseIterable, Codable {
case healthIcon
case pistolIcon, shotgunIcon
case font
case titleBackground, titleLogo
}
45 changes: 31 additions & 14 deletions Source/Rampage.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@
013D492523ED607D00763FCA /* medkit.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 013D492423ED607D00763FCA /* medkit.mp3 */; };
013D492723EE17C000763FCA /* Weapon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 013D492623EE17C000763FCA /* Weapon.swift */; };
01467C3E22E6F54600B5607D /* Easing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01467C3D22E6F54600B5607D /* Easing.swift */; };
01557AD0245109E600FF8FF0 /* HUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01557ACF245109E600FF8FF0 /* HUD.swift */; };
0159A3F523DEF636001EEB81 /* Pickup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0159A3F423DEF636001EEB81 /* Pickup.swift */; };
015A23C9230586E3004CBB78 /* Switch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 015A23C8230586E3004CBB78 /* Switch.swift */; };
016E41B3228E9A5B00ACF137 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016E41B2228E9A5B00ACF137 /* AppDelegate.swift */; };
@@ -63,7 +64,10 @@
01D09B0B22A7F7570052745A /* Texture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D09B0A22A7F7570052745A /* Texture.swift */; };
01D0F5D922F80E1600682CA1 /* RampageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D0F5D822F80E1600682CA1 /* RampageTests.swift */; };
01D0F5F122FF095E00682CA1 /* Door.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D0F5F022FF095E00682CA1 /* Door.swift */; };
01DD25AA244FA74900D00FE5 /* Font.json in Resources */ = {isa = PBXBuildFile; fileRef = 01DD25A9244FA74900D00FE5 /* Font.json */; };
01DD25AC244FA85E00D00FE5 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DD25AB244FA85E00D00FE5 /* Font.swift */; };
01E3963A2342758D00D02236 /* Pushwall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E396392342758D00D02236 /* Pushwall.swift */; };
01EDA5DB2444DC2C00FC1795 /* Game.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EDA5DA2444DC2B00FC1795 /* Game.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
@@ -129,6 +133,7 @@
013D492423ED607D00763FCA /* medkit.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = medkit.mp3; sourceTree = "<group>"; };
013D492623EE17C000763FCA /* Weapon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weapon.swift; sourceTree = "<group>"; };
01467C3D22E6F54600B5607D /* Easing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Easing.swift; sourceTree = "<group>"; };
01557ACF245109E600FF8FF0 /* HUD.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HUD.swift; sourceTree = "<group>"; };
0159A3F423DEF636001EEB81 /* Pickup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pickup.swift; sourceTree = "<group>"; };
015A23C8230586E3004CBB78 /* Switch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Switch.swift; sourceTree = "<group>"; };
016E41AF228E9A5B00ACF137 /* Rampage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rampage.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -173,7 +178,10 @@
01D0F5D822F80E1600682CA1 /* RampageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RampageTests.swift; sourceTree = "<group>"; };
01D0F5DA22F80E1600682CA1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
01D0F5F022FF095E00682CA1 /* Door.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Door.swift; sourceTree = "<group>"; };
01DD25A9244FA74900D00FE5 /* Font.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Font.json; sourceTree = "<group>"; };
01DD25AB244FA85E00D00FE5 /* Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = "<group>"; };
01E396392342758D00D02236 /* Pushwall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pushwall.swift; sourceTree = "<group>"; };
01EDA5DA2444DC2B00FC1795 /* Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Game.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
@@ -260,6 +268,7 @@
0199F57323E242D4003E3F08 /* SoundManager.swift */,
016E41B6228E9A5B00ACF137 /* Main.storyboard */,
01D09B0022A493A70052745A /* Levels.json */,
01DD25A9244FA74900D00FE5 /* Font.json */,
016E41B9228E9A5E00ACF137 /* Assets.xcassets */,
0199F55423E1A517003E3F08 /* Sounds */,
016E41BB228E9A5E00ACF137 /* LaunchScreen.storyboard */,
@@ -278,6 +287,9 @@
01D0F5F022FF095E00682CA1 /* Door.swift */,
01467C3D22E6F54600B5607D /* Easing.swift */,
012DF10722E251CF00D52706 /* Effect.swift */,
01DD25AB244FA85E00D00FE5 /* Font.swift */,
01EDA5DA2444DC2B00FC1795 /* Game.swift */,
01557ACF245109E600FF8FF0 /* HUD.swift */,
01D09B0222A4958E0052745A /* Input.swift */,
012A0C6122CC200D0068E8EF /* Monster.swift */,
0108A66E23F543740075E1AF /* Pathfinder.swift */,
@@ -436,21 +448,24 @@
attributes = {
DefaultBuildSystemTypeForWorkspace = Original;
LastSwiftUpdateCheck = 1010;
LastUpgradeCheck = 1010;
LastUpgradeCheck = 1130;
ORGANIZATIONNAME = "Nick Lockwood";
TargetAttributes = {
0108A65623F4D84C0075E1AF = {
CreatedOnToolsVersion = 11.3.1;
LastSwiftMigration = 1130;
};
016E41AE228E9A5B00ACF137 = {
CreatedOnToolsVersion = 10.1;
LastSwiftMigration = 1130;
};
016E41C8228E9A8600ACF137 = {
CreatedOnToolsVersion = 10.1;
LastSwiftMigration = 1010;
LastSwiftMigration = 1130;
};
01D0F5D522F80E1600682CA1 = {
CreatedOnToolsVersion = 10.1;
LastSwiftMigration = 1130;
TestTargetID = 016E41AE228E9A5B00ACF137;
};
};
@@ -489,6 +504,7 @@
buildActionMask = 2147483647;
files = (
0128F26223EEE7AE00439050 /* shotgunFire.mp3 in Resources */,
01DD25AA244FA74900D00FE5 /* Font.json in Resources */,
0199F56E23E1A517003E3F08 /* wallThud.mp3 in Resources */,
0199F56623E1A517003E3F08 /* playerWalk.mp3 in Resources */,
0199F56C23E1A517003E3F08 /* monsterGroan.mp3 in Resources */,
@@ -556,6 +572,7 @@
012DF10822E251CF00D52706 /* Effect.swift in Sources */,
012A0C6322CC200E0068E8EF /* Monster.swift in Sources */,
01D09B0B22A7F7570052745A /* Texture.swift in Sources */,
01EDA5DB2444DC2C00FC1795 /* Game.swift in Sources */,
0159A3F523DEF636001EEB81 /* Pickup.swift in Sources */,
015A23C9230586E3004CBB78 /* Switch.swift in Sources */,
01D09AFF22A48E990052745A /* Tilemap.swift in Sources */,
@@ -567,6 +584,7 @@
01467C3E22E6F54600B5607D /* Easing.swift in Sources */,
01D09B0722A6E09B0052745A /* Rotation.swift in Sources */,
01D09AFB22A485040052745A /* Player.swift in Sources */,
01557AD0245109E600FF8FF0 /* HUD.swift in Sources */,
012A0C6222CC200E0068E8EF /* Billboard.swift in Sources */,
013D492723EE17C000763FCA /* Weapon.swift in Sources */,
01ADC64022B9846B00DC8AAD /* World.swift in Sources */,
@@ -575,6 +593,7 @@
01D0F5F122FF095E00682CA1 /* Door.swift in Sources */,
0108A66F23F543750075E1AF /* Pathfinder.swift in Sources */,
01D09B0322A4958E0052745A /* Input.swift in Sources */,
01DD25AC244FA85E00D00FE5 /* Font.swift in Sources */,
012A0CA222D7AD0A0068E8EF /* Animation.swift in Sources */,
01D09B0522A5C9DB0052745A /* Ray.swift in Sources */,
01E3963A2342758D00D02236 /* Pushwall.swift in Sources */,
@@ -646,7 +665,6 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Renderer/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -657,7 +675,7 @@
SKIP_INSTALL = YES;
SWIFT_DISABLE_SAFETY_CHECKS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -676,7 +694,6 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Renderer/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -686,7 +703,7 @@
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_DISABLE_SAFETY_CHECKS = YES;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -744,7 +761,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -799,7 +816,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.1;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
@@ -823,7 +840,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.Rampage;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -842,7 +859,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.Rampage;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@@ -871,7 +888,7 @@
SKIP_INSTALL = YES;
SWIFT_DISABLE_SAFETY_CHECKS = NO;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -902,7 +919,7 @@
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_DISABLE_SAFETY_CHECKS = YES;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
@@ -923,7 +940,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.RampageTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Rampage.app/Rampage";
};
@@ -943,7 +960,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.RampageTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Rampage.app/Rampage";
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -29,8 +29,6 @@
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -51,8 +49,6 @@
ReferencedContainer = "container:Rampage.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Binary file modified Source/Rampage/Assets.xcassets/font.imageset/font.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "titleBackground.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions Source/Rampage/Assets.xcassets/titleLogo.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "titleLogo.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions Source/Rampage/Font.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"texture": "font",
"characters": [
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
"K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
"U", "V", "W", "X", "Y", "Z", " "
]
}
2 changes: 2 additions & 0 deletions Source/Rampage/Info.plist
Original file line number Diff line number Diff line change
@@ -28,6 +28,8 @@
<array>
<string>armv7</string>
</array>
<key>UIRequiresFullScreen</key>
<false/>
<key>UIStatusBarHidden</key>
<true/>
<key>UISupportedInterfaceOrientations</key>
77 changes: 45 additions & 32 deletions Source/Rampage/ViewController.swift
Original file line number Diff line number Diff line change
@@ -22,6 +22,12 @@ public func loadLevels() -> [Tilemap] {
return levels.enumerated().map { Tilemap($0.element, index: $0.offset) }
}

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

public func loadTextures() -> Textures {
return Textures(loader: { name in
Bitmap(image: UIImage(named: name)!)!
@@ -47,11 +53,10 @@ class ViewController: UIViewController {
private let panGesture = UIPanGestureRecognizer()
private let tapGesture = UITapGestureRecognizer()
private let textures = loadTextures()
private let levels = loadLevels()
private lazy var world = World(map: levels[0])
private var game = Game(levels: loadLevels(), font: loadFont())
private var lastFrameTime = CACurrentMediaTime()
private var lastFiredTime = 0.0

override func viewDidLoad() {
super.viewDidLoad()

@@ -72,6 +77,8 @@ class ViewController: UIViewController {
tapGesture.addTarget(self, action: #selector(fire))
tapGesture.delegate = self

game.delegate = self

try? restoreState()
NotificationCenter.default.addObserver(
forName: UIApplication.willResignActiveNotification,
@@ -86,6 +93,10 @@ class ViewController: UIViewController {
return true
}

override var prefersHomeIndicatorAutoHidden: Bool {
return true
}

private var inputVector: Vector {
switch panGesture.state {
case .began, .changed:
@@ -105,41 +116,20 @@ class ViewController: UIViewController {
@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(
let rotation = inputVector.x * game.world.player.turningSpeed * worldTimeStep
var input = Input(
speed: -inputVector.y,
rotation: Rotation(sine: sin(rotation), cosine: cos(rotation)),
isFiring: lastFiredTime > lastFrameTime
)
lastFrameTime = displayLink.timestamp
lastFiredTime = min(lastFiredTime, lastFrameTime)

let worldSteps = (timeStep / worldTimeStep).rounded(.up)
for _ in 0 ..< Int(worldSteps) {
if let action = world.update(timeStep: timeStep / worldSteps, input: input) {
switch action {
case .loadLevel(let index):
SoundManager.shared.clearAll()
let index = index % levels.count
world.setLevel(levels[index])
case .playSounds(let sounds):
for sound in sounds {
DispatchQueue.main.asyncAfter(deadline: .now() + sound.delay) {
guard let url = sound.name?.url else {
if let channel = sound.channel {
SoundManager.shared.clearChannel(channel)
}
return
}
try? SoundManager.shared.play(
url,
channel: sound.channel,
volume: sound.volume,
pan: sound.pan
)
}
}
}
}
game.update(timeStep: timeStep / worldSteps, input: input)
input.isFiring = false
}
lastFrameTime = displayLink.timestamp

let width = Int(imageView.bounds.width), height = Int(imageView.bounds.height)
var renderer = Renderer(width: width, height: height, textures: textures)
@@ -148,7 +138,7 @@ class ViewController: UIViewController {
min: Vector(x: Double(insets.left), y: Double(insets.top)),
max: renderer.bitmap.size - Vector(x: Double(insets.left), y: Double(insets.bottom))
)
renderer.draw(world)
renderer.draw(game)

imageView.image = UIImage(bitmap: renderer.bitmap)
}
@@ -190,3 +180,26 @@ extension ViewController: UIGestureRecognizerDelegate {
return true
}
}

extension ViewController: GameDelegate {
func playSound(_ sound: Sound) {
DispatchQueue.main.asyncAfter(deadline: .now() + sound.delay) {
guard let url = sound.name?.url else {
if let channel = sound.channel {
SoundManager.shared.clearChannel(channel)
}
return
}
try? SoundManager.shared.play(
url,
channel: sound.channel,
volume: sound.volume,
pan: sound.pan
)
}
}

func clearSounds() {
SoundManager.shared.clearAll()
}
}
126 changes: 82 additions & 44 deletions Source/Renderer/Renderer.swift
Original file line number Diff line number Diff line change
@@ -23,6 +23,58 @@ public struct Renderer {
}

public extension Renderer {
mutating func draw(_ game: Game) {
switch game.state {
case .title, .starting:
// Background
let background = textures[.titleBackground]
let backgroundScale = bitmap.size.y / background.size.y
let backgroundSize = background.size * backgroundScale
let backgroundPosition = (bitmap.size - backgroundSize) / 2
bitmap.drawImage(background, at: backgroundPosition, size: backgroundSize)

// Logo
let logo = textures[.titleLogo]
let logoScale = bitmap.size.y / logo.size.y / 2
let logoSize = logo.size * logoScale
let logoPosition = Vector(x: (bitmap.size.x - logoSize.x) / 2, y: bitmap.size.y * 0.15)
bitmap.drawImage(logo, at: logoPosition, size: logoSize)

// Text
let textScale = bitmap.size.y / 64
let font = textures[game.font.texture]
let charSize = Vector(x: Double(font.width / game.font.characters.count), y: font.size.y)
let textWidth = charSize.x * Double(game.titleText.count) * textScale
var offset = Vector(x: (bitmap.size.x - textWidth) / 2, y: bitmap.size.y * 0.75)
for char in game.titleText {
let index = game.font.characters.firstIndex(of: String(char)) ?? 0
let step = Int(charSize.x)
let xRange = index * step ..< (index + 1) * step
bitmap.drawImage(
font,
xRange: xRange,
at: offset,
size: charSize * textScale,
tint: .yellow
)
offset.x += charSize.x * textScale
}
case .playing:
draw(game.world)
draw(game.hud)

// Effects
for effect in game.world.effects {
draw(effect)
}
}

// Transition
if let effect = game.transition {
draw(effect)
}
}

mutating func draw(_ world: World) {
let focalLength = 1.0
let viewWidth = Double(bitmap.width) / Double(bitmap.height)
@@ -124,17 +176,14 @@ public extension Renderer {

columnPosition += step
}
}

mutating func draw(_ hud: HUD) {
// Player weapon
let weaponTexture = textures[world.player.animation.texture]
let aspectRatio = Double(weaponTexture.width) / Double(weaponTexture.height)
let screenHeight = Double(bitmap.height)
let weaponWidth = screenHeight * aspectRatio
bitmap.drawImage(
weaponTexture,
at: Vector(x: Double(bitmap.width) / 2 - weaponWidth / 2, y: 0),
size: Vector(x: weaponWidth, y: screenHeight)
)
let weaponTexture = textures[hud.playerWeapon]
let weaponScale = bitmap.size.y / weaponTexture.size.y
let weaponSize = weaponTexture.size * weaponScale
bitmap.drawImage(weaponTexture, at: (bitmap.size - weaponSize) / 2, size: weaponSize)

// Crosshair
let crosshair = textures[.crosshair]
@@ -149,20 +198,11 @@ public extension Renderer {
offset.x += healthIcon.size.x * hudScale

// 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 font = textures[hud.font.texture]
let charSize = Vector(x: Double(font.width / hud.font.characters.count), y: font.size.y)
let healthTint = hud.healthTint
for char in hud.healthString {
let index = hud.font.characters.firstIndex(of: String(char)) ?? 0
let step = Int(charSize.x)
let xRange = index * step ..< (index + 1) * step
bitmap.drawImage(
@@ -177,37 +217,35 @@ public extension Renderer {

// 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
for char in hud.ammoString.reversed() {
let index = hud.font.characters.firstIndex(of: String(char)) ?? 0
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]
let weaponIcon = textures[hud.weaponIcon]
offset.x -= weaponIcon.size.x * hudScale
bitmap.drawImage(weaponIcon, at: offset, size: weaponIcon.size * hudScale)
}

// Effects
for effect in world.effects {
switch effect.type {
case .fadeIn:
bitmap.tint(with: effect.color, opacity: 1 - effect.progress)
case .fadeOut:
bitmap.tint(with: effect.color, opacity: effect.progress)
case .fizzleOut:
let threshold = Int(effect.progress * Double(fizzle.count))
for x in 0 ..< bitmap.width {
for y in 0 ..< bitmap.height {
let granularity = 4
let index = y / granularity * bitmap.width + x / granularity
let fizzledIndex = fizzle[index % fizzle.count]
if fizzledIndex <= threshold {
bitmap[x, y] = effect.color
}
mutating func draw(_ effect: Effect) {
switch effect.type {
case .fadeIn:
bitmap.tint(with: effect.color, opacity: 1 - effect.progress)
case .fadeOut:
bitmap.tint(with: effect.color, opacity: effect.progress)
case .fizzleOut:
let threshold = Int(effect.progress * Double(fizzle.count))
for x in 0 ..< bitmap.width {
for y in 0 ..< bitmap.height {
let granularity = 4
let index = y / granularity * bitmap.width + x / granularity
let fizzledIndex = fizzle[index % fizzle.count]
if fizzledIndex <= threshold {
bitmap[x, y] = effect.color
}
}
}

0 comments on commit de28dd9

Please sign in to comment.