Skip to content

Commit

Permalink
Part 7
Browse files Browse the repository at this point in the history
  • Loading branch information
nicklockwood committed Jul 26, 2019
1 parent a0cfa8b commit 6d3decf
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 22 deletions.
17 changes: 16 additions & 1 deletion Source/Engine/Bitmap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public extension Bitmap {
}
}

mutating func blendPixel(at x: Int, _ y: Int, with newColor: Color) {
private mutating func blendPixel(at x: Int, _ y: Int, with newColor: Color) {
let oldColor = self[x, y]
let inverseAlpha = 1 - Double(newColor.a) / 255
self[x, y] = Color(
Expand All @@ -85,4 +85,19 @@ public extension Bitmap {
b: UInt8(Double(oldColor.b) * inverseAlpha) + newColor.b
)
}

mutating func tint(with color: Color, opacity: Double) {
let alpha = min(1, max(0, Double(color.a) / 255 * opacity))
let color = Color(
r: UInt8(Double(color.r) * alpha),
g: UInt8(Double(color.g) * alpha),
b: UInt8(Double(color.b) * alpha),
a: UInt8(255 * alpha)
)
for y in 0 ..< height {
for x in 0 ..< width {
blendPixel(at: x, y, with: color)
}
}
}
}
31 changes: 31 additions & 0 deletions Source/Engine/Easing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// Easing.swift
// Engine
//
// Created by Nick Lockwood on 23/07/2019.
// Copyright © 2019 Nick Lockwood. All rights reserved.
//

public enum Easing {}

public extension Easing {
static func linear(_ t: Double) -> Double {
return t
}

static func easeIn(_ t: Double) -> Double {
return t * t
}

static func easeOut(_ t: Double) -> Double {
return 1 - easeIn(1 - t)
}

static func easeInEaseOut(_ t: Double) -> Double {
if t < 0.5 {
return 2 * easeIn(t)
} else {
return 4 * t - 2 * easeIn(t) - 1
}
}
}
44 changes: 44 additions & 0 deletions Source/Engine/Effect.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Effect.swift
// Engine
//
// Created by Nick Lockwood on 19/07/2019.
// Copyright © 2019 Nick Lockwood. All rights reserved.
//

public enum EffectType {
case fadeIn
case fadeOut
case fizzleOut
}

public struct Effect {
public let type: EffectType
public let color: Color
public let duration: Double
public var time: Double = 0

public init(type: EffectType, color: Color, duration: Double) {
self.type = type
self.color = color
self.duration = duration
}
}

public extension Effect {
var isCompleted: Bool {
return time >= duration
}

var progress: Double {
let t = min(1, time / duration)
switch type {
case .fadeIn:
return Easing.easeIn(t)
case .fadeOut:
return Easing.easeOut(t)
case .fizzleOut:
return Easing.easeInEaseOut(t)
}
}
}
9 changes: 8 additions & 1 deletion Source/Engine/Monster.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ public struct Monster: Actor {
public var velocity: Vector = Vector(x: 0, y: 0)
public var state: MonsterState = .idle
public var animation: Animation = .monsterIdle
public let attackCooldown: Double = 0.4
public private(set) var lastAttackTime: Double = 0

public init(position: Vector) {
self.position = position
}
}

public extension Monster {
mutating func update(in world: World) {
mutating func update(in world: inout World) {
switch state {
case .idle:
if canSeePlayer(in: world) {
Expand All @@ -43,6 +45,7 @@ public extension Monster {
if canReachPlayer(in: world) {
state = .scratching
animation = .monsterScratch
lastAttackTime = -attackCooldown
}
let direction = world.player.position - position
velocity = direction * (speed / direction.length)
Expand All @@ -52,6 +55,10 @@ public extension Monster {
animation = .monsterWalk
break
}
if animation.time - lastAttackTime >= attackCooldown {
lastAttackTime = animation.time
world.hurtPlayer(10)
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions Source/Engine/Player.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,18 @@ public struct Player: Actor {
public var position: Vector
public var velocity: Vector
public var direction: Vector
public var health: Double

public init(position: Vector) {
self.position = position
self.velocity = Vector(x: 0, y: 0)
self.direction = Vector(x: 1, y: 0)
self.health = 100
}
}

public extension Player {
var isDead: Bool {
return health <= 0
}
}
24 changes: 24 additions & 0 deletions Source/Engine/Renderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
// Copyright © 2019 Nick Lockwood. All rights reserved.
//

private let fizzle = (0 ..< 10000).shuffled()

public struct Renderer {
public private(set) var bitmap: Bitmap
private let textures: Textures
Expand Down Expand Up @@ -108,5 +110,27 @@ public extension Renderer {

columnPosition += step
}

// 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 y in 0 ..< bitmap.height {
for x in 0 ..< bitmap.width {
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
}
}
}
}
}
}
}
76 changes: 56 additions & 20 deletions Source/Engine/World.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,15 @@

public struct World {
public let map: Tilemap
public var monsters: [Monster]
public var player: Player!
public private(set) var monsters: [Monster]
public private(set) var player: Player!
public private(set) var effects: [Effect]

public init(map: Tilemap) {
self.map = map
self.monsters = []
for y in 0 ..< map.height {
for x in 0 ..< map.width {
let position = Vector(x: Double(x) + 0.5, y: Double(y) + 0.5)
let thing = map.things[y * map.width + x]
switch thing {
case .nothing:
break
case .player:
self.player = Player(position: position)
case .monster:
monsters.append(Monster(position: position))
}
}
}
self.effects = []
reset()
}
}

Expand All @@ -37,14 +26,31 @@ public extension World {
}

mutating func update(timeStep: Double, input: Input) {
player.direction = player.direction.rotated(by: input.rotation)
player.velocity = player.direction * input.speed * player.speed
player.position += player.velocity * timeStep
// Update effects
effects = effects.compactMap { effect in
guard effect.time < effect.duration else {
return nil
}
var effect = effect
effect.time += timeStep
return effect
}

// Update player
if player.isDead == false {
player.direction = player.direction.rotated(by: input.rotation)
player.velocity = player.direction * input.speed * player.speed
player.position += player.velocity * timeStep
} else if effects.isEmpty {
reset()
effects.append(Effect(type: .fadeIn, color: .red, duration: 0.5))
return
}

// Update monsters
for i in 0 ..< monsters.count {
var monster = monsters[i]
monster.update(in: self)
monster.update(in: &self)
monster.position += monster.velocity * timeStep
monster.animation.time += timeStep
monsters[i] = monster
Expand Down Expand Up @@ -84,4 +90,34 @@ public extension World {
)
}
}

mutating func hurtPlayer(_ damage: Double) {
if player.isDead {
return
}
player.health -= damage
let color = Color(r: 255, g: 0, b: 0, a: 191)
effects.append(Effect(type: .fadeIn, color: color, duration: 0.2))
if player.isDead {
effects.append(Effect(type: .fizzleOut, color: .red, duration: 2))
}
}

mutating func reset() {
self.monsters = []
for y in 0 ..< map.height {
for x in 0 ..< map.width {
let position = Vector(x: Double(x) + 0.5, y: Double(y) + 0.5)
let thing = map.things[y * map.width + x]
switch thing {
case .nothing:
break
case .player:
self.player = Player(position: position)
case .monster:
monsters.append(Monster(position: position))
}
}
}
}
}
8 changes: 8 additions & 0 deletions Source/Rampage.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
012A0C6322CC200E0068E8EF /* Monster.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A0C6122CC200D0068E8EF /* Monster.swift */; };
012A0C9E22D47C220068E8EF /* Actor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A0C9D22D47C220068E8EF /* Actor.swift */; };
012A0CA222D7AD0A0068E8EF /* Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A0CA122D7AD0A0068E8EF /* Animation.swift */; };
012DF10822E251CF00D52706 /* Effect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012DF10722E251CF00D52706 /* Effect.swift */; };
01467C3E22E6F54600B5607D /* Easing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01467C3D22E6F54600B5607D /* Easing.swift */; };
016E41B3228E9A5B00ACF137 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016E41B2228E9A5B00ACF137 /* AppDelegate.swift */; };
016E41B5228E9A5B00ACF137 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016E41B4228E9A5B00ACF137 /* ViewController.swift */; };
016E41B8228E9A5B00ACF137 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 016E41B6228E9A5B00ACF137 /* Main.storyboard */; };
Expand Down Expand Up @@ -67,6 +69,8 @@
012A0C6122CC200D0068E8EF /* Monster.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Monster.swift; sourceTree = "<group>"; };
012A0C9D22D47C220068E8EF /* Actor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actor.swift; sourceTree = "<group>"; };
012A0CA122D7AD0A0068E8EF /* Animation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Animation.swift; sourceTree = "<group>"; };
012DF10722E251CF00D52706 /* Effect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Effect.swift; sourceTree = "<group>"; };
01467C3D22E6F54600B5607D /* Easing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Easing.swift; sourceTree = "<group>"; };
016E41AF228E9A5B00ACF137 /* Rampage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rampage.app; sourceTree = BUILT_PRODUCTS_DIR; };
016E41B2228E9A5B00ACF137 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
016E41B4228E9A5B00ACF137 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -152,6 +156,8 @@
012A0C6022CC200D0068E8EF /* Billboard.swift */,
01D09AF222A482030052745A /* Bitmap.swift */,
01D09AF022A481AB0052745A /* Color.swift */,
01467C3D22E6F54600B5607D /* Easing.swift */,
012DF10722E251CF00D52706 /* Effect.swift */,
01D09B0222A4958E0052745A /* Input.swift */,
012A0C6122CC200D0068E8EF /* Monster.swift */,
01D09AFA22A485040052745A /* Player.swift */,
Expand Down Expand Up @@ -294,6 +300,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
012DF10822E251CF00D52706 /* Effect.swift in Sources */,
012A0C6322CC200E0068E8EF /* Monster.swift in Sources */,
01D09B0B22A7F7570052745A /* Textures.swift in Sources */,
01D09AFF22A48E990052745A /* Tilemap.swift in Sources */,
Expand All @@ -303,6 +310,7 @@
012A0C4F22C96E1F0068E8EF /* Thing.swift in Sources */,
01D09AF922A484B10052745A /* Vector.swift in Sources */,
01D09AF322A482030052745A /* Bitmap.swift in Sources */,
01467C3E22E6F54600B5607D /* Easing.swift in Sources */,
01D09B0722A6E09B0052745A /* Rotation.swift in Sources */,
01D09AFB22A485040052745A /* Player.swift in Sources */,
012A0C6222CC200E0068E8EF /* Billboard.swift in Sources */,
Expand Down

0 comments on commit 6d3decf

Please sign in to comment.