Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Game controller and tvOS support #14

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Source/Engine/Actor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ public extension Actor {
return rect.intersection(with: door.rect)
}

func intersection(with pushwall: Pushwall) -> Vector? {
return rect.intersection(with: pushwall.rect)
}

func intersection(with world: World) -> Vector? {
if let intersection = intersection(with: world.map) {
return intersection
Expand All @@ -50,6 +54,11 @@ public extension Actor {
return intersection
}
}
for pushwall in world.pushwalls where pushwall.position != position {
if let intersection = intersection(with: pushwall) {
return intersection
}
}
return nil
}

Expand All @@ -67,4 +76,20 @@ public extension Actor {
attempts -= 1
}
}

func isStuck(in world: World) -> Bool {
// If outside map
if position.x < 1 || position.x > world.map.size.x - 1 ||
position.y < 1 || position.y > world.map.size.y - 1 {
return true
}
// If stuck in a wall
if world.map[Int(position.x), Int(position.y)].isWall {
return true
}
// If stuck in pushwall
return world.pushwalls.contains(where: {
abs(position.x - $0.position.x) < 0.6 && abs(position.y - $0.position.y) < 0.6
})
}
}
7 changes: 3 additions & 4 deletions Source/Engine/Color.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ public extension Color {
static let clear = Color(r: 0, g: 0, b: 0, a: 0)
static let black = Color(r: 0, g: 0, b: 0)
static let white = Color(r: 255, g: 255, b: 255)
static let gray = Color(r: 192, g: 192, b: 192)
static let red = Color(r: 255, g: 0, b: 0)
static let green = Color(r: 0, g: 255, b: 0)
static let blue = Color(r: 0, g: 0, b: 255)
static let red = Color(r: 217, g: 87, b: 99)
static let green = Color(r: 153, g: 229, b: 80)
static let yellow = Color(r: 251, g: 242, b: 54)
}
11 changes: 9 additions & 2 deletions Source/Engine/Door.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public struct Door {
public extension Door {
var rect: Rect {
let position = self.position + direction * (offset - 0.5)
return Rect(min: position, max: position + direction)
let depth = direction.orthogonal * 0.1
return Rect(min: position + depth, max: position + direction - depth)
}

var offset: Double {
Expand Down Expand Up @@ -70,8 +71,13 @@ public extension Door {
mutating func update(in world: inout World) {
switch state {
case .closed:
if world.player.intersection(with: self) != nil {
if world.player.intersection(with: self) != nil ||
world.monsters.contains(where: { monster in
monster.isDead == false &&
monster.intersection(with: self) != nil
}) {
state = .opening
world.playSound(.doorSlide, at: position)
time = 0
}
case .opening:
Expand All @@ -82,6 +88,7 @@ public extension Door {
case .open:
if time >= closeDelay {
state = .closing
world.playSound(.doorSlide, at: position)
time = 0
}
case .closing:
Expand Down
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
}
}
138 changes: 138 additions & 0 deletions Source/Engine/MapGenerator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//
// MapGenerator.swift
// Engine
//
// Created by Nick Lockwood on 09/05/2020.
// Copyright © 2020 Nick Lockwood. All rights reserved.
//

public struct MapGenerator {
public private(set) var map: Tilemap
private var rng: RNG
private var elevatorPosition: Vector!
private var playerPosition: Vector!
private var emptyTiles: [Vector] = []
private var wallTiles: [Vector] = []

public init(mapData: MapData, index: Int) {
self.map = Tilemap(mapData, index: index)
self.rng = RNG(seed: mapData.seed ?? .random(in: 0 ... .max))

// Find empty tiles
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)
if map[x, y].isWall {
if map[x, y] == .elevatorBackWall {
map[thing: x, y] = .switch
}
wallTiles.append(position)
} else {
if map[x, y] == .elevatorFloor {
elevatorPosition = position
}
switch map[thing: x, y] {
case .nothing:
emptyTiles.append(position)
case .player:
playerPosition = position
default:
break
}
}
}
}

// Add doors
for position in emptyTiles {
let x = Int(position.x), y = Int(position.y)
let left = map[x - 1, y], right = map[x + 1, y],
up = map[x, y - 1], down = map[x, y + 1]
if (left.isWall && right.isWall && !up.isWall && !down.isWall)
|| (!left.isWall && !right.isWall && up.isWall && down.isWall) {
add(.door, at: position)
}
}

// Add push-walls
for _ in 0 ..< (mapData.pushwalls ?? 0) {
add(.pushwall, at: wallTiles.filter { position in
let x = Int(position.x), y = Int(position.y)
guard x > 0, x < map.width - 1, y > 0, y < map.height - 1 else {
return false // Outer wall
}
let left = map[x - 1, y], right = map[x + 1, y],
up = map[x, y - 1], down = map[x, y + 1]
if left.isWall, right.isWall, !up.isWall, !down.isWall,
!map[x, y - 2].isWall, !map[x, y + 2].isWall {
return true
}
if !left.isWall, !right.isWall, up.isWall, down.isWall,
!map[x - 2, y].isWall, !map[x + 2, y].isWall {
return true
}
return false
}.randomElement(using: &rng))
}

// Add player
if playerPosition == nil {
playerPosition = emptyTiles.filter {
findPath(from: $0, to: elevatorPosition, maxDistance: 1000).isEmpty == false
}.randomElement(using: &rng)
add(.player, at: playerPosition)
}

// Add monsters
for _ in 0 ..< (mapData.monsters ?? 0) {
add(.monster, at: emptyTiles.filter {
(playerPosition - $0).length > 2.5
}.randomElement(using: &rng))
}

// Add medkits
for _ in 0 ..< (mapData.medkits ?? 0) {
add(.medkit, at: emptyTiles.randomElement(using: &rng))
}

// Add shotguns
for _ in 0 ..< (mapData.shotguns ?? 0) {
add(.shotgun, at: emptyTiles.randomElement(using: &rng))
}
}
}

private extension MapGenerator {
mutating func add(_ thing: Thing, at position: Vector?) {
if let position = position {
map[thing: Int(position.x), Int(position.y)] = thing
if let index = emptyTiles.lastIndex(of: position) {
emptyTiles.remove(at: index)
}
}
}
}

extension MapGenerator: Graph {
public typealias Node = Vector

public func nodesConnectedTo(_ node: Node) -> [Node] {
return [
Node(x: node.x - 1, y: node.y),
Node(x: node.x + 1, y: node.y),
Node(x: node.x, y: node.y - 1),
Node(x: node.x, y: node.y + 1),
].filter { node in
let x = Int(node.x), y = Int(node.y)
return map[x, y].isWall == false
}
}

public func estimatedDistance(from a: Node, to b: Node) -> Double {
return abs(b.x - a.x) + abs(b.y - a.y)
}

public func stepDistance(from a: Node, to b: Node) -> Double {
return 1
}
}
Loading